🌙 经典排序算法复杂度
🌙 冒泡排序(稳定)(n^2)
🌙 1.常规冒泡排序
相邻比较,两两交换
function bubbleSort1(nums) {
const n = nums.length
// 从前往后遍历
for (let i = 0; i < n; i++) {
for (let j = 0; j < n - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
[nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];
}
}
}
return nums;
}
function bubbleSort2(nums) {
let n = nums.length
// 从后往前遍历
for (let i = n - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
[nums[j], nums[j + 1]] = [nums[j + 1], nums[j]]
}
}
}
return nums
}
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
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
🌙 2.改进冒泡排序-标志位
function bubbleSort(data) {
if (data.length < 2) return data;
let len = data.length;
let flag = true;
for (let i = 0; i < len; i++) {
flag = true
for (let j = 0; j < len - i - 1; j++) {
if (data[j] > data[j + 1]) {
[data[j], data[j + 1]] = [data[j + 1], data[j]];
// 交换了
flag = false;
}
}
// 如果没有发生交换,则退出循环
if (flag) break;
}
return data
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🌙 2.改进冒泡排序-双向
function bubbleSort(data) {
if (data.length < 2) return data;
let left = 0;
let right = data.length - 1;
while (left < right) {
let flag = true
for (let i = left; i < right; i++) {
if (data[i] > data[i + 1]) {
[data[i], data[i + 1]] = [data[i + 1], data[i]];
flag = false
}
}
// 最右边已经是最大的了
right--;
for (let i = right; i > left; i--) {
if (data[i] < data[i - 1]) {
[data[i], data[i - 1]] = [data[i - 1], data[i]];
flag = false
}
}
// 最左边已经时最小的了
left++;
if (flag) break;
}
return data;
}
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
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
🌙 选择排序(不稳定)(n^2)
- 基础版
每轮记录最小的下标,然后交换到首位
function selectionSort1(data) {
let n = data.length
let minIndex;
for (let i = 0; i < n - 1; i++) {
// 假设当前的最小数的下标为i, 记录一下
minIndex = i;
// 比较之后的数,找到更小的数的下表然后替换minIndex
for (let j = i + 1; j < n; j++) {
if (data[j] < data[minIndex]) {
minIndex = j;
}
}
// 交换位置
[data[i], data[minIndex]] = [data[minIndex], data[i]];
}
return data;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
为了便于后续优化的理解,使用while循环实现:
function selectionSort2(nums) {
let n = nums.length
let left = 0
let right = n - 1
while (left < right) {
let minIndex = left
// 处理最小值
for (let i = left; i <= right; i++) {
if (nums[i] < nums[minIndex]) minIndex = i
}
if (minIndex !== left) {
[nums[minIndex], nums[left]] = [nums[left], nums[minIndex]]
}
// 左向右递进
left++
}
return nums
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 优化版 每轮记录最小数的下标和最大数的下标,然后将最小数交换到首位,最大数交换到尾部
function optimizedSelectionSort1(nums) {
let n = nums.length
let left = 0
let right = n - 1
while (left < right) {
let minIndex = left
let maxIndex = right
// 先处理最小值
for (let i = left; i <= right; i++) {
if (nums[i] < nums[minIndex]) minIndex = i
}
if (minIndex !== left) {
[nums[minIndex], nums[left]] = [nums[left], nums[minIndex]]
}
// 再处理最大值
for (let i = left; i <= right; i++) {
if (nums[i] > nums[maxIndex]) maxIndex = i
}
if (maxIndex !== right) {
[nums[maxIndex], nums[right]] = [nums[right], nums[maxIndex]]
}
// 左右向中间收敛
left++
right--
}
return nums
}
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
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
进一步优化,将最大最小值的判断合并为一个:
function optimizedSelectionSort2(nums) {
let n = nums.length
let left = 0
let right = n - 1
while (left < right) {
let minIndex = left
let maxIndex = right
for (let i = left; i <= right; i++) {
if (nums[i] < nums[minIndex]) minIndex = i
if (nums[i] > nums[maxIndex]) maxIndex = i
}
if (minIndex !== left) {
[nums[minIndex], nums[left]] = [nums[left], nums[minIndex]]
}
// 如果最大的在左边,由于上一步已经把左边的交换了,所以需要修正
if (maxIndex === left) {
maxIndex = minIndex;
}
if (maxIndex !== right) {
[nums[maxIndex], nums[right]] = [nums[right], nums[maxIndex]]
}
left++
right--
}
return nums
}
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
34
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
34
🌙 插入排序(稳定)(n^2)
function insertionSort(nums) {
let n = nums.length
for (let i = 1; i < n; i++) {
for (let j = i; j > 0 && nums[j] < nums[j - 1]; j--) {
[nums[j], nums[j - 1]] = [nums[j - 1], nums[j]]
}
}
return nums
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
🌙 *希尔排序(不稳定)(O(n^(3/2)))
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
插入排序相当于 gap = 1 的希尔排序
function shellSort(nums) {
let n = nums.length
// 使用 Knuth 序列计算初始 gap,时间复杂度 O(n^(3/2))
// let gap = 1;
// while (gap < n / 3) {
// gap = gap * 3 + 1; // 1, 4, 13, 40, ...
// }
// gap = Math.floor(gap / 3);
// 选择 n/2作为gap
for(let gap = n >> 1; gap > 0; gap = gap >> 1) {
for(let i=gap; i < n; i++) {
for(let j=i; j>gap-1 && nums[j] < nums[j-gap]; j -= gap) {
[nums[j], nums[j-gap]] = [nums[j-gap], nums[j]]
}
}
}
return nums
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
使用Knuth序列:
function shellSort(nums) {
let n = nums.length
// 计算gap
let h = 1;
while (h < n / 3) {
h = h * 3 + 1;
}
for(let gap = h; gap > 0; gap = Math.floor((gap - 1) / 3)) {
for(let i=gap; i < n; i++) {
for(let j=i; j>gap-1 && nums[j] < nums[j-gap]; j -= gap) {
[nums[j], nums[j-gap]] = [nums[j-gap], nums[j]]
}
}
}
return nums
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
🌙 快速排序(不稳定)(nlogn)
- 使用额外数组
function quickSort(data) {
if (data.length < 2) return data;
// 提出最后一个作为基准
let pivot = data.pop();
// 将data分为基准两侧的
let left = [];
let right = [];
for (let i = 0; i < data.length; i++) {
if (data[i] < pivot) {
left.push(data[i]);
} else {
right.push(data[i])
}
}
// 递归调用 [基准左侧, 基准, 基准右侧]
return [...quickSort(left), pivot, ...quickSort(right)];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 使用指针
function quickSort(nums) {
// 结果:左子数组任意元素 <= 基准元素 <= 右子数组任意元素
function _partition(_nums, left, right) {
// 以nums[left]作为基准
let i = left
let j = right
while (i < j) {
// 从右往左找到首个小于基准的数
while (i < j && _nums[j] >= _nums[left]) {
j--
}
// 从左往右找到首个大于基准的数
while (i < j && _nums[i] <= _nums[left]) {
i++
}
// 交换这两个元素
[_nums[i], _nums[j]] = [_nums[j], _nums[i]]
}
// 将基准数交换至两子数组的分界线
[_nums[i], _nums[left]] = [_nums[left], _nums[i]]
return i
}
function _sort(_nums, left, right) {
if (left >= right) return
const pivotIndex = _partition(_nums, left, right)
_sort(_nums, left, pivotIndex - 1)
_sort(_nums, pivotIndex + 1, right)
}
_sort(nums, 0, nums.length - 1)
return nums
}
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
34
35
36
37
38
39
40
41
42
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
34
35
36
37
38
39
40
41
42
🌙 归并排序(稳定)(nlogn)
- 直接切分数组(非原地排序)
function mergeSort(arr) {
if (arr.length < 2) return arr;
let mid = arr.length >> 1;
let left = arr.slice(0, mid);
let right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
function merge(left, right) {
let res = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
res.push(left[i]);
i++;
} else {
res.push(right[j]);
j++;
}
}
return [...res, ...left.slice(i), ...right.slice(j)]
}
}
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
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
- 指针切分数组(原地排序)
function mergeSort(nums) {
/*合并左子数组和右子数组*/
// 数组1: [left, mid] 数组2: [mid+1, right]
function _merge(_nums, left, mid, right) {
//左子数组区间为[left, mid],右子数组区间为[mid+1, right]
// 创建一个临时数组tmp,用于存放合并后的结果
const tmp = new Array(right - left + 1);
//初始化左子数组和右子数组的起始索引
let i = left, j = mid + 1, k = 0;
// 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中
while (i <= mid && j <= right) {
if (_nums[i] <= _nums[j]) {
tmp[k++] = _nums[i++];
} else {
tmp[k++] = _nums[j++];
}
}
//将左子数组和右子数组的剩余元素复制到临时数组中
while (i <= mid) {
tmp[k++] = _nums[i++];
}
while (j <= right) {
tmp[k++] = _nums[j++];
}
//将临时数组tmp中的元素复制回原数组nums的对应区间
for (k = 0; k < tmp.length; k++) {
_nums[left + k] = tmp[k];
}
}
/*归并排序*/
// 闭区间: [left, right]
function _mergeSort(_nums, left, right) {
//终止条件
if (left >= right) return;
//当子数组长度为1时终止递归
// 划分阶段
// let mid = Math.floor((left + right) / 2);//计算中点
let mid = ~~((left + right) / 2);//计算中点
_mergeSort(_nums, left, mid);//递归左子数组
_mergeSort(_nums, mid + 1, right);//递归右子数组
// 合并阶段
_merge(_nums, left, mid, right);
}
_mergeSort(nums, 0, nums.length - 1)
return nums
}
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
https://www.cnblogs.com/jztan/p/12273671.html
https://www.jianshu.com/p/a28db3d3cc18
🌙 堆排序(不稳定)(nlogn) (opens new window)
function heapSort(nums: number[]): number[] {
let n = nums.length
/**
* 由于叶节点没有子节点,因此它们天然就是合法的子堆,无须堆化
* 将除叶子节点以外的其他节点堆化 索引 >= Math.floor(n / 2) 的为叶子结点
* 堆顶[1,2,3,4,5,6,7,8,9,10,11]堆底
* 索引 >= 5 的为叶子结点
* 1
* 2 3
* 4 5 6 7
* 8 9 10 11
*
* */
// 从底部节点开始至顶部节点堆化
for(let i=Math.floor(n / 2) - 1; i>=0; i--) {
siftDown(nums, n, i)
}
for(let i=n-1; i>=0; i--) {
swap(nums, 0, i)
siftDown(nums, i, 0)
}
// 交换i,j处的元素
function swap(nums, i, j) {
[nums[i], nums[j]] = [nums[j], nums[i]]
}
// 从顶至底堆化
function siftDown(nums, n, i) {
while(true) {
let root = i // 根节点 交换之后:left <= max >= right
let left = i * 2 + 1 // 左叶子结点
let right = i * 2 + 2 // 右叶子节点
if(left < n && nums[left] > nums[root]) {
root = left
}
if(right < n && nums[right] > nums[root]) {
root = right
}
if(root === i) break
swap(nums, i, root)
i = root
}
}
return nums
}
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54