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

  • 相关阅读:
    JavaScript 为字符串添加样式 【每日一段代码80】
    JavaScript replace()方法 【每日一段代码83】
    JavaScript for in 遍历数组 【每日一段代码89】
    JavaScript 创建用于对象的模板【每日一段代码78】
    html5 css3 新元素简单页面布局
    JavaScript Array() 数组 【每日一段代码88】
    JavaScript toUTCString() 方法 【每日一段代码86】
    位运算
    POJ 3259 Wormholes
    POJ 3169 Layout
  • 原文地址:https://www.cnblogs.com/Bonnie3449/p/9221039.html
Copyright © 2011-2022 走看看