本想先簡單的實現下快速排序,沒想到看到一個博客后發現了新大陸,一個快排竟然有這么多優化方法,鏈接在此:https://blog.csdn.net/qq_38289815/article/details/82718428。
在簡單的消化快速排序的知識后,在此記錄。
快速排序是冒泡排序的進階排序算法,它的平均時間復雜度為O(nlogn),在最差的情況下也就是在基准數在數組的一頭,然后邊上的數都比基准數大的時候,它的排序就變成了冒泡排序,時間復雜度變為O(n^2) 。
快速排序的思想是:
先從待排序數組中選定一個基准數,然后用兩個指針i跟j分別指向數組的最左邊和最右邊,先讓指針j從右邊向左移動,在不與i重合的情況下發現比基准數小的元素時,就將指針j當前所指的元素跟基准數換位,此時基准數換到了j所指的位置,然后讓L往數組右邊移動,在不與j重合的情況下找到比基准數大的數時停下,將基准數與i所指位置交換,就這樣直到i跟j重合,此時的基准數所在位置也在i,j所指位置,你會發現此時在基准位置左邊的數都小於基准數,右邊的都大於基准數。具體過程如下圖1所示:
圖1
快排的分治思想可以用函數的遞歸來實現,為了防止遞歸深度過大導致棧內存爆炸,安全的做法是采用尾遞歸方法,我之前先去了解了尾遞歸的知識。我的理解是:只要回歸過程中沒有任何操作,當編譯器檢測到一個函數調用是尾遞歸的時候,它就覆蓋當前的活動記錄而不是在棧中去創建一個新的。編譯器可以做到這點,因為遞歸調用是當前活躍期內最后一條待執行的語句,於是當這個調用返回時棧幀中並沒有其他事情可做,因此也就沒有保存棧幀的必要了。通過覆蓋當前的棧幀而不是在其之上重新添加一個,這樣所使用的棧空間就大大縮減了,這使得實際的運行效率會變得更高。
我的尾遞歸快速排序代碼如下:
頭文件Sort.h:
1 //自己編寫的各種排序算法的頭文件。 2 #ifndef _SORT_H 3 #define _SORT_H 4 //冒泡排序 5 class bubble_sort{ 6 private: 7 int *Array,Length; 8 public: 9 bubble_sort(int *Array1,int Length); 10 int *sort_bubble(); 11 12 }; 13 //歸並排序 14 class merge_sort{ 15 public: 16 static void sort_merge(int Array1[], int Array2[], int Left, int Rightend, int Right,int recursion); 17 static void Merge(int Array1[], int Array2[], int Left, int Rightend);//遞歸方法 18 static void Merge_pass(int Array1[], int Array2[], int ordered_len,int Length); 19 static void Merge1(int Array1[], int Array2[], int Length);//非遞歸方法 20 }; 21 class quick_sort{ 22 public: 23 static void sort_quick(int Array1[],int Left,int right); 24 25 }; 26 #endif
quick_sort.cpp文件:
1 #include "Sort.h" 2 3 void quick_sort::sort_quick(int Array1[], int Left, int Right) 4 { 5 if (Left<Right) 6 { 7 int T = Array1[Left], Leftbegin = Left, Rightend = Right,temp; 8 while (Left<Right) 9 { 10 while (Array1[Right] >= T &&Right>Left)//左移右指針。 11 Right--; 12 //交換右邊指針所指數與基准數。 13 temp = Array1[Left]; 14 Array1[Left] = Array1[Right]; 15 Array1[Right] =temp; 16 while (Array1[Left] <T &&Right>Left)//右移左指針 17 Left++; 18 //交換左邊指針所指數與基准數。 19 temp = Array1[Left]; 20 Array1[Left] = Array1[Right]; 21 Array1[Right] = temp; 22 } 23 //尾遞歸。 24 quick_sort::sort_quick(Array1, Leftbegin, Left-1); 25 quick_sort::sort_quick(Array1, Left + 1, Rightend); 26 } 27 }
main.cpp:
1 #include <iostream> 2 #include "Sort.h" 3 using namespace std; 4 void main() 5 { 6 int Array[10] = { 30, 2, 1111, 4,8,10,100,33,40,11 }; 7 int N = sizeof(Array) / sizeof(int) ; 8 quick_sort::sort_quick(Array, 0,N-1); 9 10 for (int i = 0; i < sizeof(Array) / sizeof(int); i++) 11 { 12 cout << Array[i] << endl; 13 } 14 system("pause"); 15 }
快速排序的效率受基准點的選取的影響最大,當基准點被選為數組第一個數或者最后一個數的時候,若待排序的是一個有序數組,也就是說,每次遍歷一個長度為n的數組之后通過基准點分割出來的兩個數組的長度分別為0和n-1,那么快速排序就會變成冒泡排序,它的時間復雜度會變成O(n^2),又或者當你不是選擇數組第一個數或者最后一個數最為基准點而是隨機選擇數組中一個位置為基准點,而每次這個基准點剛好是這組數列中最大或者最小的數的時候,情況又會變成上面說到的,分割出兩端長度極不平衡的兩端序列,時間復雜度升至O(n^2)。
最理想的狀態就是每次通過基准點分割出來的兩個數組的長度皆為n/2。所以隨機選取基准點是一個改進的辦法,又或者是選取數組的中間位置的點,最保險的方法是選取第一個點的數和中間位置的數和最后一個數中的中間值的位置。
在最上面所給的鏈接的博客中,有寫到快速排序的很多優化方式,實際上在工程上使用排序時,往往是多個排序方式聯合使用,就如當子序列長度小於20個時可以換成插入排序對子序列進行排序。
快速排序能解決什么問題呢?
之前在面試時難到過我的一個問題:在一個擁有巨大數據量的數組中找到數值排前100位的數。
這個問題的其中一種優化解法就用到了快速排序的分治算法。
我現在的解答是:
先隨機選定數組中一個位置的數選為基准數,經過一次快速排序后(不用繼續遞歸),我們能得到該位置的數的大小在該數組中的排名k,
當k排名在99之前的話,我們就在數組位置[k+1~數組.length-1]的區間選定一個位置的數作為基准點繼續遞歸;
當k排名在99名之后的話,我們就在數組位置[0~k-1]的區間選定一個位置的數作為基准點繼續遞歸;
直到找到k=99的數。
這么做跟快速排序的穩定性一樣,是不穩定的。因此還有別的解法,這里就不多贅述了。
仍然希望讀者們發現不足之處能給予指正,謝謝!