简介
快速排序是一种应用广泛且高效的排序算法
快速排序采用的是分治的思想,分而治之,例如:当需要对一个整型数组排序时,可以在区间内找一个数作为基准值,将比这个数小于或者等于的数都放在左边,比这个数大于或者等于的数都放在右边,然后再对左边和右边重复执行刚刚的操作,用递归来实现,直到区间内只有1个数或者没有数,这时,区间就排完序了,当所有的区间都排完序,那么这个数组就排完序了
那么这个数该取区间内的哪个呢?一般来说有4种,1.区间的第一个数 2.区间的最后一个数 3.区间中间的数 4.随机一个数
我们先来看看快速排序的动态图:其中P就是区间内找的一个基准数,P找的是区间中的最后一个数,以它为基准将区间分为两半,左边都是小于等于P的,右边都是大于等于P的,再对左边的区间重复操作...
我们先来看一下快排的模版
void quick_sort(int p[],int l,int r)
{
if(l>=r) //递归结束的条件,区间内已经是顺序了
return;
int x=p[l]; //x是基准值,与动图的不同,取的是区间中第一个元素
int i=l-1,j=r+1;
while(i<j)
{
do(i++);while(p[i]<x);
do(j--);while(p[j]>x);
if(i<j) swap(p[i],p[j]); // 得检查i和j是否已经互相穿过
}
quick_sort(p,l,j);
quick_sort(p,j+1,r);
}
其中l和r是区间的下标,那为什么i不等于l,j不等于r呢?可以这么想当交换完两个数之后,不管怎么样i和j至少要移动一步,那怎么样可以实现这个呢,可以使用do-while来保证程序正常运行,不过也可以这样写
while(p[++i]<x);
while(p[--j]>x);
如果不使用上面这两种方法,而使用了下面这种,就会有一种情况,如果i和j指向的两个数相同,那么就会出现死循环
i=l,j=r;
while(p[i--]<x);
while(p[j--]>x);
接下来也就是最难处理的问题了
边界问题
如果我们在循环过程中的交换条件中使用<=和>=,会出现以下情况:当选取的基准值为区间内最大的数,那么这个q[i]<=x在这个区间内是恒成立的,会造成越界,又或者这个基准值是区间内最小的值,那么q[j]>=x也是恒成立的,也是同样的结果
因此,在快速排序算法中,通常会采用严格的<和>操作符来避免这种情况的发生。
举个例子:如果用了<=和>=
3 4 5 基准值为3,i=0,j=2
3 4 5 i=?,j=2;
还有为什么当使用x=q[l]时,递归的区间是[l,j]和[j+1,r]?
x=q[l]的边界情况
当只有两个元素时例如1,2,这时按照代码执行下来,循环之前i=l,退出循环后quick_sort(l,i-1),quick_sort(i,r);等价于quick_sort(l,i-1),quick_sort(l,r);这就跟进入递归时一样了,就导致了死循环,所以此时只能用sort(l,j),sort(j+1,r);
x = q[r]的边界情况
道理类似,如果此时为2,1也会导致j=r,进行sort(l,j),sort(j+1,r);导致死循环(一直sort(l,r),此时只能用sort(l,i-1),sort(i,r);
x=q[l+r>>1]的边界情况
此时x取的是序列中间靠左的位置(如果序列个数为奇,则取正中间,如果为偶,则取中间靠左),此时如果元素个数为2,
则中间靠左就是第1个元素,这时就跟x=q[l]的边界情况一致了,所以这时只能用sort(l,j),sort(j+1,r);
x=q[l+r>>1]的边界情况
此时x取的是序列中间靠右的情况,同理,当元素只有两个,情况就会类似x=q[r],此时只能用sort(l,i-1),sort(i,r);
总结:
如果x=q[l] or x=q[l+r>>1]此时只能用sort(l,j),sort(j+1,r);
如果x=q[r] or x=q[l+r + 1>>1]此时只能用sort(l,i-1),sort(i,r);
有时候基准值x采用区间内随机一个值,能够更好地完成工作