算法 | 常见的排序算法及其优化(含图示和JS实现)
冒泡排序
冒泡排序是通过重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成
算法思路
(以升序为例)
- 比较相邻元素,初始为索引为0和索引为1的元素,如果第一个比第二个大,就交换这两个元素
- 对每一对相邻元素重复同样的工作,直到最后一对,索引为
arr.length - 2
和arr.length - 1
,这样arr.length - 1
索引处的元素将会是最大的 - 重复步骤1和2,得到索引为
arr.length - 2
为剩余元素中最大的 - 重复步骤1-3,直到排序完成
算法实现
1 | function bubbleSort(arr) { |
代码优化
外层优化:在某一层循环的时候并没有发生交换,说明前面已经全部有序,不需要再执行下去了,直接退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29function bubbleSortA(arr) {
for (let i = arr.length; i > 0; i--) {
let flag = false; // flag用于标识此轮是否有交换行为
for (let j = 0; j < i; j++) {
if (arr[j-1] > arr[j]) {
flag = true; // flag === true就表示有
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
if (flag === false) { // 这一轮没有交换行为 就表示全部都有序了 直接返回arr
return arr;
}
};
return arr;
}
/*
复杂度分析:
时间复杂度: 最好O(n),只需要循环一次,最坏O(n^2)
空间复杂度: O(1)
稳定性分析:冒泡排序并不会破坏元素的相对位置
稳定
*/
// test
const arr = [2,7,4,3,8,9,10,1,5,2,3];
bubbleSortA(arr); // [1, 2, 2, 3, 3, 4, 5, 7, 8, 9, 10]内层优化:在每一层循环的时候,记住这一层循环最后一次交换的位置,这样就说明后面的是有序的不用重复比较交换了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34function bubbleSortB(arr) {
let i = arr.length, pos; // 用pos记录最后一次交换的位置
while (i > 0) {
let flag = false; // flag用于标识此轮是否有交换行为
for (let j = 0; j < i; j++) {
if (arr[j-1] > arr[j]) {
flag = true; // flag === true就表示有
pos = j; // 目前最后一次交换在j的位置
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
};
i = pos; // 边界用pos代替
if (flag === false) { // 说明此轮没有交换行为
return arr;
}
}
return arr;
}
// test
const arr = [2,7,4,3,8,9,10,1,5,2,3];
bubbleSortB(arr); // [1, 2, 2, 3, 3, 4, 5, 7, 8, 9, 10]
/*
复杂度分析:
时间复杂度: O(n^2)
空间复杂度: O(1)
稳定性分析:冒泡排序并不会破坏元素的相对位置
稳定
*/
选择排序
算法思路
- 首先在未排序的数组中找到最大(小)的元素,放在排序数组的起始位置
- 再从剩余的元素中找到最大(小)的元素,放在已经排序的末尾
- 重复步骤2,直至排序完成
算法实现
1 | function selectSort(arr) { |
代码优化
- 优化:一次就选出最大最小的
1 | function selectSortA(arr) { |
插入排序
工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
算法思路
- 初始化:将第一个元素看作是有序数组,将第一个元素之后的元素看作是未排序序列
- 遍历未排序序列,并依次插入到有序数组的合适位置(这里需要注意的是,插入到排序数组合适位置之后排序数组的其他元素的位置是如何处理的?)
- 返回排序数组
算法实现
1 | function insertSort(arr) { |
算法优化
优化点:在找插入位置的时候可以采用二分查找
1 | function insertSortA(arr) { |
归并排序
算法步骤
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设两个指针,最初位置为两个已经排好序序列的起始位置
- 比较两个指针所指向的元素,选择相对较小的元素放入合并空间,并移动指针
- 重复步骤3直到指针到达末尾
- 将另一序列剩下的元素直接合并到序列尾部
算法实现
1 | function mergeSort(arr) { |
快速排序
算法思路
- 找到一个元素,作为基准
- 剩余元素重新排序,比基准小的放在左边,比基准大的放在右边
- 递归左边和右边,得到结果
算法实现
1 | function quickSort(arr) { |
堆排序
算法思路
- 构造大顶堆,把堆顶元素和最后一个元素交换(相当于把最大的元素放在最后面了),堆大小减去1
- 交换后,把剩余的元素继续构造大顶堆,即把剩余元素最大的元素选出来放在堆顶,重复步骤1
- 继续重复步骤1和2,直至堆大小为1
算法实现
1 | // 实现最大堆的构建 |