歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

快速排序的簡單實現

 算法這一塊是我的弱項。就以快速排序這樣簡單的算法,大二學完以後,就沒有回顧過了。因為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)式要求的函數實現如下(采用模板):

template <typename Type>
int find_pilot(Type arr[], int iLen) {
    int my_pilot = 0;
    int pilot_value = arr[0];

    for (int i = 1; i != iLen; ++i) {
        if (pilot_value > arr[i]) {
            // pilot位置上放上arr[i]
            arr[my_pilot] = arr[i];

            // pilot自增
            my_pilot++;

            // pilot和arr[i]交換,以保證pilot指向的值無意義。
            std::swap(arr[i], arr[my_pilot]);
        }
    }

    arr[my_pilot] = pilot_value;
    return my_pilot;
}

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

template <typename Type>
void quick_sort(Type arr[], int iLen) {
    if (iLen <= 1) {
        return;
    }

    int pilot = find_pilot(arr, iLen);
    quick_sort(arr, pilot);
    quick_sort(arr + pilot + 1, iLen - pilot - 1);
}

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

int main() {
    std::cout << "= = = = = 快速排序算法 = = = = =" << std::endl;
    std::cout << "排序前的數組:";

    int iTestArray[10] = { 0 };
    srand((unsigned int)time(NULL));
    for (int i = 0; i != 10; ++i) {
        iTestArray[i] = rand() % 100;
        std::cout << iTestArray[i] << " ";
    }
    std::cout << std::endl;

    quick_sort(iTestArray, 10);

    std::cout << "排序後的數組:";
    for (int i = 0; i != 10; ++i) {
        std::cout << iTestArray[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

測試結果如下:

Copyright © Linux教程網 All Rights Reserved