快速排序的效率主要取决于你是怎么来实现这个算法的,如果中间有一步的实现没有做好,快速排序可能会变得非常慢。
快速排序的步骤:
- 选主元(pivot):在待排序数组中选取一个数作为主元。
- 子集划分:围绕所选取的主元划分数组,主元左边的数均小于主元,主元右边的数均大于主元。(这样划分后主元的位置已经确定)
- 递归:将主元左边的元素和主元右边的子集元素分别递归的执行上面两步,直到数组完全有序。
这里主要的步骤之一就是选主元,主元怎么选关乎整个算法的效率。经分析可知当每次所选的主元都是数组元素的中间值,即比它大的元素和比它小的元素个数近似相等时,每一次都可以将数组平分,算法效率最高。
由图可得选取数组的第一个数或最后一个数为主元都是不可取的,因为如果数组本来就有序,会造成每次主元所划分的数组有一边为空。
用生成随机数的方法取主元也不可取,因为rand()函数会耗费大量时间。
其实选取主元的方法有很多种,下面我们介绍一种最常见的方法:取头、中、尾中位数作主元(还有五位数取中位数和七位数取中位数的取法)。
取头中尾的中尾数即取数组的头部、中间、尾部这三个数中的中位数作为主元,例如a[0]=8,a[(n-1)/2]=12,a[n-1]=3;则选取8作为主元。
子集划分:设待划分数组的左界为L,右界为R,将主元选好后,把头、中、尾中最小的放在a[L],最大的放在a[R],中位数放在a[(R+L)/2];将主元和a[R-1]互换位置(即给主元一个临时位置,以便于接下来子集的划分),设置两个指针i和j,由于a[R]和a[L]上的元素已经确定是大于主元和小于主元的所以不用参与划分,令i=L+1,j=R-2,(因为R-1上放置的是主元),while(a[i]<pivot)i++;while(a[j]>pivot)j--;当i指向大于pivot的元素,j指向小于pivot的元素时,交换a[i],a[j],重复上述步骤,直到i>j时循环停止;将a[i]和a[R-1]互换即可。
注意:若a[i]或a[j]与pivot相等最好也要停下来互换位置,想象一下如果一个数组所有的元素都相等,如果a[i]与pivot相等时不停下来的话,会导致主元的位置放在两边,两边子列分组极不平衡,所以虽然停下来交换会增加交换次数,但是可以使子列近似平分。
由于快速排序是递归实现的,对于小规模的数据(例如N<100),快速排序可能还没有插入排序快,所以我们可以设置一个阈值,当递归的数据规模充分小,则停止递归,直接调用简单排序。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define size 10000
#define cutoff 100
//由于快速排序是递归实现的,对于小规模的数据(例如N<100),快速排序可能还没有插入
//排序快,所以我们可以设置一个阈值,当递归的数据规模充分小,则停止递归,直接调用简单排序。
void get_pivot(int a[],int start,int end);//将主元调整到end-1的位置
void quike_sort(int a[],int n);//快速排序
void sort(int a[],int start,int end);
void Insert_sort(int a[],int n);//插入排序
void print(int a[],int n)
{
int i;
for(i=0;i<n;i++)
printf("%d ",a[i]);
}
int main()
{
srand((unsigned)time(NULL));
int i;
int a[size];
for(i=0;i<size;i++)
a[i]=rand()%100000;
printf("排序前:");
print(a,size);
int time1=clock();
quike_sort(a,size);
int time2=clock();
printf("\n排序后:");
print(a,size);
printf("\n算法运行时间:%d\n",time2-time1);
return 0;
}
void get_pivot(int a[],int start,int end)
{
int middle=(start+end)/2;
int e;
if(a[middle]<a[start])
{
e=a[middle];
a[middle]=a[start];
a[start]=e;
}
if(a[end]<a[start])
{
e=a[start];
a[start]=a[end];
a[end]=e;
}
//此时a[start]一定是最小的
if(a[middle]>a[end])
{
e=a[middle];
a[middle]=a[end];
a[end]=e;
}
e=a[middle];
a[middle]=a[end-1];
a[end-1]=e;
}
void quike_sort(int a[],int n)
{
sort(a,0,n-1);
}
void sort(int a[],int start,int end)
{
if(end-start>cutoff)
{
get_pivot(a,start,end);
int i=start;
int j=end-1;
int e;
while(1)
{
while(a[++i]<a[end-1]);
while(a[--j]>a[end-1]);
if(i<=j)
{
e=a[i];
a[i]=a[j];
a[j]=e;
}
else break;
}
e=a[i];
a[i]=a[end-1];
a[end-1]=e;
sort(a,start,i-1);
sort(a,i+1,end);
}
else
Insert_sort(&a[start],end-start+1);
}
void Insert_sort(int a[],int n)
{
int i;
for(i=1;i<n;i++)
{
int temp=a[i];
for(;i>0&&a[i-1]>temp;i--)
a[i]=a[i-1];
a[i]=temp;
}
}