常见的数组排序算法有哪些?

29 阅读4分钟

常见的数组排序算法有哪些?

1. JS内置排序方法 Array.prototype.sort()

默认按字符串Unicode码排序

  1. 升序 arr.sort((a, b) => a - b);
  2. 降序 arr.sort((a, b) => b - a);

底层实现(V8引擎)

小数组(小于等于10):插入排序(Insertion Sort)

大数组:Timsort (稳定、自适应、混合归并+插入,时间复杂度O(nlogn)

特点: 原地排序(会修改原数组)稳定排序(ES209起规范要求)

2. 快速排序

平均时间复杂度O(n logn),空间复杂度O(logn),不稳定

  1. 先从一个数列中取出一个数作为“基准”。
  2. 分区过程:将比这个“基准”大的数全放在“基准”的右边,比这个“基准”小的数放在左边。
  3. 再对左右区间重复第二步,直到各区间只有一个数。
const arr = [23,54,34,8,35,98,21,45,76];
function quickSort(arr) {
  if (arr.length <= 1) return arr;
  const pivot = arr[Math.floor(arr.length / 2)]; //基准数
  const left = [];
  const right = [];
  const equal = [];
  for (let item of arr) {
    if (item < pivot) {
      left.push(item);
    } else if (item > pivot) {
      right.push(item);
    } else {
      equal.push(item);
    }
  }
  return [...quickSort(left), ...equal, ...quickSort(right)];//链接左数组、基准数数组、右数组
  console.log(quickSort([...arr1])); // [8, 21, 23, 34, 35, 45, 54, 76, 98]

3. 归并排序

平均时间复杂度O(nlogn),空间复杂度O(n),稳定

  1. 将数组递归地二分,直到每个子数组只有一个元素(或空)。
  2. 将两个已排序的子数组合并成一个有序数组。
  3. 整个过程像二叉树,从根(原数组)不断拆分到叶子(单个元素),再从叶子向上逐层合并回有序数组。
// 归并排序
const arr = [23,54,34,8,35,98,21,45,76];
function mergeSort(arr:number[]):number[] {
    //基线条件
    if(arr.length<=1)return arr;
    //分:找到中点,分割数组
    const mid = Math.floor(arr.length/2);
    const left = arr.slice(0, mid);
    const right = arr.slice(mid);
    //递归排序左右两部分,并合并
    return merge(mergeSort(left), mergeSort(right));
}

// 合并两个已排序数组
function merge(left: number[], right: number[]){
    const result = [];
    let i = 0, 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++;
        }
    }
    // 将剩余元素拼接(left或right必有一个已空)
    return result.concat(left.slice(i)).concat(right.slice(j));

}
console.log(mergeSort(arr1)); // [8, 21, 23, 34, 35, 45, 54, 76, 98]

4. 选择排序

平均时间复杂度O(n2),空间复杂度O(1),不稳定

  1. 首先在未排序序列中找到最小(最大)元素,存放在排序序列的起始位置;
  2. 再从剩余未排序元素中继续寻找最小(最大)元素,存放在已排序序列的末尾;
  3. 重复第二步,直到所有元素均排序完毕。
//选择排序
const arr = [23,54,34,8,35,98,21,45,76];
function selectionSort(arr: number[]):number[] {
    let minIndex: number, temp: number;
    for(var i =0; i<arr.length -1; i++){
        minIndex = i;
        for(var j=i+1; j< arr.length; j++){
            if(arr[j]< arr[minIndex]){ //寻找最小的数
                minIndex = j; // 将最小数的索引保存
            }
        }
        temp = arr[i];
        arr[i]= arr[minIndex];
        arr[minIndex]= temp;
    }
    return arr;
}
console.log(selectionSort([...arr1]));// [8, 21, 23, 34, 35, 45, 54, 76, 98]

5. 冒泡排序

平均时间复杂度O(n2),空间复杂度O(1),稳定

  1. 比较两个相邻的元素。如果第一个比第二个大,就交换它们两个位置。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
function bubbleSort(arr: number[]): number[] {
    for(var i=0;i<arr.length-1;i++){
        for(var j=0; j< arr.length-1-i; j++){
            if(arr[j]>arr[j+1]){ //相邻两个元素对比
                var temp =arr[j+1]; //元素交换
                arr[j+1]=arr[j];
                arr[j]=temp;
            }
        }
    }
    return arr;
}
console.log(bubbleSort([...arr1]));// [8, 21, 23, 34, 35, 45, 54, 76, 98]

6. 插入排序

平均时间复杂度O(n2),空间复杂度O(1),稳定

  1. 将第一个元素看作已排序序列,将第二个到最后一个元素当成未排序序列。
  2. 从头到尾以此扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
  3. 如果待插入的元素和有序序列中的某个元素相等,则将待插入元素插入到相等元素后面
//插入排序
function insertionSort(arr: number[]): number[] {
    let j:number, current:number;
    for(var i=1;i<arr.length;i++){
        j = i-1;
        current = arr[i]; //当前要插入的元素
        // 在已排序部分[0,i-1]中从后往前找插入的位置
        while(j>=0 && arr[j] > current) {
            arr[j+1]=arr[j]; // 元素后移
            j--;
        }
        arr[j+1] = current; // 插入到正确位置
    }
    return arr;
}
console.log(insertionSort([...arr1])); // [8, 21, 23, 34, 35, 45, 54, 76, 98]