快速排序的簡單實現


  算法這一塊是我的弱項。就以快速排序這樣簡單的算法,大二學完以后,就沒有回顧過了。因為C中有qsort()接口,而C++中也有sort()接口。前一陣子想鞏固一下基礎知識,回顧了這一著名算法。

  因為大學學過,所以大致知道它的一個過程——也就是一個遞歸。設給定一序列arr[0...N],首先通過arr[0]將arr[0...N]一分為二(我比較懶,不畫圖了,大家將就看哈),如下:

  __前半部分,特點:這半部分序列中元素<arr[0]__ arr[0] __后半部分,特點:這半部分序列元素>= arr[0]_ (1)

                                    ↑

                     pilot所在位置(是不是叫pilot的?我忘了,^_^)

  接下來,就是遞歸排序前半部分和后半部分,遞歸終止條件就是待排序的序列長度<=1。這個過程是不是很簡單?那怎么找arr[0]所在位置,換句話說,怎么把原來的序列一分為二,滿足(1)給定的條件?其實,這個問題我一開始也想了好久,第一版代碼寫出來運行結果還是不對的,經過調試才最后運行正確。

  這里必然要對序列進行遍歷,並且遍歷過程中要保持一定的要求——就是所謂的循環不變式(《算法導論》一開始就介紹這個概念了)。我們這里要滿足的要求(或學術化一點叫循環不變式)可以這么描述:遍歷過程中,假設現在循環變量值為i,我們要保證:

          arr'[0]...arr'[pilot-1] < arr'[pilot] <= arr'[pilot+1]...arr'[i]      (2)

我們這里符號用的是arr'(上撇號),這里arr'[pilot]存放的是arr[0],采用不同符號以表示與原始序列不同了。

問題:怎么在遍歷過程中,保持(2)成立???

  其實,想到也不難,可能要花點心思。首先把arr[0]保存下來,放在變量pilot_value中,初始的情形如下:

        ____ arr[1] arr[2]...arr[N]

          ↑   ↑

        pilot  i

因為arr[0]保存下來了,所以pilot所指的位置可以認為是沒有意義了,所以我在畫了一條線——表示這可以看做是空的。遍歷從i=1開始,遍歷的目的是把所有小於pilot_value的值放到pilot所指位置的左邊(如上:初始的時候,pilot左邊是沒有元素)。現在我們假設循環變量到i+1了,表明前面i個元素滿足(2)式要求了,現在就是移動arr[i+1]到合適位置,如下:

        arr'[0]...arr'[pilot-1] < ___ <= arr'[pilot+1]...arr'[i] arr[i+1]                (3)

                     

                    pilot

很簡單,就是比較pilot_value和arr[i+1],兩種情況咯:

     (1)若 pilot_value <= arr[i+1],不需要做任何操作;

     (2)若pilot_value > arr[i+1],此時:直接將pilot位置上放置arr[i]嗎?那就試試看,如果這么做,就會出現如下情況:

              arr'[0]...arr'[pilot-1] arr[i] arr'[pilot+1]...arr'[i] _____              (4)

                                        ↑

                                      新的pilot

這時,pilot應該+1,即箭頭指向的位置。我們知道pilot指向的位置在遍歷完成后,是要存放arr[0](即:pilot_value)的,而此時pilot指向的是一個有意義的位置。注意(4)中最后一個位置存放的是arr[i+1],這個位置現在存放這個值還有意義嗎?顯然沒意義了!這時只要把arr’[pilot+1]和下划線所在位置的值換一下就好了,這樣新的pilot所指向的位置就可以認為沒意義了——可以看做是空的了。最后結果如下:

            arr'[0]...arr'[pilot-1] arr[i] _____ arr'[pilot+2]...arr'[i]arr'[pilot+1]        (5)

                           ↑

                         新的pilot

其實現在 新的pilot所指向的位置存放的值時arr[i]。(5)式顯然是保持(2)式要求的。遍歷完整個序列,得到最后的pilot,然后把pilot_value放到這個位置上,就可以了。最后查找pilot並整理序列以滿足(2)式要求的函數實現如下(采用模板):

 1 template <typename Type>
 2 int find_pilot(Type arr[], int iLen) {
 3     int my_pilot = 0;
 4     int pilot_value = arr[0];
 5 
 6     for (int i = 1; i != iLen; ++i) {
 7         if (pilot_value > arr[i]) {
 8             // pilot位置上放上arr[i]
 9             arr[my_pilot] = arr[i];
10 
11             // pilot自增
12             my_pilot++;
13 
14             // pilot和arr[i]交換,以保證pilot指向的值無意義。
15             std::swap(arr[i], arr[my_pilot]);
16         }
17     }
18 
19     arr[my_pilot] = pilot_value;
20     return my_pilot;
21 }

  快速排序就是先通過上述函數將原始序列按pilot分為兩個子序列,然后針對兩個子序列分別遞歸調用,代碼如下:

 1 template <typename Type>
 2 void quick_sort(Type arr[], int iLen) {
 3     if (iLen <= 1) {
 4         return;
 5     }
 6 
 7     int pilot = find_pilot(arr, iLen);
 8     quick_sort(arr, pilot);
 9     quick_sort(arr + pilot + 1, iLen - pilot - 1);
10 }

  最后,我們測試代碼如下:

 1 int main() {
 2     std::cout << "= = = = = 快速排序算法 = = = = =" << std::endl;
 3     std::cout << "排序前的數組:";
 4 
 5     int iTestArray[10] = { 0 };
 6     srand((unsigned int)time(NULL));
 7     for (int i = 0; i != 10; ++i) {
 8         iTestArray[i] = rand() % 100;
 9         std::cout << iTestArray[i] << " ";
10     }
11     std::cout << std::endl;
12 
13     quick_sort(iTestArray, 10);
14 
15     std::cout << "排序后的數組:";
16     for (int i = 0; i != 10; ++i) {
17         std::cout << iTestArray[i] << " ";
18     }
19     std::cout << std::endl;
20 
21     return 0;
22 }

測試結果如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM