目录
[1. 冒泡排序 (Bubble Sort](#1. 冒泡排序 (Bubble Sort)
[2. 选择排序 (Selection Sort)](#2. 选择排序 (Selection Sort))
[3. 插入排序 (Insertion Sort)](#3. 插入排序 (Insertion Sort))
[4. 快速排序 (Quick Sort)](#4. 快速排序 (Quick Sort))
[5. 归并排序 (Merge Sort)](#5. 归并排序 (Merge Sort))
[6. 堆排序 (Heap Sort)](#6. 堆排序 (Heap Sort))
|----------|----------------------------------------------------|-----------|-----------------------------------------------------|
| 排序算法 | 时间复杂度 | 空间复杂度 | 备注 |
| 冒泡排序 | 最好情况: O(n) 平均情况: O(n^2) 最坏情况: O(n^2) | O(1) | 原地排序,只需常量级额外空间 |
| 选择排序 | 最好情况: O(n^2) 平均情况: O(n^2) 最坏情况: O(n^2) | O(1) | 原地排序,只需常量级额外空间 |
| 插入排序 | 最好情况: O(n) 平均情况: O(n^2) 最坏情况: O(n^2) | O(1) | 原地排序,只需常量级额外空间 |
| 快速排序 | 最好情况: O(n log n) 平均情况: O(n log n) 最坏情况: O(n^2 | O(log n) | 递归调用栈空间,最好情况O(log n),最坏情况O(n),但平均情况为O(log n) |
| 归并排序 | 最好情况:O(n log n) 平均情况: O(n log n) 最坏情况: O(n log n) | O(n) | 需要额外的临时数组来存储合并结果 |
| 堆排序 | 最好情况: O(n log n) 平均情况: O(n log n) 最坏情况: O(n log n) | O(1) | 原地排序,堆的调整过程在数组内部进行,只需常量级额外空间(不考虑递归实现,若考虑递归则与快速排序类似) |
1. 冒泡排序 (Bubble Sort
时间复杂度:
- 最好情况: O(n)
- 平均情况: O(n^2)
- 最坏情况: O(n^2)
javascript
function bubbleSort(arr) {
let n = arr.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
2. 选择排序 (Selection Sort)
原理:
选择排序是一种简单直观的排序算法。它的工作原理是首先在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
具体步骤如下:
- 从未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(或最大)元素,放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
特点:
- 选择排序的时间复杂度为O(n^2),其中n是待排序元素的数量。
- 选择排序是一种原地排序算法,因为它只需要一个额外的空间来存储当前找到的最小(或最大)元素。
- 选择排序不是稳定的排序算法,因为相同元素的相对位置可能会在排序过程中发生改变。
时间复杂度:
- 最好情况: O(n^2)
- 平均情况: O(n^2)
- 最坏情况: O(n^2)
javascript
function selectionSort(arr) {
let n = arr.length;
for (let i = 0; i < n - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换元素
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
return arr;
}
3. 插入排序 (Insertion Sort)
原理:
插入排序的工作方式是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,找到相应位置并插入时,不需要移动元素,只需将要插入的元素移动到插入点即可。
具体步骤如下:
- 从第一个元素开始,该元素可以认为已经被排序。
- 取出下一个元素,在已经排序的元素序列中从后向前扫描。
- 如果该元素(已排序)大于新元素,则将该元素移到下一位置。
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
- 将新元素插入到该位置后。
- 重复步骤2~5,直到所有元素均排序完毕。
特点:
- 插入排序的时间复杂度为O(n^2),在数据规模较小时表现良好,特别是当数据基本有序时,时间复杂度可以接近O(n)。
- 插入排序是一种原地排序算法,因为它只需要一个额外的空间来存储当前正在插入的元素。
- 插入排序是稳定的排序算法,因为相同元素的相对位置在排序过程中不会发生改变。
时间复杂度:
- 最好情况: O(n)
- 平均情况: O(n^2)
- 最坏情况: O(n^2)
javascript
function insertionSort(arr) {
let n = arr.length;
for (let i = 1; i < n; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
return arr;
}
4. 快速排序 (Quick Sort)
原理:
快速排序是一种通过基准划分区块,再不断交换左右项的排序方式。它采用了分治法,减少了交换的次数。快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归或迭代进行,以此让整个数列变成有序序列。
具体步骤如下:
- 在待排序区间找到一个基准点(pivot),一般选择数组的第一个元素、最后一个元素或者随机选择一个元素。
- 逐个循环数组,将小于基准的项放在左侧,将大于基准的项放在右侧。一般通过交换的方式来实现。
- 对基准点左侧全部项和基点右侧全部项分别通过递归(或迭代)方式重复上述步骤,直到所有数组都交换完成。
时间复杂度:
- 最好情况: O(n log n)
- 平均情况: O(n log n)
- 最坏情况: O(n^2) (但可以通过随机化选择基准元素等方法优化)
javascript
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
let pivot = arr[Math.floor(arr.length / 2)];
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++) {
if (i === Math.floor(arr.length / 2)) continue;
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
5. 归并排序 (Merge Sort)
原理:
归并排序是一种分治算法,其工作原理是将未排序的列表划分为n个子列表,每个子列表包含一个元素(包含一个元素的列表被认为是有序的),然后重复合并子列表以生成新的有序子列表,直到只剩下一个子列表。
具体步骤如下:
- 分解:将待排序的n个元素的序列分成两个子序列,每个子序列包含n/2个元素。
- 解决:使用归并排序递归地排序两个子序列。
- 合并:将两个已排序的子序列合并成一个最终的排序序列。
时间复杂度:
- 最好情况: O(n log n)
- 平均情况: O(n log n)
- 最坏情况: O(n log n)
javascript
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
const middle = Math.floor(arr.length / 2);
const left = arr.slice(0, middle);
const right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}
return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
}
6. 堆排序 (Heap Sort)
堆排序(Heap Sort)是一种基于堆(Heap)这种数据结构的比较排序算法。堆是一个近似完全二叉树的结构,分为最大堆(Max Heap)和最小堆(Min Heap)。在最大堆中,每个节点的值都大于或等于其子节点的值;在最小堆中,每个节点的值都小于或等于其子节点的值。堆排序通常使用最大堆来实现升序排序
堆排序原理:
- 构建最大堆:
- 将数组看作一个完全二叉树,构建最大堆。
- 从最后一个非叶子节点开始,向上依次调整堆,使得每个子树都满足最大堆的性质。
2.堆排序过程:
- 将堆顶元素(最大值)与堆的最后一个元素交换。
- 堆的大小减1,重新调整堆顶元素所在的子树,使其满足最大堆的性质。
- 重复上述步骤,直到堆的大小为1。
时间复杂度:
- 最好情况: O(n log n)
- 平均情况: O(n log n)
- 最坏情况: O(n log n)
javascript
function heapSort(arr) {
let n = arr.length;
// 构建最大堆
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 一个个从堆顶取出元素
for (let i = n - 1; i > 0; i--) {
// 交换当前堆顶(最大值)和最后一个元素
[arr[0], arr[i]] = [arr[i], arr[0]];
// 重新调整堆
heapify(arr, i, 0);
}
return arr;
}
function heapify(arr, n, i) {
let largest = i; //最大子节点
let left = 2 * i + 1; //左子节点
let right = 2 * i + 2; //右子节点
//如果左子节点存在且大于当前最大子节点
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
//如果右子节点存在且大于当前最大子节点
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
//如果最大值不是当前子节点,则交换
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]];
heapify(arr, n, largest);
}
}