您的位置:首页 > 编程语言

编程珠玑——快速排序总结

2015-11-07 21:35 246 查看

快速排序

快速排序是二十世纪最伟大的10大算法之一。如何写好一个快速排序通常不是一件容易的事,同时面试中或者实际应用中也经常要用到快速排序。

编程珠玑第11章专门介绍了快速排序,涉及到了基本的算法思想实现和一些优化,这里进行简单的总结以便以后快速的回忆。

基本思想

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

我们直观上可以想到式子:T(n)=2T(n/2)+O(n),从而得到时间复杂度为O(nlgn),好于O(n2)的时间复杂度。

实现

至于具体怎么分割数组,《编程珠玑》首先提到了一种方法,一趟快速排序流程如下:

设置两个变量l、y,[l,r]表示要排序的数组范围,排序开始的时候:l=0,r=N-1;

以范围内的第一个数组元素作为关键比较数据,赋值给pivot,即pivot=A[l];

设index=l, 从i=l+1开始搜索,即由左开始向右搜索(i++),若找到一个小于key的值A[i],将A[i]和A[++index]互换;

i遍历到范围[l,r]的最右边r之后,再将pivot元素A[l]与A[index]互换(index此时指向最后一个小于pivot的元素的位置)

整个过程也是比较教科书式的,为了直观一些,举个例子int A[]={3,4,5,1,2,6};那么整个排序过程如下

开始,l=0,r=5,pivot = A[0]=3;第一次分割之后数组如下:



分割之后,分为两块左边,l=0,r=1; 右边l=3,r=5. 两边进行第二次分割之后数组如下:



最后再将l=4,r=5的数组快进行partition排序,即完成了整个数组的排序。

实现代码如下:

void quicksort(int a[],int l,int r)
{
if(l>=r) return;
int pivot = a[l];
int index = l;
for(int i=l+1;i<=r;i++)
{
if(a[i]<pivot)
swap(a[++index],a[i]);
}
swap(a[l],a[index]);
quicksort(a,l,index-1);
quicksort(a,index+1,r);
}


特殊情况

上述讲了一般情况下的快排思想,平均时间复杂度为O(nlgn)。但是一些特殊情况的输入下仍然会导致时间复杂度为O(n2)。

全是重复元素

比如数组 a={3,3,3,3,3,3}.此时可以看到,第一次我们将数组分成长度1和5的两块;之后将长度为5的数组块又分成长度1和长度4的数组块….没有达到分治的效果。

此时时间复杂度为T(n)=T(n−1)+O(n),显然就是O(n2)的复杂度了。针对这样的情况,我们可以稍微改变快排的思路,每一趟的快排前两步和上述的快排一样

和上述一样;

和上述一样;

设i=l+1,j=r;

从i开始向后搜索,即由左开始向右搜索(i++),找到第一个大于等于key的值A[i];从j开始向前搜索,即由右开始向左搜索(j–),找到第一个小于等于key的值A[i];若i>j,终止此步,否则将A[j]和A[i]互换,重复此步骤直到终止;

交换元素A[l]与元素A[j]。

这样所有元素都相同的情况也能基本实现每次partition二分,此时时间复杂都也变成了O(NlgN)。

实现代码如下:

void quicksort(int a[],int l,int r)
{
if(l>=r) return;

int pivot = a[l];
int i=l;
int j=r+1;

while(1)
{
do{i++;}while((i<=r)&&a[i]<pivot);
while(a[--j]>pivot);
if(i>j) break;
swap(a[i],a[j]);
}
swap(a[l],a[j]);
quicksort(a,l,j-1);
quicksort(a,j+1,r);
}


输入已经排好序

面对这种情况,上述已经优化的快排代码又会出现O(N2)的时间复杂度。因为它会首先围绕最小的元素划分,将数组大小分为1和N-1,然后是围绕第二小的元素划分,依此类推,还是需要O(N2)的时间。

解决这种情况的方法很简单就是随机选择划分元素,通过把A[l]与A[l,r]中的一个随机项交换来实现这一点。只需要加两行代码:

int num=rand()%(r-l+1) + l;
swap(a[l],a[num]);


这样期望的运行时间就为O(NlgN)啦。

优化

《编程珠玑》课后练习11.11提出了一种快排的小优化点,如下图:



之前我们都没有单独考虑过重复的元素,若是将数组中和比较元素t相同的所有元素都放到中间位置,那么之后进行下一趟快排的时候就会多过滤掉一些元素,起到了优化的效果。实现代码如下:

void quciksort(int a[],int l,int r)
{
if(l>=r) return;
int m=n=r+1;
int pivot = a[l];
for(int i=r;i>=l;i--)
{
while(a[i]<pivot) i--;
if(a[i]==pivot)
swap(a[--m],a[i]);
else{//a[i]>pivot
swap(a[--n],a[i]);
swap(a[--m],a[i]);
}
}
quciksort(a,l,m-1);
quciksort(a,n,r);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息