这里是对快速排序算法的一个简介,适合初学者。写在这里也是加深自己的印象,同时供学习交流。
1,我下面先对快速排序算法的过程做一个简单图形化介绍这样大家就会对后面的程序有个更清晰的认识。
排序问题如下:
问题描述:请用快速排序算法对数组{5,1,6,3,6,5,8,9,1}进行从小到大的快速排序操作。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
5 | 1 | 6 | 3 | 6 | 5 | 8 | 9 | 1 |
注:第一行为数组下标,第二行为对应数组元素
划分操作:快速排序首先对数组进行划分。划分的依据是选定一个pivot(称为枢轴量),它把原数组分为两个部分,一侧是小于pivot,一侧是大于pivot。通常的pivot选取为数组的第一个元素。这里我们选择下标为0的pivot值5。开始第一次划分操作。划分从左右两侧进行。首先从右侧开始遇到1小于pivot就将下标为8的数值1复制到pivot的位置。然后开始从左向右的划分。注意:这里之所以选择从右到左进行,是因为我们的目的是把原数组划分为左小于pivot,右大于pivot.如果先从左边开始则碰到一个比pivot大的我们再去执行赋值操作,这样的结果是比pivot的大的值放在了数组左边,与目的相悖。
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
5 | 1 | 6 | 3 | 6 | 5 | 8 | 9 | 1 |
我们用j代表数组从右到左比较的下标:显然这里是当j=8时发生复制,然后转向从左到右的比较。
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 6 | 3 | 6 | 5 | 8 | 9 | 1 |
注意:我们并没有因为1的赋值而弄丢5,而是一直保存在变量pivot中。
从左到右的进行,碰到值小于pivot的跳过,碰到值大于pivot的就执行赋值操作,应当注意到列表中在上次赋值后出现了重复的数值1,考虑下我们划分的目标。凡是大于pivot的值都放在右边,因此这次应将6复制到下标为8的位置:
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1(跳过) | 6 | 3 | 6 | 5 | 8 | 9 | 1 |
这里用i标记从左到右的进行,注意i的初值为下标1,因为0的位置是pivot。所有的划分都是在跟pivot比较。
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 6 | 3 | 6 | 5 | 8 | 9 | 6 |
然后重复上面步骤,开始下一次从右向左,直到i和j相等。
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 6 | 3 | 6 | 5 | 8 | 9 | 6 |
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 6 | 3 | 6 | 5 | 8(跳过) | 9(跳过) | 6 |
这里6是上次赋值后的重复项,将5填充过去。当然因为5等于pivot也可以不予操作。
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 5 | 3 | 6 | 5 | 8(跳过) | 9(跳过) | 6 |
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 5 | 3 | 6 | 5 | 8(跳过) | 9(跳过) | 6 |
再次从左往右,发现下标为4的6大于pivot,赋值给下标为5的5.
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 5 | 3 | 6 | 6 | 8(跳过) | 9(跳过) | 6 |
到这里,我们看到,左右重逢了,也就是i,j相等了。这时肯定中间的位置会有一个重复值,如上图下标为4的6,此时就可以将pivot复制过去。
0(pivot) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 5 | 3 | 5 | 6 | 8(跳过) | 9(跳过) | 6 |
现在看到凡是左侧都是小于等于5的,右侧都是大于5的。完成一次划分。这样,我们就把原数组划分成两派----两个独立的数组。
继续重复上面划分,开始下一轮。这样不停划分下去。直到分开的数组就剩下一个元素。
0(pivot) | 1 | 2 | 3 | 4 | 5(pivot) | 6 | 7 | 8 |
1 | 1 | 5 | 3 | 5 | 6 | 8(跳过) | 9(跳过) | 6 |
0 | 1(pivot) | 2 | 3 | 4 | 5 | 6(pivot) | 7 | 8 |
1 | 1 | 5 | 3 | 5 | 6 | 8 | 9 | 6 |
0 | 1 | 2(pivot) | 3 | 4 | 5 | 6 | 7(pivot) | 8 |
1 | 1 | 5 | 3 | 5 | 6 | 6 | 9 | 8 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
1 | 1 | 3 | 5 | 5 | 6 | 6 | 8 | 9 |
从上面可以看出快排总共就两个操作,划分,比较。分成两派后再分别划分,比较。
附上程序:
public static void quick_sort(int s[], int l, int r)
{
if(l<r){ //这里是判断,分成的子数组元素个数,如果为一个时则分组结束。
int start=l,end=r; //必须记下l,r的值,这里是数组的首位下标,因为最后继续划分还会用到,所以重新命名两个变量。
int value=s[start]; //这里的value就是我们一直讨论的pivot
while(start<end){ //每一轮可能都有多次比较复制,直到start=end.
while(start<end&&s[end]>=value) //从后往前比较重,只有在end>start时才有可能继续向前比较
end--;
if(start<end){ //这里我们在复制之后,下标自然右移或左移。最后在下标相等处复制pivot.如果没有这个条///件控制那么在一种极端情况,pivot右侧都大于它。这时start=end.也就是划分目标已经达到。此时如果再让start++,那么最后start将不等于//end,pivot的最终赋值就会出错。
s[start]=s[end]; //复制操作
start++; //start++ 为从左到右还没比较的数值做准备。start之前的数已经比较过了
}
while(start<end&&s[start]<value){
start++;
}
if(start<end){
s[end]=s[start];
end--;
}
s[start]=value;//一轮比较后,end=start pivot 复制过去。
}
quick_sort(s,l,start-1);//下一轮的划分pivot将不再参与
quick_sort(s,start+1,r);
}
}
快排的最好复杂度为O(N*logN),每次均匀划分,但是如果最坏情况下复杂度则为O(n2),即每次划分都是T(n)=T(n-1)+T(1)的情况。
本篇文章代码主要来自于:http://blog.csdn.net/morewindows/article/details/6684558;若然还有不解建议参看左边链接。