快速排序算法
一、快速排序算法的基本特性
时间复杂度:O(n*lgn)
最坏:O(n^2)
空间复杂度:O(n*lgn)
不稳定。
快速排序是一种排序算法,对包含n个数的输入数组,平均时间为O(nlgn),最坏情况(已经排好序)是O(n^2),最好情况(完全无序)是O(nlgn)。
通常是用于排序的最佳选择。因为,基于比较的排序,最快也只能达到O(nlgn)。
二、快速排序算法的描述
算法导论,第7章
快速排序时基于分治模式处理的,
对一个典型子数组A[p...r]排序的分治过程为三个步骤:
1.分解:
A[p..r]被划分为俩个(可能空)的子数组A[p ..q-1]和A[q+1 ..r],使得
A[p ..q-1] <= A[q] <= A[q+1 ..r]
2.解决:通过递归调用快速排序,对子数组A[p ..q-1]和A[q+1 ..r]排序。
3.合并。
三、快速排序算法
快速排序算法的版本很多,这其实也是基于每个人自己的思维方式不同,接受和理解方式不同来区分。并没有明显的优劣之分,只要自己能理解,就可以啦!接下来我要说的就是算法导论上的那个版本。
QUICKSORT(A, p, r)
1 if p < r
2 then q ← PARTITION(A, p, r) //关键
3 QUICKSORT(A, p, q - 1)
4 QUICKSORT(A, q + 1, r)
快速排序算法的关键是PARTITION过程(数组划分),它对A[p..r]进行就地重排:
PARTITION(A, p, r)
1 x ← A[r] //以最后一个元素,A[r]为主元
2 i ← p - 1
3 for j ← p to r - 1 //j从p指向的是r-1,不是r。
4 do if A[j] ≤ x
5 then i ← i + 1
6 exchange A[i] <-> A[j]
7 exchange A[i + 1] <-> A[r]
8 return i + 1
举个例子说明一下这个数组划分的过程:2 8 7 1 3 5 6 4
注意:以最后一个元素4为主元,且i,j俩指针都从头出发,j 一前,i 一后。i 指元素的前一个位置,j 指着待排序数列中的第一个元素。
①
i p/j r
2 8 7 1 3 5 6 4(主元)
j指的元素是2,因为2<=4(主元),于是i++,i也指到元素2,所以A[i] <-> A[j],也就是2和2互换,原数组不变。
然后,j继续后移,直到指向1。
②
p/i j r
2 8 7 1 3 5 6 4(主元)
j指向元素1,因为1<=4(主元),于是i++,i指向了8,所以8与1交换。
数组变成了:
p i j r
2 1 7 8 3 5 6 4
③
p i j r
2 1 7 8 3 5 6 4
然后j继续后移,指向了元素3,因为3<=4(主元),于是i++,i这是指向了7,于是7与3交换。
数组变成了:
p i j r
2 1 3 8 7 5 6 4
④
j继续后移,一直到r-1的位置,都发现没有再比4小的数,所以,执行到了PARTITION过程的最后一步,
即上述PARTITION(A, p, r)代码部分的 第7行。
因此,i后移一个单位,指向了8
p i j r
2 1 3 8 7 5 6 4
执行A[i + 1] <-> A[r],即8与4交换,所以,数组最终变成了如下形式,
2 1 3 4 7 5 6 8
至此,快速排序第一趟完成。
4(主元)把整个数组分成了俩部分,2 1 3和7 5 6 8,再递归对这俩部分分别快速排序。
i p/j
2 1 3(主元)
2与2互换,不变,然后又是1与1互换,还是不变,最后,3与3互换,不变,
最终,3(主元)把2 1 3,分成了俩部分,2 1和3。
再对2 1,递归排序,最终结果成为了1 2 3。
7 5 6 8(主元),7、5、6、都比8小,所以第一趟,还是7 5 6 8,
不过,此刻8把7 5 6 8,分成了 7 5 6和8。[7 5 6->5 7 6->5 6 7]
再对7 5 6,递归排序,最终结果变成5 6 7 8。
ok,所有快速排序的过程,全部分析完成。
最后还得再说说快速排序中最关键的PARTITION过程(数组划分),由上述过程,可看出每一次PARTITION过程中,j扫描了整个数组一遍,只要一旦遇到比4(主元)小的元素,i 就++,然后,kj、ki交换。那么,为什么当j找到比4小的元素后,i 要++了? 你想了,如果i始终停在原地不动,与kj 每次交换的ki不就是同一个元素了么?如此,还谈什么排序?。所以,j在前面开路,i跟在j后,j只要遇到比4小的元素,i 就向前前进一步,然后把j找到的比4小的元素,赋给i,然后,j才再前进。
打个比喻就是,你可以这么认为,i所经过的每一步,都必须是比4小的元素,否则,i就不能继续前行。好比j 是先行者,为i 开路搭桥,把小的元素作为跳板放到i 跟前,为其铺路前行啊。
于此,j扫描到最后,也已经完全排查出了比4小的元素,只有最后一个主元4,则交给i处理,因为最后一步,exchange A[i + 1] <-> A[r]。这样,不但完全确保了只要是比4小的元素,都被交换到了数组的前面,且j之前未处理的比较大的元素则被交换到了后面,而且还是O(N)的时间复杂度,你不得不佩服此算法设计的巧妙。
四、快速排序算法的时间复杂度
ok,大概理解了快速排序,那么,也能很快的判断出:快速排序算法的平均时间复杂度,即为O(nlgn)。为什么了?因为你看,j,i扫描一遍数组,花费用时多少?对了,扫描一遍,当然是O(n)了,那样,扫描多少遍列,lgn到n遍,最快lgn,最慢n遍。且可证得,快速排序的平均时间复杂度即为O(n*lgn)。
PARTITION可能做的最平衡的划分中,得到的每个子问题都不能大于n/2。因为其中一个子问题的大小为|_n/2_|。另一个子问题的大小为|-n/2-|-1。
在这种情况下,快速排序的速度要快得多。为:
T(n)<=2T(n/2)+O(n).可以证得,T(n)=O(nlgn)。
五、Java代码实现快速排序算法
public class QuickSort { /** * 快速排序的主方法 * @param array :待排序数组 * @param begin :数组第一个元素的下标(其实就是0) * @param end :数组最后一个元素的下标(其实就是数组长度-1) * @return */ public static int[] quickSort(int[] array,int begin,int end ) { if(begin < end){ int k = partition(array, begin, end); quickSort(array,begin,k-1); quickSort(array,k+1,end); } return array; } public static int partition(int[] data,int head,int hi){ int key=data[hi]; //以最后一个元素,data[hi]为主元 int i=head-1; for(int j=head;j<hi;j++) ///注,j从p指向的是r-1,不是r。 { if(data[j]<=key){ i=i+1; swap(data,i,j); } } swap(data,i+1,hi); return i+1; } public static void swap(int[] data ,int i ,int j){ int temp = data[i]; data[i] = data[j]; data[j] = temp; } public static void main(String[] args) { int[] array = {2,8,7,1,3,5,6,4,9}; int len = array.length-1; array = quickSort(array, 0, len); for (int i = 0; i < array.length; i++) { System.out.print(array[i]); } } }
整理自:http://blog.csdn.net/v_JULY_v/article/details/6211155
http://blog.csdn.net/v_JULY_v/article/details/6116297