十大经典排序算法(JavaScript版)

573 阅读5分钟

前言

本文整理了常用的十大经典排序算法,如果对答案有不一样见解的同学欢迎评论区补充讨论,当然有问题,也欢迎在评论区指出。

该篇文章基本引用于菜鸟教程,笔者只是做了一个简化版,以作记录

image.png

归并排序、快速排序和堆排序的适用场景

  • 归并排序是稳定的,但是空间复杂度O(n),空间复杂度较大
  • 快速排序是不稳定的,空间复杂度O(log n),在不要求稳定性的情况下,是最好的排序算法(前提是乱序、数据量大)
  • 堆排序是不稳定的,空间复杂度O(1),(前提是数据量大)
    • 堆排序数据访问的方式没有快速排序友好,是跳着访问,对CPU缓存不友好
    • 堆排序数据的交换次数较多,每次重新建堆,都需要交换大量数据

1.1 冒泡排序

1. 算法步骤

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

2. 动图演示

img

3. JavaScript 代码实现

function bubbleSort(arr) {
    const len = arr.length;
    for(let i=0;i<len-1;i++){
        for(let j=0;j<len-i;j++){
            if(arr[j-1]>arr[j]){// 相邻元素两两对比
                [arr[j-1],arr[j]] = [arr[j],arr[j-1]];// 元素交换
            }
        }
    }
    return arr;
}
console.log(bubbleSort([4,2,5,6,7,2,3,41,3,2]))

1.2 选择排序

1. 算法步骤

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

2. 动图演示

img

3. JavaScript 代码实现

function selectionSort(arr) {
    const len = arr.length;
    for(let i=0;i<len;i++){
        let minIndex = i;
        for(let j=i+1;j<len;j++){
            if(arr[minIndex]>arr[j]){// 寻找最小的数
                minIndex = j;// 将最小数的索引保存
            }
        }
        [arr[i],arr[minIndex]] = [arr[minIndex],arr[i]];//每轮只用交换一次
    }
    return arr;
}
console.log(selectionSort([23,4,1,2,5,2,5,6,7,4]))

1.3 插入排序

1. 算法步骤

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

2. 动图演示

img

3. JavaScript代码实现

function insertionSort(arr) {
    const len = arr.length;
    for(let i=1;i<len;i++){
        let index = i;
        const current = arr[i];
        while (current<arr[index-1]&&index>=0){
            arr[index]=arr[index-1];
            index--;
        }
        arr[index]=current;
    }
    return arr
}
console.log(insertionSort([23,4,1,2,5,2,5,6,7,4]))

1.4 希尔排序

1. 算法步骤

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

  2. 按增量序列个数 k,对序列进行 k 趟排序;

  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

2. JavaScript

function shellSort(arr) {
    var len = arr.length,
        temp,
        gap = 1;
    while(gap < len/3) {          //动态定义间隔序列
        gap =gap*3+1;
    }
    for (gap; gap > 0; gap = Math.floor(gap/3)) {
        for (var i = gap; i < len; i++) {
            temp = arr[i];
            for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
                arr[j+gap] = arr[j];
            }
            arr[j+gap] = temp;
        }
    }
    return arr;
}

1.5 归并排序

function mergeSort(arr) {  // 采用自上而下的递归方法
    const len = arr.length;
    if(len<2) return arr;
    const mid = Math.floor(len/2);
    const leftArr = arr.slice(0,mid);
    const rightArr = arr.slice(mid);
    return merge(mergeSort(leftArr),mergeSort(rightArr));
}
​
function merge(left, right) {
    const res = [];
    while (left.length&&right.length){
        if(left[0]<right[0]){
            res.push(left.shift());
        }else{
            res.push(right.shift());
        }
    }
    while (left.length){
        res.push(left.shift());
    }
    while (right.length){
        res.push(right.shift());
    }
    return res;
}
console.log(mergeSort([23,4,1,2,5,2,5,6,7,4]))

1.6 快速排序

1. 算法步骤

  1. 从数列中挑出一个元素,称为 "基准"(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

2. 动图演示

img

3. JavaScript

function quickSort(arr, left, right) {
    const len = arr.length;
    left = typeof left != 'number' ? 0 : left;
    right = typeof right != 'number' ? len - 1 : right;
    if(left<right){
        const partitionIndex = partition(arr,left,right);
        quickSort(arr,left,partitionIndex-1);
        quickSort(arr,partitionIndex+1,right);
    }
    return arr;
}
​
function partition(arr, left ,right) {     // 分区操作
    const pivot = left;
    let index = left+1;
    for(let i=index;i<=right;i++){
        if(arr[pivot]>arr[i]){
            [arr[index],arr[i]]=[arr[i],arr[index]];
            index++;
        }
    }
    [arr[pivot],arr[index-1]] = [arr[index-1],arr[pivot]];
    return index-1
}
console.log(quickSort([23,4,1,2,5,2,5,6,7,4]))

1.7 堆排序

1. 算法步骤

  1. 创建一个堆 H[0……n-1];
  2. 把堆首(最大值)和堆尾互换;
  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
  4. 重复步骤 2,直到堆的尺寸为 1。

2. 动图演示

img

3. JavaScript

var len;    // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量
function buildMaxHeap(arr) {   // 建立大顶堆
    len = arr.length;
    for (var i = Math.floor(len/2); i >= 0; i--) {
        heapify(arr, i);
    }
}
function heapify(arr, i) {     // 堆调整
    var left = 2 * i + 1,
        right = 2 * i + 2,
        largest = i;
    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }
    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }
    if (largest != i) {
        swap(arr, i, largest);
        heapify(arr, largest);
    }
}
function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
function heapSort(arr) {
    buildMaxHeap(arr);
    for (var i = arr.length-1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        heapify(arr, 0);
    }
    return arr;
}

1.8 计数排序

1. 算法步骤

  1. 找出待排序的数组中最大和最小的元素 (0)
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

2. 动图演示

img

3. JavaScript

function countingSort(arr, maxValue) {
    const res = [];
    const container = new Array(maxValue+1).fill(0);
    //将所有数据存放在容器里
    for(let i=0;i<arr.length;i++){
        container[arr[i]]++;
    }
    for(let j=0;j<=maxValue;j++){
        while (container[j]>0){
            res.push(j);
            container[j]--;
        }
    }
    return res;
}
console.log(countingSort([23,4,1,2,5,2,5,6,7,4],23))

1.9 桶排序

1. 示意图

元素分布在桶中:

img

然后,元素在每个桶中排序:

img

2. 算法步骤

  1. 找到数组中的最大值、最小值
  2. 设置桶的数量,然后由最大值、最小值和桶的数量,计算出每个桶装的数的大小范围
  3. 遍历数组,将数据分别放入每个桶中
  4. 对每个桶进行排序

1.10 基数排序

原理是将整数按位数切割成不同的数字,然后按每个位数分别比较

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

2. 基数排序动图演示

img

总结

觉得写得好的,对你有帮助的,可以分享给身边人,知识越分享越多,千万不要吝啬呀

后续继续分享我的经验总结,请关注我,我们一起学前端