漫谈经典排序算法:三、冒泡排序 && 快速排序
2011-10-31 15:31
513 查看
1、序言
这是《漫谈经典排序算法系列》第三篇,先解析了冒泡排序,然后引出快速排序,给出了快速排序的两种实现版本。各种排序算法的解析请参考如下:
《漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析》
《漫谈经典排序算法:二、各种插入排序解析及性能比较》
《漫谈经典排序算法:三、冒泡排序
&& 快速排序》
《漫谈经典排序算法:四、归并排序》
《漫谈经典排序算法:五、线性时间排序(计数、基数、桶排序)》
《漫谈经典排序算法:六、各种排序算法总结》
注:为了叙述方便,本文以及源代码中均不考虑A[0],默认下标从1开始。
2、冒泡排序
2.1 引出
前面的两篇博客里讲的插入排序是基于“逐个记录插入”,选择排序是基于“选择”,那么冒泡排序其实是基于“交换”。每次从第一个记录开始,一、二两个记录比较,大的往后放,二三两个记录比较...依次类推,这就是一趟冒泡排序。每一趟冒泡排序后,无序序列中值最大的记录冒到序列末尾,所以称之为冒泡排序。
2.2 代码
viewplain
//冒泡排序
void bubbleSort(int *a,int n)
{
int i,j;
for(i=1;i<n;i++)
for(j=1;j<n-i+1;j++){
if(a[j+1]<a[j]){
a[j]=a[j]+a[j+1];
a[j+1]=a[j]-a[j+1];
a[j]=a[j]-a[j+1];
}
}
}
2.3 效率分析
相对于简单选择排序,冒泡排序交换次数明显更多。它是通过不断地交换把最大的数冒出来。冒泡排序平均时间和最坏情况下(逆序)时间为o(n^2)。最佳情况下虽然不用交换,但比较的次数没有减少,时间复杂度仍为o(n^2)。此外冒泡排序是稳定的。
3、快速排序
3.1 引出
快速排序是冒泡排序的一种改进,冒泡排序排完一趟是最大值冒出来了,那么可不可以先选定一个值,然后扫描待排序序列,把小于该值的记录和大于该值的记录分成两个单独的序列,然后分别对这两个序列进行上述操作。这就是快速排序,我们把选定的那个值称为枢纽值,如果枢纽值为序列中的最大值,那么一趟快速排序就变成了一趟冒泡排序。
3.2 代码
两种版本,第一种是参考《数据结构》,在网上这种写法很流行。第二种是参考《算法导论》,实现起来较复杂。view
plain
//快速排序(两端交替着向中间扫描)
void quickSort1(int *a,int low,int high)
{
int pivotkey=a[low];//以a[low]为枢纽值
int i=low,j=high;
if(low>=high)
return;
//一趟快速排序
while(i<j){//双向扫描
while(i < j && a[j] >= pivotkey)
j--;
a[i]=a[j];
while(i < j && a[i] <= pivotkey)
i++;
a[j]=a[i];
}
a[i]=pivotkey;//放置枢纽值
//分别对左边、右边排序
quickSort1(a,low,i-1);
quickSort1(a,i+1,high);
}
//快速排序(以最后一个记录的值为枢纽值,单向扫描数组)
void quickSort2(int *a,int low,int high)
{
int pivotkey=a[high];//以a[high]为枢纽值
int i=low-1,temp,j;
if(low>=high)
return;
//一趟快速排序
for(j=low;j<high;j++){
if(a[j]<=pivotkey){
i++;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
i++;
//放置枢纽值
temp=a[i];
a[i]=pivotkey;
a[high]=temp;
//分别对左边、右边排序
quickSort2(a,low,i-1);
quickSort2(a,i+1,high);
}
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 所有源代码
viewplain
#include<stdio.h>
//冒泡排序
void bubbleSort(int *a,int n)
{
int i,j;
for(i=1;i<n;i++)
for(j=1;j<n-i+1;j++){
if(a[j+1]<a[j]){
a[j]=a[j]+a[j+1];
a[j+1]=a[j]-a[j+1];
a[j]=a[j]-a[j+1];
}
}
}
void main()
{
int i;
int a[7]={0,3,5,8,9,1,2};//不考虑a[0]
bubbleSort(a,6);
for(i=1;i<=6;i++)
printf("%-4d",a[i]);
printf("\n");
}
view
plain
#include<stdio.h>
//快速排序(两端交替着向中间扫描)
void quickSort1(int *a,int low,int high)
{
int pivotkey=a[low];//以a[low]为枢纽值
int i=low,j=high;
if(low>=high)
return;
//一趟快速排序
while(i<j){//双向扫描
while(i < j && a[j] >= pivotkey)
j--;
a[i]=a[j];
while(i < j && a[i] <= pivotkey)
i++;
a[j]=a[i];
}
a[i]=pivotkey;//放置枢纽值
//分别对左边、右边排序
quickSort1(a,low,i-1);
quickSort1(a,i+1,high);
}
//快速排序(以最后一个记录的值为枢纽值,单向扫描数组)
void quickSort2(int *a,int low,int high)
{
int pivotkey=a[high];//以a[high]为枢纽值
int i=low-1,temp,j;
if(low>=high)
return;
//一趟快速排序
for(j=low;j<high;j++){
if(a[j]<=pivotkey){
i++;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
i++;
//放置枢纽值
temp=a[i];
a[i]=pivotkey;
a[high]=temp;
//分别对左边、右边排序
quickSort2(a,low,i-1);
quickSort2(a,i+1,high);
}
void main()
{
int i;
int a[7]={0,3,5,8,9,1,2};//不考虑a[0]
quickSort2(a,1,6);
quickSort1(a,1,6);
for(i=1;i<=6;i++)
printf("%-4d",a[i]);
printf("\n");
}
原文出处:/article/7618469.html
相关文章推荐
- 漫谈经典排序算法:三、冒泡排序 && 快速排序
- C#实现所有经典排序算法(选择排序 冒泡排序 快速排序)
- C#实现所有经典排序算法(选择排序 冒泡排序 快速排序)
- C#实现所有经典排序算法(选择排序 冒泡排序 快速排序)
- 漫谈经典排序算法:五、线性时间排序(计数、基数、桶排序)
- 排序算法----交换排序(冒泡排序,快速排序)
- 简单的排序算法——插入排序,选择排序,交换排序(冒泡排序,快速排序)
- 改进排序算法:快速排序(对冒泡排序的改进)
- JAVA经典算法-冒泡排序和快速排序
- 必须知道的八大种排序算法【java实现】(一) 冒泡排序、快速排序
- 冒泡排序、快速排序等常用排序算法
- 常用排序算法笔记之冒泡排序、快速排序
- 易混的排序算法:冒泡排序、选择排序、快速排序
- 漫谈经典排序算法:六、各种排序算法总结
- 漫谈经典排序算法:六、各种排序算法总结
- 排序算法解析——冒泡排序、选择排序、快速排序
- 经典排序算法回顾:插入排序,冒泡排序
- 常见的五类排序算法图解和实现(交换类:冒泡排序,递归的快速排序)
- C语言常用的几种排序算法代码(选择排序,冒泡排序,插入排序,快速排序)
- 经典的排序算法--选择排序与冒泡排序