• JS 实现快排以及其优化方案


    代码

    • 在数据集之中,选择一个元素作为"基准"(pivot),这里取数组中间的值。
    • 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
    • 对"基准"左边和右边的两个子集,递归重复第一步和第二步,直到所有子集只剩下0个或者1个元素为止。
    • 最后返回左边子集,基准,右边子集的结合数组。
    function quicksort (arr) {
        // 如果子集只剩下一个元素,或者没有元素,就直接返回该数组
        if (arr.length <= 1) {
            return arr;
        }
        // 设置比较基准
        var pivotIndex = Math.floor(arr.length/2);
        var pivot = arr.splice(pivotIndex, 1)[0];
        // 定义左子集和右子集
        var left = [];
        var right = [];
        // 遍历,小于基准的元素移到基准的左边,大于基准的元素移到基准的右边
        for (var i = 0, j = arr.length; i < j; i++) {
            arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]);
        }
        // 最后返回左边子集,基准,右边子集的结合数组
        return quicksort(left).concat([pivot], quicksort(right));
    }
    
    var qsort = [85, 24, 63, 45, 17, 31, 96, 50];
    console.log('sort order = ' + quicksort(qsort));
    

    时间复杂度考虑

    我们都知道快排的时间复杂度是 O(nlogn),遇到最差的情况会退化成为 O(n^2),但什么情况最差?

    最差的情况:

    1. 数组已经是排好序的,并且你每次基准 pivot 选的是数组最左面或者是最右面
    2. 所有元素都相同

    为了避免第一种情况,一般采用的方法是三数取中,即取头、中、尾的中位数 O(1) 做基准,那么总体排序时间复杂度仍旧是 O(nlogn)。

    1 2 3 4 5 6 7
    
    每次取最后一个做基准值,当前取 7
    得左 1 2 3 4 5 6;  基准值 7;  右 null
    第二次基准值取 6
    得左 1 2 3 4 5;基准值 6; 右 null
    ......递归下去就会发现这是最坏的情况
    
    改变策略用三数取中
    
    第一次基准值取 1, 4, 7 的中位数就是 4
    得左 1 2 3; 基准值 4; 右 5 6 7
    .......就脱离了最坏的情况了
    
    

    那么第二种情况,若有元素等于基准,把它收集存放去一个临时的数组里,并且不参与接下来的递归分割。直到递归结束,才将递归结果与临时数组拼接起来。

    2 2 2 2 2
    
    因为中位数取来还是2,
    第一次左 2 2 2 2; 基准值2
    第二次右 2 2 2; 基准值还是2
    .......又陷入了最坏情况
    
    
    改变策略使用临时数组
    第一次左 null; 基准值2; 临时数组 2 2 2 2; 右 null;
    不需要再递归了,直接拼接结果。时间会快得多。
    

    优化后的源码

    // 三数取中
    function getMedian(left, middle, right) {
      var temp = [left];
      middle > left ? temp.push(middle) : temp.unshift(middle);
      if (right > temp[1]) {
        temp.push(right);
      } else if (right < temp[0]) {
        temp.unshift(right);
      } else {
        temp.splice(1, 0, right);
      }
      return temp[1];
    }
    
    function quicksort(arr) {
      // 如果子集只剩下一个元素,或者没有元素,就直接返回该数组
      if (arr.length <= 1) {
        return arr;
      }
      var middleIdx = Math.floor(arr.length / 2);
      // 三数取中
      var pivot = getMedian(arr[0], arr[middleIdx], arr[arr.length - 1]);
    
      // 定义左子集和右子集和与基准相同的集
      var left = [];
      var right = [];
      var same = [];
      // 遍历,小于基准的元素移到基准的左边,大于基准的元素移到基准的右边,相同的暂存起来不需要再排序
      for (var i = 0, j = arr.length; i < j; i++) {
        if (arr[i] < pivot) {
          left.push(arr[i]);
        } else if (arr[i] > pivot) {
          right.push(arr[i]);
        } else {
          same.push(arr[i]);
        }
      }
      // 最后返回左边子集,基准,右边子集的结合数组
      return quicksort(left).concat(same, quicksort(right));
    }
    // var qsort = [85, 24, 63, 45, 17, 31, 96, 50];
    var qsort = new Array(1000000).fill(2);
    console.time("sort");
    let result = quicksort(qsort);
    console.timeEnd("sort");
    // 只要 18 ms
    

    参考

    阮一峰的快速排序(Quicksort)的Javascript实现

    快排的最差情况以及如何避免

    三种快排及四种优化方式

    《算法导论》

  • 相关阅读:
    15款经典图表软件推荐 创建最漂亮的图表
    CSS+JS打造的自适应宽度的滑动门和选项卡
    兼容多浏览器的加入收藏代码
    指针与引用深层次的区别
    反编译winform资源文件
    程序创业必过三关
    自动ping博客服务程序
    C#批量加水印程序
    C#应用程序随机启动
    失败降临是命中注定
  • 原文地址:https://www.cnblogs.com/everlose/p/12815606.html
Copyright © 2020-2023  润新知