zoukankan      html  css  js  c++  java
  • 快速排序的JavaScript实现

    思想

    分治的思想,将原始数组分为较小的数组(但没有像归并排序一样将它们分隔开)。

    1. 主元选择: 从数组中任意选择一项作为主元,通常为数组的第一项,即arr[i];或数组的中间项, arr[Math.floor((i+j)/2)];
    2. 划分操作: 创建两个指针,左边一个指向数组的第一项,右边一个指向数组的最后一项。向右移动左指针,直到找到一个不小于比主元的项;向左移动右指针,直到找到一个不大于主元的项。交换它们。然后重复这个过程,直到左指针超过了右指针。这样使得比主元小的值都排在主元之前,而比主元大的值都排在主元之后。注意:在这个过程中,主元的位置也可能发生了改变;而主元本身在一次划分操作之后不会在正确的位置,其正确的位置应该在本次划分操作后最终得到的那个分割点上(即sliceIndex上),在下轮操作的右半路操作会立刻把主元换到正确的位置上
    3. 对子数组重复划分操作: 比主元小的项(即主元左边的部分)组成一个子数组,比主元大的项(即主元右边的部分)组成另一个子数组。对这两个小数组继续执行主元选择和划分操作。直到数组已完全排序。

    代码

    代码段1:

    function quickSort(arr) {
      return quick(arr, 0, arr.length-1);
    }
    function quick(arr, left, right) {
      if(arr.length>1) {
        const sliceIndex = partition(arr, left, right);
        if (left < sliceIndex-1) {
          quick(arr, left, sliceIndex-1);
        }
        if (sliceIndex < right) { //*1
          quick(arr, sliceIndex, right);//*2
        }
      }  
    }
    function partition(arr, left, right) {
      let i = left;
      let j = right;
      //const pivot = arr[Math.floor((i+j)/2)];
      const pivot = arr[i];
      while(i< j) { 
        while (arr[i] < pivot) { // *3
        //Q:*3,*4:为什么这里必须用<和>, 而不能用 <=或>= ?
        //A:*3和*4必须是<和>,都不能包含=的情况——实际验证结果也是如此
        //因为如果包含=,那么就永远不处理pivot及值等于pivot的情况
          i++;
        }
        while (arr[j] > pivot) { // *4
          j--;
        }
        if(i<j) { // *5 
        //思考:为什么这里必须是 <= 而不能用 < ?
        //A:它其实是为了下面一段的i++,j--。如果分开两段写,那么这里应该是i<j,而下面一段应该是i<=j
          const temp = arr[i];
          arr[i] = arr[j];
          arr[j] = temp;
        }
        if(i<=j) {//*6
          //这里条件一定要是i<=j。 
          //如果条件仅仅是i<j,那么当i==j的时候就永远也进入不了这个条件,return 的值就是i也是j。那么这一整段partion代码下来,i可能从来没有变过,又原封不动地return了i,即  const sliceIndex = partition(arr, left, right);的这个sliceIndex是和参数left相等的,那么在接下来的quick(arr, sliceIndex, right)就等于之前的quick(arr,left,right),无限循环了。。。
          //总之,这个条件的目的就是让i一定要改变一次
          i++;
          j--;
        }
      }
      return i;
    }
    

    思考

    1. 其实每次划分操作之后,主元并不在正确的位置上……那么为什么说每次划分操作都把比主元小的值排在了主元之前,而把比主元大的值都排在了主元之后?

    主元本身在一次划分操作之后确实不在正确的位置,其正确的位置应该在本次划分操作后最终得到的那个分割点上(即sliceIndex上),在下轮操作的右半路操作会立刻把主元换到正确的位置上。所以其实在本轮划分操作最后,可以把主元交换到正确的位置上,再进行下轮操作,然后下轮操作就可以不管sliceIndex那个位置上了。

    这种写法其实是把小于主元的放到左边,大于主元的放到右边,等于主元的可能在左可能在右,而并不是把主元放在了正确的位置

    代码改进成如下这样就好理解了。

    代码段2:

    function quickSort(arr) {
      return quick(arr, 0, arr.length-1);
    }
    function quick(arr, left, right) {
      if (arr.length ===1) {
        return;
      }
      const sliceIndex = partition(arr, left, right);
      if(left < sliceIndex-1) {
        quick(arr, left, sliceIndex-1);
      }
      //下次划分不用再考虑主元了
      if(sliceIndex + 1 < right) { //*1
        quick(arr, sliceIndex + 1, right);//*2
      }
    }
    
    function partition(arr, left, right) {
      let i = left;
      let j = right;
      const pivot = arr[i];
      while(i < j) {
        while(arr[i] < pivot) { //*3
          i++;
        }
        while(arr[j] > pivot) {
          j--;
        }
        if(i < j) {
          const temp = arr[i];
          arr[i] = arr[j];
          arr[j] = temp;
        }
        if(i <= j) { 
          i++;
          j--;
        }
      }
    
      //交换主元到划分位置上
      const tempPivotIndex = arr.indexOf(pivot); //*7
      arr[tempPivotIndex] = arr[i];//*8
      arr[i]=pivot;//*9
      return i;//其实也是等于j的
    }
    

    比起原代码,这种写法:把1,2做了修改,下次划分操作不再考虑本次的slickIndex(即主元已经排好了);然后新增7,8,*9,就是在本次的划分操作最后,把主元交换到了正确的位置上,这样写,与Es6写法(代码段3)的思路是一致的

    工作过程

    待画图

    性能分析

    • 时间复杂度: 最好、平均O(nlogn),最坏O(n^2)
    • 空间复杂度: O(logn), 不稳定
    • 特点:越有性能却差

    延伸:es6的实现

    代码段3:

      function quickSortEs6(arr) {
        if (!arr.length) { 
          //要处理的临界条件一定是arr为空的情况,因为可能filter过滤后就一项也不剩了
          //arr长度为1的条件可以不单独处理,因为长度为1那么[pivot, ...rest]=arr的rest就为[]了
          return [];
        }
    
        const [pivot, ...rest] = arr;
        return [
          ...quickSortEs6(rest.filter(item => item < pivot)),
          pivot,
          ...quickSortEs6(rest.filter(item => item >= pivot)) //一定要有=不然和pivot相等的其他值会被过滤掉
        ]
      }
    

    这个比起之前的排法更好理解。

    参考资料

    -《学习JavaScript数据结构和算法》10.1.5
    -《数据结构(C语言版)》9.3.2

  • 相关阅读:
    生成器 和 生成器 表达式
    函数的三大器
    02.python网络爬虫第二弹(http和https协议)
    python网络爬虫第三弹(<爬取get请求的页面数据>)
    线程
    网络编程
    分时操作系统 与 多道程序系统
    javascript原型深入解析2--Object和Function,先有鸡先有蛋
    javascript原型深入解析1-prototype 和原型链、js面向对象
    js模块化
  • 原文地址:https://www.cnblogs.com/Bonnie3449/p/9221039.html
Copyright © 2011-2022 走看看