这是我参与「第四届青训营 」笔记创作活动的的第7天
JavaScript排序算法笔记 | 青训营笔记
文中代码全部升序排序
直接插入排序(插入类)
将第i个记录插入到前面i-1个已经排好序的记录中
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
- 稳定性:稳定
解读:在待排序的关键字序列基本有序且关键字个数n较少时,其算法的性能最佳。
代码:
function InsertSort(arr) {
const n = arr.length;
for (let i = 1; i < n; i++) {
let tmp = arr[i];
// i->n是无序部分
// 0->j是有序部分
for (let j = i - 1; j >= 0; j--) {
// 把arr[i]插入到arr[0...i-1]中,使其有序
if (tmp < arr[j]) {
arr[j + 1] = arr[j];
arr[j] = tmp;
}else{
break;
}
}
console.log(arr);
}
return arr;
}
arr = [8, 6, 3, 4, 2, 7, 3, 9, 0]
InsertSort(arr)
(9) [6, 8, 3, 4, 2, 7, 3, 9, 0]
(9) [3, 6, 8, 4, 2, 7, 3, 9, 0]
(9) [3, 4, 6, 8, 2, 7, 3, 9, 0]
(9) [2, 3, 4, 6, 8, 7, 3, 9, 0]
(9) [2, 3, 4, 6, 7, 8, 3, 9, 0]
(9) [2, 3, 3, 4, 6, 7, 8, 9, 0]
(9) [2, 3, 3, 4, 6, 7, 8, 9, 0]
(9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
(9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
排序过程:
arr = [8, 6, 3, 4, 2, 7, 3, 9, 0]
(9) [6, 8, 3, 4, 2, 7, 3, 9, 0] (9) [3, 6, 8, 4, 2, 7, 3, 9, 0] (9) [3, 4, 6, 8, 2, 7, 3, 9, 0] (9) [2, 3, 4, 6, 8, 7, 3, 9, 0] (9) [2, 3, 4, 6, 7, 8, 3, 9, 0] (9) [2, 3, 3, 4, 6, 7, 8, 9, 0] (9) [2, 3, 3, 4, 6, 7, 8, 9, 0] (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
折半插入排序(插入类)
它是对直接插入排序的一种优化算法。
对有序表进行查找时,折半查找(二分法) 的性能优于顺序查找。
这里改变了比较的时间O(nlogn),但是交换的时间没有变化O(n^2)。
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
- 稳定性:稳定
// 折半
function BinSort(arr) {
const n = arr.length;
for (let i = 1; i < n; i++) {
let tmp = arr[i];
// i->n是无序部分
// 0->j是有序部分
let begin = 0;
let end = i - 1;
while (begin <= end) {
let mid = Math.floor((begin + end) / 2);
if (arr[mid] > tmp) {
end = mid - 1;
} else {
begin = mid + 1;
}
}
for (let j = i - 1; j >= begin; j--) {
arr[j + 1] = arr[j];
}
arr[begin] = tmp;
console.log(arr);
}
return arr;
}
BinSort(arr)
arr: (9)[8, 6, 3, 4, 2, 7, 3, 9, 0]
(9)[6, 8, 3, 4, 2, 7, 3, 9, 0]
(9)[3, 6, 8, 4, 2, 7, 3, 9, 0]
(9)[3, 4, 6, 8, 2, 7, 3, 9, 0]
(9)[2, 3, 4, 6, 8, 7, 3, 9, 0]
(9)[2, 3, 4, 6, 7, 8, 3, 9, 0]
(9)[2, 3, 3, 4, 6, 7, 8, 9, 0]
(9)[2, 3, 3, 4, 6, 7, 8, 9, 0]
(9)[0, 2, 3, 3, 4, 6, 7, 8, 9]
希尔排序(插入类)
对直接插入排序的一种优化算法
希尔排序是把序列按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量的逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个序列恰好被分为一组,算法便终止。
function ShellSort(arr) {
const n = arr.length;
let gap = Math.floor(n / 2);
while (gap >= 1) {
for (let i = gap; i < n; i++) {
let tmp = arr[i];
for (let j = i - gap; j >= 0; j -= gap) {
if (arr[j] > tmp) {
arr[j + gap] = arr[j];
arr[j] = tmp;
} else {
break;
}
}
}
gap = Math.floor(gap / 2);
console.log(arr);
}
}
ShellSort(arr)
// arr: (9) [8, 6, 3, 4, 2, 7, 3, 9, 0]
// (9) [0, 6, 3, 4, 2, 7, 3, 9, 8]
// (9) [0, 4, 2, 6, 3, 7, 3, 9, 8]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
- 时间复杂度:
gap = gap / 2时,平均复杂度:O(nlogn),最坏复杂度:O(n^2)
注:gap的取值不同,它所对应的时间复杂度也不同。Knuth提出gap=gap/3+1的时间复杂度为O(n^1.25)~1.6O(n^1.25)。
- 空间复杂度:O(1)
- 稳定性:不稳定
用这样步长序列的希尔排序比插入排序要快,甚至在小数组中比快速排序和堆排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。
冒泡排序(相邻比序法)
反复扫描待排序记录序列,在扫描过程中顺次比较相邻俩个元素的大小,若逆序就交换。
function BubbleSort(arr) {
const 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]) {
let tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
console.log(arr);
}
}
arr = [8, 6, 3, 4, 2, 7, 3, 9, 0]
console.log("arr:", arr);
BubbleSort(arr);
// arr: (9) [8, 6, 3, 4, 2, 7, 3, 9, 0]
// (9) [6, 3, 4, 2, 7, 3, 8, 0, 9]
// (9) [3, 4, 2, 6, 3, 7, 0, 8, 9]
// (9) [3, 2, 4, 3, 6, 0, 7, 8, 9]
// (9) [2, 3, 3, 4, 0, 6, 7, 8, 9]
// (9) [2, 3, 3, 0, 4, 6, 7, 8, 9]
// (9) [2, 3, 0, 3, 4, 6, 7, 8, 9]
// (9) [2, 0, 3, 3, 4, 6, 7, 8, 9]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
- 稳定性:稳定
快速排序(交换类排序)
快速排序是冒泡排序的改进方法。冒泡排序一次只能消除一个逆序,快速排序中的一次交换可能消除多个逆序。
算法思想: 从待排序的数据中选取一个元素为基准值,然后将小于基准值的元素放到基准值的前面,将大于等于基准值的元素放到基准值的后面。这样就将待排序的数据分为俩个子表,将这个过程称为一趟快速排序。对分割后的子表继续按照上面的方法进行操作,直至所有子表的表长不超过1为止,此时待排序数据序列就变成了一个有序表。
那么现在就得思考如何取基准值?大家都希望基准值是数据序列中值为中间的元素,可是现在序列还是一个无序表,又怎么找呢?因此基准值的三种较为常用的取法中,三数取中法是更为合适的。
基准值较常使用的取法共有三种:
最左侧的元素 最右侧的元素 三数取中法:最左侧的元素、最右侧的元素和中间的元素进行比较,选择值不大不小的元素作为基准值。 ———————————————— 版权声明:本文为CSDN博主「mu'mu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq_51419787…
function QuickSort(arr, begin, end) {
if (begin >= end) {
return;
}
let l = begin;
let r = end;
let key = arr[begin];
while (l < r) {
while (l < r && key < arr[r]) {
r--;
}
arr[l] = arr[r];
while (l < r && key >= arr[l]) {
l++;
}
arr[r] = arr[l];
}
arr[l] = key;
QuickSort(arr, begin, l - 1);
QuickSort(arr, l + 1, end);
console.log(arr);
}
QuickSort(arr, 0, arr.length - 1);
// arr: (9) [8, 6, 3, 4, 2, 7, 3, 9, 0]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
- 时间复杂度:O(nlogn)
- 空间复杂度:O(logn)
- 稳定性:不稳定
简单选择排序(选择类排序)
每一趟选出较大或较小的值,将它与排好序后位于位置的值进行交换。每一趟在n-i+1个元素中选取关键字最小(最大)的元素作为有序序列中第i个记录。(一共有n个元素)
function SelectSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
let minidx = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minidx]) {
minidx = j;
}
}
let tmp = arr[minidx];
arr[minidx] = arr[i];
arr[i] = tmp;
console.log(arr);
}
}
SelectSort(arr)
// arr: (9) [8, 6, 3, 4, 2, 7, 3, 9, 0]
// (9) [0, 6, 3, 4, 2, 7, 3, 9, 8]
// (9) [0, 2, 3, 4, 6, 7, 3, 9, 8]
// (9) [0, 2, 3, 4, 6, 7, 3, 9, 8]
// (9) [0, 2, 3, 3, 6, 7, 4, 9, 8]
// (9) [0, 2, 3, 3, 4, 7, 6, 9, 8]
// (9) [0, 2, 3, 3, 4, 6, 7, 9, 8]
// (9) [0, 2, 3, 3, 4, 6, 7, 9, 8]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
树形选择排序(选择类排序)
树形选择排序也称作锦标赛排序,树形选择排序时简单选择排序的改进算法。在简单选择排序中,首先从n个元素中选择关键字最小的元素需要n-1次比较,在n-1个元素中选择关键字最小的元素需要n-2次比较,......,每次都没有利用上次比较结果,所以比较操作的时间复杂度为O(n^2),想要降低比较的次数,则需要把比较过程中的大小关系保存下来。
算法思想: 先把待排序的n个元素俩俩进行比较,取出较小者,然后再n/2个较小者中采取同样的方法进行比较,选出较小者,如此反复,直至选出最小的元素为止。这个过程可以使用一颗满二叉树来表示,不满时用无穷大的数补充,选出的最小元素就是这棵树的根结点。将根结点输出后,叶子结点为最小值的变为无穷大的数,然后从该叶子结点和其兄弟结点的关键字比较,修改该叶子结点到根结点路径上各结点的值,则根结点的值为次小的元素。
时间复杂度:O(nlogn) 空间复杂度:O(n) 稳定性:稳定 ———————————————— 版权声明:本文为CSDN博主「mu'mu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq_51419787…
不是排序算法中考察的重点。
堆排序(选择类排序)
堆排序是树形选择排序的改进算法, 弥补树形选择排序占用太多空间的缺陷 。 采用堆排序,只需要一个记录大小的辅助空间。
堆排序过程详解:堆排序例题超详细解答_ 小顶堆例题 初建堆,重建堆,排序全过程哔哩哔哩bilibili
function HeapSort(arr) {
const n = arr.length;
const swap = ((i, j) => {
let tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
})
const heapify = ((i, len) => {
let l = 2 * i + 1;
let r = 2 * i + 2;
let largest = i;
if (l < len && arr[l] > arr[largest]) {
largest = l;
}
if (r < len && arr[r] > arr[largest]) {
largest = r;
}
if (largest !== i) {
swap(i, largest);
heapify(largest, len);
}
})
const buildMaxHeap = (() => {
for (let i = Math.floor(n / 2); i >= 0; i--) {
heapify(i, n);
}
});
buildMaxHeap();
for (let i = n - 1; i > 0; i--) {
swap(0, i);
heapify(0, i);
}
console.log(arr);
return arr;
}
HeapSort(arr);
注:升序建立大堆,降序建立小堆。
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
- 稳定性:不稳定
参考
- 堆排序例题超详细解答_ 小顶堆例题 初建堆,重建堆,排序全过程哔哩哔哩bilibili
- 五分钟看懂一个高难度的排序:堆排序 - 掘金 (juejin.cn)
- JavaScript 数据结构与算法之美 - 归并排序、快速排序、希尔排序、堆排序 - 掘金 (juejin.cn)
归并排序
function MergeSort(arr){
const n = arr.length;
if(n <= 1){
return arr;
}
const mid = Math.floor(n / 2);
const merge = ((leftarr, rightarr) => {
let tmp = [];
while(leftarr.length && rightarr.length){
if(leftarr[0]<rightarr[0]){
tmp.push(leftarr.shift());
}else{
tmp.push(rightarr.shift());
}
}
return tmp.concat(leftarr).concat(rightarr);
})
let result = merge(MergeSort(arr.slice(0, mid)), MergeSort(arr.slice(mid)));
console.log(result);
return result;
}
MergeSort(arr)
// arr: (9) [8, 6, 3, 4, 2, 7, 3, 9, 0]
// (2) [6, 8]
// (2) [3, 4]
// (4) [3, 4, 6, 8]
// (2) [2, 7]
// (2) [0, 9]
// (3) [0, 3, 9]
// (5) [0, 2, 3, 7, 9]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n)
- 稳定性:稳定
桶排序
计数排序(分配类排序)
- 算法思想:
对于给定的输入序列中的每一个元素,统计该序列中元素的出现的次数,将元素出现次数存放起来,再从序列的最小值元素开始输出(如果出现次数为0则不输出,出现次数为2则输出2次)。
- 稳定性:稳定
function CountSort(arr) {
const n = arr.length;
if (n <= 1) {
return arr;
}
let maxnum = -Infinity;
for (num of arr) {
maxnum = Math.max(maxnum, num);
}
const bucket = new Array(maxnum + 1).fill(0);
for (num of arr) {
bucket[num]++;
}
console.log(bucket);
let i = 0;
for (let j = 0; j <= maxnum; j++) {
while (bucket[j]-- > 0) {
arr[i++] = j;
}
}
console.log(arr);
return arr;
}
CountSort(arr)
// arr: (9) [8, 6, 3, 4, 2, 7, 3, 9, 0]
// (10) [1, 0, 1, 2, 1, 0, 1, 1, 1, 1]
// (9) [0, 2, 3, 3, 4, 6, 7, 8, 9]