漫谈经典排序算法:三、冒泡排序 && 快速排序

1、序言

这是《漫谈经典排序算法系列》第三篇,先解析了冒泡排序,然后引出快速排序,给出了快速排序的两种实现版本

各种排序算法的解析请参考如下:

《漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析》

《漫谈经典排序算法:二、各种插入排序解析及性能比较》

《漫谈经典排序算法:三、冒泡排序 && 快速排序》

《漫谈经典排序算法:四、归并排序》

《漫谈经典排序算法:五、线性时间排序(计数、基数、桶排序)》

《漫谈经典排序算法:六、各种排序算法总结》

 

注:为了叙述方便,本文以及源代码中均不考虑A[0],默认下标从1开始。

2、冒泡排序

          2.1 引出

           前面的两篇博客里讲的插入排序是基于“逐个记录插入”,选择排序是基于“选择”,那么冒泡排序其实是基于“交换”。每次从第一个记录开始,一、二两个记录比较,大的往后放,二三两个记录比较...依次类推,这就是一趟冒泡排序。每一趟冒泡排序后,无序序列中值最大的记录冒到序列末尾,所以称之为冒泡排序。

          2.2 代码

[cpp]  view plain copy
  1. //冒泡排序  
  2. void bubbleSort(int *a,int n)  
  3. {  
  4.     int i,j;  
  5.     for(i=1;i<n;i++)  
  6.         for(j=1;j<n-i+1;j++){  
  7.             if(a[j+1]<a[j]){  
  8.                 a[j]=a[j]+a[j+1];  
  9.                 a[j+1]=a[j]-a[j+1];  
  10.                 a[j]=a[j]-a[j+1];  
  11.             }  
  12.         }  
  13. }  

          2.3 效率分析

相对于简单选择排序,冒泡排序交换次数明显更多。它是通过不断地交换把最大的数冒出来。冒泡排序平均时间和最坏情况下(逆序)时间为o(n^2)。最佳情况下虽然不用交换,但比较的次数没有减少,时间复杂度仍为o(n^2)。此外冒泡排序是稳定的。

3、快速排序

          3.1 引出

           快速排序是冒泡排序的一种改进,冒泡排序排完一趟是最大值冒出来了,那么可不可以先选定一个值,然后扫描待排序序列,把小于该值的记录和大于该值的记录分成两个单独的序列,然后分别对这两个序列进行上述操作。这就是快速排序,我们把选定的那个值称为枢纽值,如果枢纽值为序列中的最大值,那么一趟快速排序就变成了一趟冒泡排序。

          3.2 代码

           两种版本,第一种是参考《数据结构》,在网上这种写法很流行。第二种是参考《算法导论》,实现起来较复杂。

[cpp]  view plain copy
  1. //快速排序(两端交替着向中间扫描)  
  2. void quickSort1(int *a,int low,int high)  
  3. {  
  4.     int pivotkey=a[low];//以a[low]为枢纽值  
  5.     int i=low,j=high;  
  6.     if(low>=high)  
  7.         return;  
  8.     //一趟快速排序  
  9.     while(i<j){//双向扫描  
  10.         while(i < j && a[j] >= pivotkey)  
  11.             j--;  
  12.         a[i]=a[j];  
  13.         while(i < j && a[i] <= pivotkey)  
  14.             i++;  
  15.         a[j]=a[i];  
  16.     }  
  17.     a[i]=pivotkey;//放置枢纽值  
  18.     //分别对左边、右边排序  
  19.     quickSort1(a,low,i-1);   
  20.     quickSort1(a,i+1,high);   
  21. }  
  22.   
  23. //快速排序(以最后一个记录的值为枢纽值,单向扫描数组)  
  24. void quickSort2(int *a,int low,int high)  
  25. {  
  26.     int pivotkey=a[high];//以a[high]为枢纽值  
  27.     int i=low-1,temp,j;  
  28.     if(low>=high)  
  29.         return;  
  30.     //一趟快速排序  
  31.     for(j=low;j<high;j++){  
  32.         if(a[j]<=pivotkey){  
  33.             i++;  
  34.             temp=a[i];  
  35.             a[i]=a[j];  
  36.             a[j]=temp;  
  37.         }  
  38.     }  
  39.     i++;  
  40.     //放置枢纽值  
  41.     temp=a[i];  
  42.     a[i]=pivotkey;  
  43.     a[high]=temp;  
  44.     //分别对左边、右边排序  
  45.     quickSort2(a,low,i-1);   
  46.     quickSort2(a,i+1,high);   
  47. }  

          3.3 效率分析

        快速排序时间与划分是否对称有关。快速排序的平均时间复杂度为o(n*logn),至于为什么是o(n*logn),请参考《算法导论》第7章,书中用递归树的方法阐述了快速排序平均时间。且常数因子很小,所以就平均时间而言,快速排序是很好的内部排序方法。最佳情况下(每次划分都对称)时间复杂度o(n*logn)。最坏情况下(每次划分都不对称,如输入的序列有序或者逆序时)时间复杂度为o(n^2),所以在待排序序列有序或逆序时不宜选用快速排序。此外,快速排序是不稳定的。

        最佳情况下,每次划分都是对称的,由于枢纽值不再考虑,所以得到的两个子问题的大小不可能大于n/2,同时一趟快速排序时间为o(n),所以运行时间递归表达式:

T(n)<=2T(n/2)+o(n)。这个递归式的解法请参考下一篇博客中归并排序效率分析。其解为T(n)=o(n*logn)。

        最坏情况下,每次划分都很不对称,T(n)=T(n-1)+o(n),可以用递归树来解,第i层的代价为n-i+1.总共有n层。把每一层代价加起来有n-1个n相加。所以这个递归式的解为T(n)=o(n^2),此时就是冒泡排序。

4、附录

         4.1 参考书籍

          《数据结构》严蔚敏版                    《算法导论》

           4.2 所有源代码
[cpp]  view plain copy
  1. #include<stdio.h>  
  2.   
  3. //冒泡排序  
  4. void bubbleSort(int *a,int n)  
  5. {  
  6.     int i,j;  
  7.     for(i=1;i<n;i++)  
  8.         for(j=1;j<n-i+1;j++){  
  9.             if(a[j+1]<a[j]){  
  10.                 a[j]=a[j]+a[j+1];  
  11.                 a[j+1]=a[j]-a[j+1];  
  12.                 a[j]=a[j]-a[j+1];  
  13.             }  
  14.         }  
  15. }  
  16.   
  17. void main()  
  18. {  
  19.     int i;  
  20.     int a[7]={0,3,5,8,9,1,2};//不考虑a[0]  
  21.     bubbleSort(a,6);  
  22.     for(i=1;i<=6;i++)  
  23.         printf("%-4d",a[i]);  
  24.     printf("\n");  
  25. }  
[cpp]  view plain copy
  1. #include<stdio.h>  
  2.   
  3. //快速排序(两端交替着向中间扫描)  
  4. void quickSort1(int *a,int low,int high)  
  5. {  
  6.     int pivotkey=a[low];//以a[low]为枢纽值  
  7.     int i=low,j=high;  
  8.     if(low>=high)  
  9.         return;  
  10.     //一趟快速排序  
  11.     while(i<j){//双向扫描  
  12.         while(i < j && a[j] >= pivotkey)  
  13.             j--;  
  14.         a[i]=a[j];  
  15.         while(i < j && a[i] <= pivotkey)  
  16.             i++;  
  17.         a[j]=a[i];  
  18.     }  
  19.     a[i]=pivotkey;//放置枢纽值  
  20.     //分别对左边、右边排序  
  21.     quickSort1(a,low,i-1);   
  22.     quickSort1(a,i+1,high);   
  23. }  
  24.   
  25. //快速排序(以最后一个记录的值为枢纽值,单向扫描数组)  
  26. void quickSort2(int *a,int low,int high)  
  27. {  
  28.     int pivotkey=a[high];//以a[high]为枢纽值  
  29.     int i=low-1,temp,j;  
  30.     if(low>=high)  
  31.         return;  
  32.     //一趟快速排序  
  33.     for(j=low;j<high;j++){  
  34.         if(a[j]<=pivotkey){  
  35.             i++;  
  36.             temp=a[i];  
  37.             a[i]=a[j];  
  38.             a[j]=temp;  
  39.         }  
  40.     }  
  41.     i++;  
  42.     //放置枢纽值  
  43.     temp=a[i];  
  44.     a[i]=pivotkey;  
  45.     a[high]=temp;  
  46.     //分别对左边、右边排序  
  47.     quickSort2(a,low,i-1);   
  48.     quickSort2(a,i+1,high);   
  49. }  
  50.   
  51. void main()  
  52. {  
  53.     int i;  
  54.     int a[7]={0,3,5,8,9,1,2};//不考虑a[0]  
  55.     quickSort2(a,1,6);  
  56.     quickSort1(a,1,6);  
  57.     for(i=1;i<=6;i++)  
  58.         printf("%-4d",a[i]);  
  59.     printf("\n");  
  60. }  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值