基于JS算法-常用排序算法
ljt 2023/12/24 算法
# 前沿
亮点:
1、思维导图
2、例子代码
# 思维导图

# 概要
- 主要讲解十大排序,基于js实现
- 总结是空间换时间、时间换空间
# 时间复杂度为O(n^2)
# 冒泡排序
js代码实现
function bubbleSort(arr) {
const n = arr.length;
let swapped;
// 外层循环控制排序的轮数
for (let i = 0; i < n - 1; i++) {
swapped = false; // 每一轮开始时假设没有交换
// 内层循环进行相邻元素的比较和交换
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]];
swapped = true; // 记录发生了交换
}
}
// 如果这一轮没有交换,数组已经是有序的,可以提前退出
if (!swapped) {
break;
}
}
return arr;
}
// 示例
const arr = [64, 34, 25, 12, 22, 11, 90];
const sortedArr = bubbleSort(arr);
console.log(sortedArr);
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
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
操作解释
- 外层循环:控制整个排序的轮数。每次迭代确保至少将一个元素放到正确的位置。
- 内层循环:比较相邻的元素,如果它们的顺序错误,则交换它们。
n - 1 - i
是因为每轮后最大的元素会被放到正确的位置,所以可以减少比较的次数。 - 优化:使用
swapped
标志,如果一轮内没有交换,说明数组已经有序,可以提前退出循环,从而提高效率。
# 插入排序
基于js代码实现
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
const key = arr[i];
let j = i - 1;
// 将大于 key 的元素移动到右边
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
// 将 key 插入到合适的位置
arr[j + 1] = key;
}
return arr;
}
// 示例用法
const array = [5, 2, 9, 1, 5, 6];
const sortedArray = insertionSort(array);
console.log(sortedArray); // 输出: [1, 2, 5, 5, 6, 9]
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
操作解释
- 外层循环:从第二个元素开始遍历数组(索引从 1 开始),将每个元素视为待插入的
key
。 - 内层循环:从
key
的前一个元素开始,向左遍历已排序部分,将大于key
的元素向右移动一位,为key
腾出位置。 - 插入:将
key
插入到正确的位置。
# 时间复杂度为O(log(n))
# 快速排序
基于js代码实现
function quickSort(arr) {
// 基础情况:数组为空或只包含一个元素
if (arr.length <= 1) {
return arr;
}
// 选择基准(pivot),这里选择数组的最后一个元素
const pivot = arr[arr.length - 1];
const left = []; // 存放小于基准的元素
const right = []; // 存放大于基准的元素
// 遍历数组(不包括基准)
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] < pivot) {
left.push(arr[i]); // 小于基准的放左边
} else {
right.push(arr[i]); // 大于基准的放右边
}
}
// 递归调用快速排序并合并结果
return [...quickSort(left), pivot, ...quickSort(right)];
}
// 示例用法
const array = [5, 2, 9, 1, 5, 6];
const sortedArray = quickSort(array);
console.log(sortedArray); // 输出: [1, 2, 5, 5, 6, 9]
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
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
操作
- 基础情况:如果数组为空或只包含一个元素,则直接返回该数组,因为它已经是有序的。
- 选择基准:选择数组的最后一个元素作为基准(
pivot
)。 - 分割数组:遍历数组(不包括基准),将小于基准的元素放入
left
数组,大于基准的元素放入right
数组。 - 递归调用:分别对
left
和right
数组进行快速排序,并将结果合并,最后在中间插入基准。
# 归并排序
基于js代码实现
function mergeSort(arr) {
// 基础情况:数组为空或只包含一个元素
if (arr.length <= 1) {
return arr;
}
// 找到中间索引
const mid = Math.floor(arr.length / 2);
// 递归分割数组
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
// 合并已排序的子数组
return merge(left, right);
}
function merge(left, right) {
const result = [];
let i = 0; // 左边数组的指针
let j = 0; // 右边数组的指针
// 合并两个已排序的数组
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
// 将剩余元素添加到结果数组
return result.concat(left.slice(i)).concat(right.slice(j));
}
// 示例用法
const array = [5, 2, 9, 1, 5, 6];
const sortedArray = mergeSort(array);
console.log(sortedArray); // 输出: [1, 2, 5, 5, 6, 9]
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
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
操作
- 基础情况:如果数组为空或只包含一个元素,直接返回该数组。
- 分割数组:通过中间索引将数组分为两个子数组,分别递归调用
mergeSort
进行排序。 - 合并:使用
merge
函数将两个已排序的子数组合并成一个有序数组。该函数使用两个指针遍历两个子数组,并比较元素,将较小的元素放入结果数组中。 - 返回结果:在合并完成后,返回合并后的有序数组。
# 希尔排序
js实现
function shellSort(arr) {
const n = arr.length;
let gap = Math.floor(n / 2); // 初始间隔为数组长度的一半
// 逐步缩小间隔
while (gap > 0) {
// 对每个间隔进行插入排序
for (let i = gap; i < n; i++) {
const temp = arr[i]; // 当前要插入的元素
let j = i; // 当前元素的索引
// 插入排序
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap]; // 移动元素
j -= gap; // 更新索引
}
arr[j] = temp; // 插入当前元素
}
gap = Math.floor(gap / 2); // 缩小间隔
}
return arr;
}
// 示例用法
const array = [5, 2, 9, 1, 5, 6];
const sortedArray = shellSort(array);
console.log(sortedArray); // 输出: [1, 2, 5, 5, 6, 9]
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
操作
- 初始化间隔:设置初始间隔为数组长度的一半。
- 逐步缩小间隔:在
while
循环中,逐步减小间隔(gap)。 - 插入排序:对于每个间隔,进行插入排序。内层循环负责将当前元素插入到正确的位置。
- 返回结果:完成排序后返回排序后的数组。
# 时间复杂度为O(nlog(n))
# 堆排序
基于js实现
function heapSort(arr) {
const n = arr.length;
// 构建最大堆
function buildMaxHeap() {
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
heapify(i, n);
}
}
// 堆化
function heapify(i, heapSize) {
let largest = i;
const left = 2 * i + 1; // 左子节点
const right = 2 * i + 2; // 右子节点
if (left < heapSize && arr[left] > arr[largest]) {
largest = left;
}
if (right < heapSize && arr[right] > arr[largest]) {
largest = right;
}
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]]; // 交换
heapify(largest, heapSize); // 递归堆化
}
}
buildMaxHeap();
// 进行排序
for (let i = n - 1; i > 0; i--) {
[arr[0], arr[i]] = [arr[i], arr[0]]; // 将最大元素放到数组末尾
heapify(0, i); // 对剩余的元素进行堆化
}
return arr;
}
// 示例
const arr = [3, 5, 1, 10, 2, 7];
const sortedArr = heapSort(arr);
console.log(sortedArr);
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
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
代码解释
- 构建最大堆:
buildMaxHeap
函数从最后一个非叶子节点开始,向上调整每个节点,构建出一个最大堆。 - 堆化:
heapify
函数确保以i
为根的子树是一个最大堆。它检查当前节点与其左右子节点的值,若有子节点比当前节点大,则进行交换,并递归调整。 - 排序过程:在
heapSort
函数中,将最大元素(根节点)与数组末尾的元素交换,然后对剩余的元素重新堆化,以保证它们依然形成最大堆。
# 时间复杂度为O(n)
# 桶排序
基于js实现
function bucketSort(arr) {
if (arr.length === 0) return [];
// 1. 创建桶
const minValue = Math.min(...arr);
const maxValue = Math.max(...arr);
const bucketCount = Math.floor((maxValue - minValue) / arr.length) + 1;
const buckets = Array.from({ length: bucketCount }, () => []);
// 2. 将元素放入桶中
for (let num of arr) {
const index = Math.floor((num - minValue) / arr.length);
buckets[index].push(num);
}
// 3. 对每个桶进行排序并合并结果
return buckets.reduce((sortedArr, bucket) => {
return sortedArr.concat(bucket.sort((a, b) => a - b));
}, []);
}
// 示例
const arr = [0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12];
const sortedArr = bucketSort(arr);
console.log(sortedArr);
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
代码解释
- 创建桶:根据数组中的最小值和最大值,计算出桶的数量,并初始化桶数组。
- 将元素放入桶中:根据每个元素的值计算它应该放入哪个桶中。
- 排序并合并结果:对每个桶内的元素进行排序,然后将它们合并成一个最终的排序数组。
# 计数排序
基于js实现
function countingSort(arr) {
if (arr.length === 0) return arr;
// 找到最大值和最小值
const min = Math.min(...arr);
const max = Math.max(...arr);
const range = max - min + 1;
// 创建计数数组
const count = new Array(range).fill(0);
// 计数每个元素出现的次数
for (let num of arr) {
count[num - min]++;
}
// 累加计数数组
for (let i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 创建输出数组
const output = new Array(arr.length);
// 根据计数数组填充输出数组
for (let i = arr.length - 1; i >= 0; i--) {
const num = arr[i];
output[count[num - min] - 1] = num;
count[num - min]--;
}
return output;
}
// 示例用法
const arr = [4, 2, 2, 8, 3, 3, 1];
const sortedArr = countingSort(arr);
console.log(sortedArr); // 输出: [1, 2, 2, 3, 3, 4, 8]
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
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
代码解释
- 找到最大值和最小值:用
Math.min
和Math.max
方法找到数组中的最大和最小值,以确定计数数组的大小。 - 创建计数数组:根据值的范围创建一个计数数组,并用零填充。
- 计数:遍历原数组,记录每个元素的出现次数。
- 累加计数:对计数数组进行累加,使得每个位置存储的是该元素在排序后应该出现的最后位置。
- 填充输出数组:从原数组的最后一个元素开始,根据计数数组确定每个元素在输出数组中的位置,并更新计数数组。
- 返回结果:返回排序后的数组。
# 参考
主要是收集每个实现js的代码