/*
快速排序(QuickSort)
2013年10月3日
by --- acton
算法思想
快速排序是C.R.A.Hoare於1962年提出的一種劃分交換排序。它採用了一種分治的策略,通常稱其爲分治法(Divide-and-ConquerMethod)。
(1) 分治法的基本思想
分治法的基本思想是:將原問題分解爲若干個規模更小但結構與原問題相似的子問題。遞歸地解這些子問題,然後將這些子問題的解組合爲原問題的解。
(2)快速排序的基本思想
設當前待排序的無序區爲R[low..high],利用分治法可將快速排序的基本思想描述爲:
①分解:
在R[low..high]中任選一個記錄作爲基準(Pivot),以此基準將當前無序區劃分爲左、右兩個較小的子區間R[low..pivotpos-1)和R[pivotpos+1..high],並使左邊子區間中所有記錄的關鍵字均小於等於基準記錄(不妨記爲pivot)的關鍵字pivot.key,右邊的子區間中所有記錄的關鍵字均大於等於pivot.key,而基準記錄pivot則位於正確的位置(pivotpos)上,它無須參加後續的排序。
注意:
劃分的關鍵是要求出基準記錄所在的位置pivotpos。劃分的結果可以簡單地表示爲(注意pivot=R[pivotpos]):
R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
其中low≤pivotpos≤high。
②求解:
通過遞歸調用快速排序對左、右子區間R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
③組合:
因爲當 "求解 "步驟中的兩個遞歸調用結束時,其左、右兩個子區間已有序。對快速排序而言, "組合 "步驟無須做什麼,可看作是空操作。
快速排序的複雜度是O(nlgn),其遞歸樹的表示形式應該爲T(n) = 2T(n/2) + O(n)
(比較理想的情況是剛好一分爲2,然後問題的規模變小),但是也不排除會出現 T(n) = T(n-1) + O(n)也就是一邊
有n-1個元素一邊的這種情況,此時的複雜度達到了O(n2)了,蛻化跟InsertSort一樣,所以關鍵是那個劃分的元素的選取,
如果沒有選取一個比較恰當的pivotkey的話,可能會造成O(n2)的複雜度,但是快速排序在大多的情況下表現的還是比較的優秀的,
例如當劃分爲一邊爲1/10,另一邊爲9/10, 即爲 T(n) = T((1/10)*n) + T((9/10)*n) + O(n)的情況時候,解遞歸樹採用漸進的方法
可以得到其複雜度近似爲O(nlgn),(見算法導論中文版 P151),所以快速排序還是比較優秀的一個排序的算法,而且它是在對元素進行原地的排序,
快速排序的隨機化版本:
如上已經說過,快速排序的key在於partition,而partition的關鍵在於對pivotkey的選取,如果沒有適當的選取,可能會到最壞的複雜度,
所以此處採用隨機化選取那個pivotkey
如下僞算法的實現:
Randomized_Partition(A,p,r){
i = RANDOM(p,r);
Exchange(A[i],A[r]);
return Partition(A,p,r);
}
其他的算法都基本沒有改變主要是pivotkey的選取,算法導論中P157中用指示器隨機 變量證明了該算法的平均複雜度爲O(nlgn)
*/
# include <stdio.h>
# define N 10
void Exchange(int * p, int * q){
int temp = *p;
*p = *q ;
*q = temp;
}
int Partition(int A[], int p , int r){
int pivot = A[r]; //pivot 應該是隨機選取的
int i = p -1 ;
for (int j = p ; j < r ; j ++){
if(A[j] <= pivot){
i ++ ;
Exchange(&A[i],&A[j]);
}
}
Exchange(&A[i+1],&A[r]);
return i + 1 ;
}
void QuickSort(int A[] ,int p ,int r){
if (p < r){
int q = Partition(A,p,r);
QuickSort(A,p,q-1);
QuickSort(A,q+1,r);
}
}
int main(void){
int a [N] = {9,2,10,11,2,8,7,4,3};
QuickSort(a,3,8);
for (int i = 3 ; i < 9 ; i ++ ){
printf("%5d ",a[i]);
}
return 0;
}