《算法導論》學習記錄目錄html
快速排序,對於n個數的輸入數組,最壞狀況運行時間:Θ(n^2);指望運行時間:Θ(nlgn);就地排序(Sort in place)。算法
數組A[p..r]會被分爲兩個子數組A[p..q-1]和A[q+1..r],其中A[p..q-1]的元素都不大於A[q],A[q+1..r]都不小於A[q]。數組
如何劃分子數組對運行時間的有很大影響,最壞的狀況爲n個數的數組劃分爲一個n-1的數組和一個0元素的數組;最佳狀況爲對半分(1:1);對於平均狀況的劃分,其運行時間與最佳狀況很接近。dom
先看代碼實現:ide
快速排序的關鍵是數組劃分,對子數組進行就地重排。函數
1 /* 2 * 調用劃分函數,使得子數組順序重排 3 */ 4 void quick_sort(int A[], int p, int r){ 5 if(p < r){ 6 int q = partition(A, p, r); 7 quick_sort(A, p, q-1); 8 quick_sort(A, q+1, r); 9 } 10 } 11 12 /* 13 * 對於比x值小的元素經過交換放置到小於x值的區域。 14 * 最後將大於x值的區域的第一個元素與x值,即原A[r],交換 15 * 該下標即爲q,造成兩個符合要求的數組(A[p..q-1]的元素都不大於A[q],A[q+1..r]都不小於A[q]) 16 */ 17 int partition(int A[], int p, int r){ 18 int x = A[r]; 19 int i = p - 1; 20 int j; 21 for(j = p; j <= r-1; j++){ 22 if(A[j] <= x){ 23 i++; 24 int temp = A[i]; 25 A[i] = A[j]; 26 A[j] = temp; 27 } 28 } 29 int temp = A[i+1]; 30 A[i+1] = A[r]; 31 A[r] = temp; 32 return i+1; 33 }
其中partition的運行時間爲Θ(n),n = r-p+1,循環的次數爲r-1-p;性能
過程圖以下:學習
練習7.1-2ui
當數組A[p..r]中的元素均相同時,partition返回的q值是多少?修改partition,使得數組A[p..r]中的元素均相同時, q = floor((p+r)/2)spa
當數組A[p..r]中的元素均相同時,partition返回的q值是r;修改很簡單,直接判斷返回的q值是否與r相同,相同就返回 floor((p+r)/2)。
經網友提醒這種方法判斷元素是否所有相同存在錯誤。。。。。往後再補上。。。囧。。。。
快速排序的性能
快速排序的運行時間與劃分數組是否對稱有關。若是劃分是對稱,從漸近意義上講,與合併排序同樣快,不然就與插入排序同樣慢。
最壞狀況爲劃分一個n-1個元素的子數組和一個0個元素的子數組。(最大程度不對稱)。劃分運行時間爲Θ(n)。若是對一個0個元素的數組進行遞歸調用,運行時間爲Θ(1).
遞歸式爲:T(n) = T(n-1) + T(0) + Θ(n) = T(n-1) + Θ(n)。經過代換法可證實T(n) = Θ(n^2) (練習7.2-1)
證實:先假設C1(n-1)^2 <= T(n-1) <= C2(n-1)^2成立
則C1(n-1)^2 + Θ(n) <= T(n-1) + Θ(n) <= C2(n-1)^2 + Θ(n)
C1n^2 -C1(2n - 1) + Θ(n) <= T(n-1) + Θ(n) <= C2n^2 -C2(2n - 1) + Θ(n)
咱們能夠選擇合適的常數C1、C2使得C1(2n - 1)、C2(2n - 1)支配Θ(n)。所以能夠得出T(n) = Θ(n^2)。
注意,當輸入數組徹底排好序(升降序同樣)和全部元素都是相同的值時,運行時間爲Θ(n^2),由於每次劃分都是最大程度不對稱。畫畫圖,過一遍就會知道。
最佳狀況爲劃分的兩個子數組大小爲floor(n/2)和ceiling(n/2)-1。
遞歸式爲:T(n) <= 2T(n/2) + Θ(n)。該遞歸式的解爲:T(n) = O(nlgn)
直接經過主定理的狀況2得出。
平均狀況運行時間更接近最佳狀況而不是最壞狀況。任一種按常數進行劃分都會產生深度爲Θ(lgn)的遞歸書,每一層的總代價爲O(n),所以運行時間爲O(nlgn)。
從下圖看能夠更加明顯
最壞狀況的劃分以後最佳狀況劃分的總代價與直接最佳狀況的總代價都爲Θ(n)。
練習7.2-5
假設快速排序的每一層,所作劃分比例都是1-a:a,其中0<a<=1/2是個常數。證實:在對應的遞歸樹中,葉結點的最小深度大約是-lgn/lga,最大深度大約是-lgn/lg(1-a)。
最小深度爲每次劃分後都是選擇最小的一部分繼續往下走,每次乘以a。一次迭代減小的元素數從n到an,迭代m次直到剩下的元素爲1。
則(a^m)*n = 1, a^m = 1/n,取對數得mlga = -lgn,m = -lgn/lga。
同理可得((1-a)^M)*n = 1,M = -lgn/lg(1-a)。
快速排序隨機化版本
便於對於全部輸入,均能得到較好的平均狀況性能。
1 int randomized_partition(int A[], int p, int r){ 2 int i = p + rand() % (r - p + 1); 3 int temp = A[r]; 4 A[r] = A[i]; 5 A[i] = temp; 6 return partition(A, p, r); 7 } 8 9 int partition(int A[], int p, int r){ 10 int x = A[r]; 11 int i = p - 1; 12 int j; 13 for(j = p; j <= r-1; j++){ 14 if(A[j] <= x){ 15 i++; 16 int temp = A[i]; 17 A[i] = A[j]; 18 A[j] = temp; 19 } 20 } 21 int temp = A[i+1]; 22 A[i+1] = A[r]; 23 A[r] = temp; 24 return i+1; 25 } 26 27 void randomized_quick_sort(int A[], int p, int r){ 28 if(p < r){ 29 int q = randomized_partition(A, p, r); 30 randomized_quick_sort(A, p, q-1); 31 randomized_quick_sort(A, q+1, r); 32 } 33 }
練習7.3-2
在randomized-quicksort的運行過程當中,最壞狀況下對隨機數產生器random調用了多少次?最佳狀況調用了多少次?
都爲Θ(n)。
快速排序的分析
算導關於快排分析得很詳細,數學太差了,看了不少遍才明白一點點,往後必定要繼續努力,爭取用本身的語言表達出來。
練習7.4-2
證實:快速排序的最佳狀況運行時間爲Ω(nlgn)
Hoare劃分快速排序
劃分方式有些不同;前面的partition劃分是將主元值與圍繞它劃分造成的兩部分分隔開來。而Hoare劃分則老是將主元值放入到兩個劃分的子數組裏。
1 int hoare_partition(int A[], int p, int r){ 2 int x = A[p]; 3 int i = p; 4 int j = r; 5 6 while(i < j){ 7 while(i < j && A[j] > x) 8 j--; 9 A[i] = A[j]; 10 while(i < j && A[i] < x) 11 i++; 12 A[j] = A[i]; 13 } 14 A[i] = x; 15 return j; 16 } 17 18 void hoare_quick_sort(int A[], int p, int r){ 19 if(p < r){ 20 int q = hoare_partition(A, p, r); 21 hoare_quick_sort(A, p, q-1); 22 hoare_quick_sort(A, q+1, r); 23 } 24 }
繼續努力。。。