zoukankan      html  css  js  c++  java
  • 快速排序

     上篇中归并排序开启了排序篇章,这次继续学习另外一种非常经典的排序-----快速排序【之所有叫快速排序肯定它的效率慢不了】,另外上篇对于归并排序只有实现,木有分析它的时间复杂度,由于它的复杂度跟快速排序的是一模一样的,所以学习快速排序之后直接针对它来计算一下既可,下面正式开启学习之旅:

     先来上一张网络上的图有个直观的认识,接下来会来论证它,就跟学习归并排序一下:
                      

    解释一下上图的具体过程,对于一组无序的数组,需要选定一个数当轴心,而这里规定每次都是让最左边的数当轴心,于是乎下面开始:

    这里最左边是49,然后将比它小的数都放到左边【注意:左边的不保证顺序,只保证大小】,比它大的数都放到右边,经过一次划分之后的结果:

    这时发现整个还是乱序的,这时则对轴的左边和右边分别再做一次快速排序【实际上这就用到递归了】,如下:

    这时经过左边的数据排序之后,最后27这个轴的左边13和38都只剩下一位数了,也就结束排序了,实际上这也是递归的终止条件。

    经过右排序之后,轴76的左边还有两个数还可以继续快速排序,而右边97只剩一个了,则结束排序,下面则进行76的左边数进一步快速排序:

    而经过这次之后,整个数组也变成了一个有序的序列了,这就展示了快排的一个思想,有了它下面来看下具体代码实现:

    这里还是采用上篇学习归并排序的方法,先贴出具体实现,然后再用程序DEBUG方式展现出来,这里学习两种实现方法:

    方法一:

    先编写主框架:

    其核心就是来实现quick_sort这个方法啦,直接上代码:

    编译运行:

    下面来分析整个代码实现的过程:

    其中参数中:begin=0、end=7

    ①、

    还是先判断递归的边界条件,如果只剩一个元素则直接结束递归。

    ②、

    left=0, right=7:用来进行数组遍历的~

    pivot_value=array[0]=49,它主要是用来表示轴心的~

    ③、从左到右进行遍历循环,将比轴心小的数据放在它的左边,轴心大的数据放在它的右边,如下:

     

      其中循环条件left < right = 0 < 7条件为真,于是进入循环体:
      Loop1:

      a、array[0 + 1] = a[1] = 38 < 49,也就是比轴心的数据要小条件为真;
         array[0] = array[1]  --->   array[0] = 38,也就是将小的数据放到left的位置;

         left++=1;
      这时整个数组的数据为:【38【标红的则是更新的数字】、38【黑色的则原数组数据,随着循环继续会不断被新数据替换的】、65、97、76、13、27、49】

      其中循环条件left < right = 1 < 7条件为真,于是进入循环体:
      Loop2:

      a、array[1 + 1] = a[2] = 65 < 49,也就是比轴心的数据要小条件为假,则执行条件b;

      b、将left+1和right的位置进行交换,也就是array[2]跟array[7]两数交换;
         right--=6;

      这时整个数组的数据为:【38、38、49、97、76、13、27、65


      其中循环条件left < right = 1 < 6 条件为真,于是进入循环体:

      Loop3:

      a、array[1 + 1] = a[2] = 49 < 49,也就是比轴心的数据要小条件为假,则执行条件b;

      b、将left+1和right的位置进行交换,也就是array[2]跟array[6]两数交换;
        right--=5;

      这时整个数组的数据为:【38、38、27、97、76、13、49、65】

      其中循环条件left < right = 1 < 5 条件为真,于是进入循环体:

      Loop4:

      a、array[1 + 1] = a[2] = 27 < 49,也就是比轴心的数据要小条件为真;

         array[1] = array[2]  --->   array[1] = 27,也就是将小的数据放到left的位置;

         left++=2;
      这时整个数组的数据为:【38、27、27、97、76、13、49、65】

      其中循环条件left < right = 2 < 5 条件为真,于是进入循环体:

      Loop5:

      a、array[2 + 1] = a[3] = 97 < 49,也就是比轴心的数据要小条件为假,则执行条件b;

      b、将left+1和right的位置进行交换,也就是array[3]跟array[5]两数交换;
        right--=4;

      这时整个数组的数据为:【38、27、27、13、76、97、49、65】

      其中循环条件left < right = 2 < 4 条件为真,于是进入循环体:

      Loop6:

      a、array[2 + 1] = a[3] = 13 < 49,也就是比轴心的数据要小条件为真;

         array[2] = array[3]  --->   array[2] = 13,也就是将小的数据放到left的位置;

         left++=3;
      这时整个数组的数据为:【38、27、13、13、76、97、49、65】

      其中循环条件left < right = 3 < 4 条件为真,于是进入循环体:

      Loop7:

      a、array[3 + 1] = a[4] = 76 < 49,也就是比轴心的数据要小条件为假,则执行条件b;

      b、将left+1和right的位置进行交换,也就是array[4]跟array[4]两数交换;
        right--=3;

      这时整个数组的数据为:【38、27、13、13、76、97、49、65】

      其中循环条件left < right = 3 < 3 条件为假,退出循环,继续执行④

    ④、,也就是将轴心放到left的位置,也就是中间位置array[3] = 49,

      这时整个数组的数据为:【38、27、13、49、76、97、49、65】,到这一步就已经将轴心的左边和右边的数据全部整理好了。

    ⑤、,这个不解释,就是把排序后的数据打印一下,刚好跟咱们上面分析的结果是一样的:

      

    ⑥、,也就是继续对轴心左边的数据进一步递归,由于整个执行过程跟上面分析的一致,所以这里就不多分析,只分析一下何时终止递归,看这种结果之后:

    当上面排序完之后,接着会执行到此流程,所有quick_sort(array, 0, 1),于是乎进入到方法体中,执行如下:

      其中参数中:begin=0、end=1

      ⑥_①、

      还是先判断递归的边界条件,如果只剩一个元素则直接结束递归。

      ⑥_②、

      left=0, right=1:用来进行数组遍历的~

      pivot_value=array[0]=27,它主要是用来表示轴心的~

      ⑥_③、从左到右进行遍历循环,将比轴心小的数据放在它的左边,轴心大的数据放在它的右边,如下:

        

        其中循环条件left < right = 0 < 1条件为真,于是进入循环体:
        Loop1:

        ⑥_③a、array[0 + 1] = a[1] = 13 < 27,也就是比轴心的数据要小条件为真;
           array[0] = array[1]  --->   array[0] = 13,也就是将小的数据放到left的位置;

           left++=1;
        这时整个数组的数据为:【13、 13、 38、 49、 76、 97、 49、 65 】

        其中循环条件left < right = 1 < 1 条件为假,退出循环,继续执行④,
        也就是,array[1] = 27,
        这时整个数组的数据为:【13、 27、 38、 49、 76、 97、 49、 65 】刚好跟打印的结果一致:

        

    ⑦、,也就是继续对轴心右边的数据进一步递归,由于思想跟⑥一样的,所以这里就不分析了。

    这样经过上面的不断递归,这样就达到最终快速排序的目的了,总结下这种实现方法的核心步骤:

    1、判断递归结束条件,也就是只剩一个元素时则直接结束递归。

    2、声明三个变量:left指向begin、right指向end,另外指出最左边的元素为轴心。

    3、然后循环递归不断进行排序,其循环条件是left<right,另外循环体内有两个条件:

      a、如果发现left+1的元素比轴心数据小,则将left+1的数据放到left的位置,并将left++继续往前对比。

      b、如果发现left+1的元素比轴心数据大,则将left+1跟right的位置进行交换,并将right--继续往回对比。

    4、到这一步就基本上是按左边比轴心小的数据,右边比轴心大的数据了,但是还差一步就是这个数据中轴心不是真正的轴心,所以需要将轴心直接放到left的位置既可。

    方法二: 这种方法是网上都能搜索到的,直接上代码:

     

    编译运行:

    下面以同样的方式对其进行分析一下:

    其中参数begin=0,end=7:

    ①、,不多解释,递归条件判定,跟方法的一样。现在条件为假所以往下执行。

    ②、

      从变量名就可以知晓其含:pivot_index代表的是轴心的位置,具体为啥要它可以在后面分析中可以体会到,pivot_value则为轴心的值,pivot_index=0;pivot_value=array[0]=49;

    ③、接下来开始循环进行排序,如下:

    判断循环条件:k = 0+1 = 1;   k<=7为真,于是乎进入循环体内执行:

    loop1

    判断条件:array[1] < 49=true,执行条件体:

      pivot_index ++ = 1;

      swap(array[1], array[1]),同一个数交换当然不用啦,

    经过这次循环的数组内容为:【49、38、65、97、76、13、27、49】

    k++ = 2;

    判断循环条件:k = 2;   k<=7为真,于是乎进入循环体内执行:

    loop2

    判断条件:array[2] = 65 < 49=false,继续下次循环:

    经过这次循环的数组内容没变化为:【49、38、65、97、76、13、27、49】

    k++ = 3;

    判断循环条件:k = 3;   k<=7为真,于是乎进入循环体内执行:

    loop3

    判断条件:array[3] = 97 < 49=false,继续下次循环: 

    经过这次循环的数组内容没变化为:【49、38、65、97、76、13、27、49】

    k++ = 4;

    判断循环条件:k = 4;   k<=7为真,于是乎进入循环体内执行:

    loop4
    判断条件:array[4] = 76 < 49=false,继续下次循环:
    经过这次循环的数组内容没变化为:【49、38、65、97、76、13、27、49】 

    k++ = 5;

    判断循环条件:k = 5;   k<=7为真,于是乎进入循环体内执行:

    loop5

    判断条件:array[5] = 13 < 49=true,执行条件体:

      pivot_index ++ = 2;

      swap(array[2], array[5]),

    经过这次循环的数组内容为:【49、38、13、97、76、65、27、49】

    k++ = 6;

    判断循环条件:k = 6;   k<=7为真,于是乎进入循环体内执行:

    loop6

    判断条件:array[6] = 27 < 49=true,执行条件体:

      pivot_index ++ = 3;

      swap(array[3], array[5]),

    经过这次循环的数组内容为:【49、38、13、27、76、65、97、49】

    k++ = 7;

    判断循环条件:k = 7;   k<=7为真,于是乎进入循环体内执行:

    loop7

    判断条件:array[7] = 49 < 49=false,继续下次循环:

    经过这次循环的数组内容不变为:【49、38、13、27、76、65、97、49】

    k++ = 8;

    判断循环条件:k = 8;   k<=7为假,于是乎终止循环

    ④、

      swap(array[0], array[3]),也就是将轴心放到中间来,这时数组的内容为:【27、38、13、49、76、65、97、49】

    通过这一次的排序过程,总结下:1、循环从下标为1开始,依次跟轴心值比较;2、如果发现比轴心值小的话,则将pivot_index++ ,而它最终也就是我们将轴心存放的位置;3、并且将pivot_index位置的数据跟当前循环的数值进行一下交换;4当整个数据排序完之后,目前轴心还没放在合适的中间位置,则再将轴心的值跟pivot_index值交换一下位置既将轴心放到了一个中间合适的位置以达到了这一次的排序目的。

    ⑤、,不多说刚好跟分析的结果一致:

    ⑥、,思想跟方法一实现的类似,继续对轴心的左边数据进行进一步递归。

    ⑦、,对轴心的右边数据进行进一步递归。

    以上是两种对快速排序的实现方式,如开头所说,这次通过分析它的时间复杂度顺便也对上次的归并排序的时间复杂度进行一个补充,他们俩的时间复杂度是一样的,所以只分析一下快排就可以了,具体如下:

    所以T(n) = O(n) + 2 *T (n / 2) ,那它倒底是多少呢?其实网上有相应的公式,这里如维基百科对快速排序的说明中有如下说明:

    很显示其实它的复杂度就是O(n log n),而图片中标红的“master theorem”是算法时间复杂度的来源之处,到百度中搜一下它是啥?

    而点击进去稍微了解一下,其实为啥是O(n log n),是有一套规则算出来的:

    总之,快排跟归并排序的时间复杂度记着是:O(n log n)级别的就成~

    那它的空间复杂度是多少呢?

    而由于整个递归的深度是O(log n)【不包括for循环,只看递归】,所以产生的空间也是随着不断递归,每次递归都会产生上图标红的两个变量,那么它的空间复杂度也是S(n) = O(log n)的,随着n的增加空间也在不断增加。

  • 相关阅读:
    MySQL事件(定时任务)
    MySQL存储过程
    WebSocket小记
    Python计算给定日期位于当年第几周
    报错解决——Failed building wheel for kenlm
    计算机基础系列之压缩算法
    计算机基础系列之内存、磁盘、二进制
    计算机基础系列之CPU
    常用正则表达大全
    报错解决——TypeError: LoadLibrary() argument 1 must be str, not None
  • 原文地址:https://www.cnblogs.com/webor2006/p/7159078.html
Copyright © 2011-2022 走看看