1、快速排序概述html
關於快速排序,我以前寫過兩篇文章,一篇是寫VC庫中的快排函數,另外一篇是寫了快排的三種實現方法。如今再一次看算法導論,發現對快速排序又有了些新的認識,總結以下:算法
(1)、快速排序最壞狀況下的時間複雜度爲O(n^2),雖然最壞狀況下性能較差,但快排在實際應用中是最佳選擇。緣由在於:其平均性能較好,爲O(nlgn),且O(nlgn)記號中的常數因子較小,並且是穩定排序。編程
(2)、快速排序的思想和合並排序同樣,即分治。快排排序的分治思想體如今:數組
a、首先從待排序的數中選擇一個做爲基數,基數的選擇對於排序的性能有很大的影響,也是快排改進的關鍵所在。網絡
b、分治,將比基數小的數放在左邊,比基數大的數放在右邊。dom
c、對分出來的兩個分區分別執行上一步,直到區間只有一個數爲止。函數
2、Hoare(霍爾)排序工具
快速排序首先由 C. A. R. Hoare(東尼霍爾,Charles Antony Richard Hoare)在1960年提出,以後又有許多人作了進一步的優化。見書本習題7-1。性能
霍爾排序思路:採用數列第一個數做爲基數,而後在數列的收尾兩端分別設置兩個「哨兵」,兩個哨兵分別向中間探測比基數大、小的數,而後進行交換。以下圖展現:優化
下面是霍爾排序的代碼:
1 int Hoare_Partition(int arr[], int left, int right) 2 { 3 int temp = arr[left]; 4 int i = left; 5 int j = right; 6 7 while(i < j) { 8 while (arr[j] >= temp && i < j) //from right to left 9 j --; 10 while (arr[i] <= temp && i < j) //from left to right 11 i ++; 12 Swap(arr[i], arr[j]); 13 } 14 Swap(arr[left], arr[i]); 15 return i; 16 } 17 18 void Hoare_QuickSort(int arr[], int left, int right) 19 { 20 if (left < right) { 21 int mid = Hoare_Partition(arr, left, right); 22 Hoare_QuickSort(arr, left, mid-1); 23 Hoare_QuickSort(arr, mid+1, right); 24 } 25 }
3、算法導論講述的快排
和霍爾排序不一樣的是,算法導論上實現的快排選取待排序數列的最後一個數做爲基數,而後也設置兩個哨兵,但這兩個哨兵是從頭至尾一塊兒前進探測的。若是探測到一個數比基數小,就把該數移到左邊,天然右邊就成了最大的數了。代碼以下:
1 int Partition(int arr[], int left, int right) 2 { 3 int temp = arr[right]; 4 int i = left - 1; 5 6 for (int j = left; j <= right-1; j ++) { 7 if (arr[j] <= temp) { 8 i ++; 9 Swap(arr[i], arr[j]); 10 } 11 } 12 Swap(arr[right], arr[i+1]); //!!!note: can't use temp:local variable 13 return i+1; 14 } 15 16 void QuickSort(int arr[], int left, int right) 17 { 18 if (left < right) { 19 int mid = Partition(arr, left, right); 20 QuickSort(arr, left, mid-1); 21 QuickSort(arr, mid+1, right); 22 } 23 }
4、快排的優化版本
如前所述,影響快排性能最大的因素在於基數的選取,雖然無論基數如何選取,算法最壞狀況下時間複雜度都還存在,但可以減小常數項因子,從而優化了算法性能。下面引述下書上介紹的幾種優化機制:
一、隨機優化:
由於快排中Partition所產生的劃分中可能會有」差的「,而劃分的關鍵在於主元A[r]的選擇。咱們能夠採用一種不一樣的、稱爲隨機取樣的隨機化技術,把主元A[r]和A[p..r]中隨機選出一個元素交換,這樣至關於,咱們的主元不在是固定是最後一個A[r],而是隨機從p,...,r這一範圍隨機取樣。這樣可使得指望平均狀況下,Partition的劃分可以比較對稱。
二、中位數優化法:
所謂「三數取中」是指,從子數組中隨機選出三個元素,取其中間數做爲主元,這算是前面隨機化版本的升級版。雖然是升級版,可是也只能影響快速排序時間複雜度O(nlgn)的常數因子。見習題7-5.
三、遞歸棧的優化:
QUICKSORT算法包含兩個對其自身的遞歸調用,即調用PARTITION後,左邊的子數組和右邊的子數組分別被遞歸排序。QUICKSORT中的第二次遞歸調用並非必須的,能夠用迭代控制結構來代替它,這種技術叫作「尾遞歸」,大多數的編譯器也使用了這項技術。
模擬的尾遞歸:
代碼實現:
1 //隨機優化版本 2 //get random num between m and n; 3 int Random(int m, int n) 4 { 5 srand((unsigned int)time(0)); 6 int ret = m + rand() % (n-m+1); 7 return ret; 8 } 9 10 11 void Random_QuickSort(int arr[], int left, int right) 12 { 13 int index = Random(left, right); 14 15 Swap(arr[index], arr[right]); 16 QuickSort(arr, left, right); 17 }
1 //中位數優化,下面一個獲取中位數的函數 2 //get mid num of a,b,c; 3 int MidNum(int a, int b, int c) 4 { 5 if ((a-b)*(a-c) <= 0) 6 return a; 7 else if ((b-a)*(b-c) <= 0) 8 return b; 9 else if ((c-a)*(c-b) <= 0) 10 return c; 11 }
1 //模擬尾遞歸 2 void Tail_Recursive_QuickSort(int arr[], int left, int right) 3 { 4 while (left < right) { //use while not if 5 int mid = Partition(arr, left, right); 6 Tail_Recursive_QuickSort(arr, left, mid-1); 7 left = mid + 1; 8 } 9 } 10 11 //尾遞歸優化 12 void Tail_Recursive_QuickSort_Optimize(int arr[], int left, int right) 13 { 14 while(left < right) { 15 int mid = Partition(arr, left, right); 16 if (mid-left < right-mid) { 17 Tail_Recursive_QuickSort_Optimize(arr, left, mid-1); 18 left = mid + 1; 19 } 20 else { 21 Tail_Recursive_QuickSort_Optimize(arr, mid+1, right); 22 right = mid - 1; 23 } 24 } 25 }
此外,還有一些其餘的方法,好比,將遞歸的方式改爲非遞歸,還有習題7-6提出的區間模糊排序法:咱們沒法準確知道待排序的數字是什麼,但知道它屬於實數軸上的某個區間,也就是知道形如[ai, bi]的閉區間。咱們能夠對這些區間進行排序,感興趣的能夠本身實現下。
個人公衆號 「Linux雲計算網絡」(id: cloud_dev),號內有 10T 書籍和視頻資源,後臺回覆 「1024」 便可領取,分享的內容包括但不限於 Linux、網絡、雲計算虛擬化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++編程技術等內容,歡迎你們關注。