JavaScript排序算法笔记 | 青训营笔记

142 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的的第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)
  • 稳定性:不稳定

参考

归并排序

 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]

基数排序

参考