排序算法之 快速排序 及其时间复杂度和空间复杂度

原文:http://blog.csdn.net/yuzhihui_no1/article/details/44198701

总结:

最好的情况是枢纽元选取得当,每次都能均匀的划分序列。 时间复杂度O(nlogn)

最坏情况是枢纽元为最大或者最小数字,那么所有数都划分到一个序列去了 时间复杂度为O(n^2)

 快速排序是排序算法中效率相对较高的,但使用的人却是比较少,大家一般信手拈来的排序算法就是冒泡排序。因为冒泡排序主观,容易理解,而快速排序使用到了递归,大家可能就有点不知所措了。


算法分析

        快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

        我看了下网上有些bolg写排序算法,有的是理解错误了;有的呢是太过于复杂;还有的呢就干脆是用临时数组,而不是就地排序。当然我的也并没有多好,只是提够一种思路;

        说说我的基本思路:每次都取数组的第一个元素作为比较标准(哨兵元素),凡是大于这个哨兵元素的都放在它的右边,凡是小于这个哨兵元素的都放在它的左边;

        大概的步骤:

        1、判断参数条件,其实这是递归的出口;

       2、以数组的第一个元素为哨兵元素,让其他元素和它比较大小;(记住这时候第一个元素位置是口的,因为里面的值被作为哨兵元素保存起来了)

        3、开始从数组尾部往前循环得到一个小于哨兵元素的  元素A ,把该  元素A  放到第一个元素位置(也就是哨兵元素位置上,因为哨兵元素位置是空的);(这时候要记住 元素A  的位置是空的了)

       4、开始从数组头部往后循环得到一个大于哨兵元素的   元素B ,把该  元素B  放在上一步中移出的  元素A  的位置上;

       5、依次循环上面3、4步,直到最后一个元素为止,那么最后一个元素就存放哨兵元素了。

       6、把小于哨兵元素的那一部分和大于哨兵元素的那一部分分别递归调用本函数,依次递归排序好所有元素;


实现代码

        代码部分:    

[cpp]  view plain  copy
  1. #include<stdio.h>  
  2.    
  3. // 打印数组  
  4.  void print_array(int *array, int length)  
  5.  {  
  6.      int index = 0;  
  7.      printf("array:\n");  
  8.      for(; index < length; index++){  
  9.          printf(" %d,", *(array+index));  
  10.      }     
  11.      printf("\n\n");  
  12.  }  
  13.    
  14.  void quickSort(int array[], int length)  
  15.  {  
  16.      int start = 0;  
  17.      int end = length-1;  
  18.      int value = array[start];// 得到哨兵元素  
  19.    
  20.      if (1 > length) return;// 递归出口  
  21.    
  22.      while(start < end){// 以哨兵元素为标准,分成大于它和小于它的两列元素  
  23.    
  24.          while(start < end){// 从数组尾部往前循环得到小于哨兵元素的一个元素  
  25.              if ( array[end--] < value ){  
  26.                  array[start++] = array[++end];  
  27.                  break;  
  28.              }     
  29.          }     
  30.    
  31.          while( start < end ){// 从数组头部往后循环得到大于哨兵元素的一个元素  
  32.              if( array[start++] > value){  
  33.                  array[end--] = array[--start];  
  34.                  break;  
  35.              }     
  36.          }     
  37.      }     
  38.    
  39.      array[start] = value;// 放置哨兵元素  
  40.      printf("\nstart:%d, end:%d\n", start, end);// 这个是测试下start和end是否一样  
  41.      quickSort(array, start);// 递归排序小于哨兵元素的那一列元素  
  42.      quickSort(array + start + 1, length - start - 1);// 递归排序大于哨兵元素的那一列  
  43.  }  
  44.    
  45.    
  46.  int main(void)  
  47.  {  
  48.      int array[12] = {1,11,12,4,2,6,9,0,3,7,8,2};  
  49.      print_array(array, 12);// 开始前打印下  
  50.      quickSort(array, 12);// 快速排序  
  51.      print_array(array, 12);// 排序后打印下  
  52.      return 0;  
  53.  }  

        

        运行结果:

        


时间复杂度

        快速排序涉及到递归调用,所以该算法的时间复杂度还需要从递归算法的复杂度开始说起;
        递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n)  ;对于递归算法的时间复杂度这里就不展开来说了;

最优情况下时间复杂度

         快速排序最优的情况就是每一次取到的元素都刚好平分整个数组(很显然我上面的不是);
        此时的时间复杂度公式则为:T[n] = 2T[n/2] + f(n);T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间;
        下面来推算下,在最优的情况下快速排序时间复杂度的计算(用迭代法):
                                          T[n] =  2T[n/2] + n                                                                     ----------------第一次递归

                 令:n = n/2        =  2 { 2 T[n/4] + (n/2) }  + n                                               ----------------第二次递归

                                            =  2^2 T[ n/ (2^2) ] + 2n

                令:n = n/(2^2)   =  2^2  {  2 T[n/ (2^3) ]  + n/(2^2)}  +  2n                         ----------------第三次递归  

                                            =  2^3 T[  n/ (2^3) ]  + 3n

                ......................................................................................                        

                令:n = n/(  2^(m-1) )    =  2^m T[1]  + mn                                                  ----------------第m次递归(m次后结束)

               当最后平分的不能再平分时,也就是说把公式一直往下跌倒,到最后得到T[1]时,说明这个公式已经迭代完了(T[1]是常量了)。

               得到:T[n/ (2^m) ]  =  T[1]    ===>>   n = 2^m   ====>> m = logn;

               T[n] = 2^m T[1] + mn ;其中m = logn;

               T[n] = 2^(logn) T[1] + nlogn  =  n T[1] + nlogn  =  n + nlogn  ;其中n为元素个数

               又因为当n >=  2时:nlogn  >=  n  (也就是logn > 1),所以取后面的 nlogn;

               综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )


最差情况下时间复杂度

        最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)

     这种情况时间复杂度就好计算了,就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n;

     综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )


平均时间复杂度

        快速排序的平均时间复杂度也是:O(nlogn)

空间复杂度

        其实这个空间复杂度不太好计算,因为有的人使用的是非就地排序,那样就不好计算了(因为有的人用到了辅助数组,所以这就要计算到你的元素个数了);我就分析下就地快速排序的空间复杂度吧;
        首先就地快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据;
     最优的情况下空间复杂度为:O(logn)  ;每一次都平分数组的情况
     最差的情况下空间复杂度为:O( n )      ;退化为冒泡排序的情况

        还有个问题就是怎么取哨兵元素才能不会让这个算法退化到冒泡排序,想想了还是算了吧,越深入研究就会有越多感兴趣的问题,而我又不是搞算法分析的,所以就先这样吧。

        转载请注明作者和原文出处,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/44198701

        若有不正确之处,望大家指正,共同学习!谢谢!!!



  • 32
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值