《編程珠璣》之快速排序

1. 一種簡單的快速排序

快速排序最重要的就是partition函數,即選定某一個數后,使得所有小于該數的數字都在其左側,大于該數的數字都在其右側。本節給出的算法過程如下:

我們將需要劃分的目標區間定位[l, u]。
首先給定目標值t = x[l]。我們需要重新組織x[l...u],使得所有小于t的元素都在m的一端,所有大于t的元素在m的另一端。
初始時m = l,我們將i從l+1一直遍歷到u,代碼在檢測第i個元素時必須考慮兩種情況。如果x[i]>=t,那么一切正常,不變式為真;如果x[i]<t,可以通過使m增加1(指向小元素的新位置)重新獲得不變式,然后交換x[i]和x[m]。

完整代碼如下:

void qsort1(int nums[], int l, int u) {
    if (l < u) {
        int m = l;
        for (int i = l + 1; i <= u; i++) {
            int num = nums[i];
            if (num < nums[l]) {
                swap(nums[++m], nums[i]);
            }
        }
        swap(nums[m], nums[l]);
        myqsort(nums, l, m - 1);
        myqsort(nums, m + 1, u);
    }
}
2. 更好的幾種快速排序

qsort1函數能夠快速完成對隨機整數數組的排序,但是在非隨機的輸入上它的性能如何呢?我們不妨采用一種極端的情況:n個相同元素組成的數組,對于這種輸入,插入排序的性能非常好:每個元素需要移動的距離都為0;但是qsort1函數的性能卻非常糟糕。n-1次劃分中每次劃分都需要O(n)時間來去掉一個元素,所以總的運行時間為O(n2)。使用雙向劃分可以避免這種情況。

下標i和j初始化為待劃分數組的兩端。主循環中有兩個內循環,第一個內循環將i向右移過小元素,遇到大元素時停止;第二個內循環將j向左移過大元素,遇到小元素時停止。然后主循環測試這兩個下標是否交叉并交換它們的值。這樣做雖然交換的次數增加了,但卻將所有元素都相同的最壞情況變成了差不多需要nlog2n次比較的最好情況,實現代碼如下:

void qsort2(int nums[], int l, int u) {
    if (l < u) {
        int i = l + 1;
        int j = u;
        int t = nums[l];
        while (i <= j) {
            while (i <= u && nums[i] < t) {
                i++;
            }
            
            while (nums[j] > t) {
                j--;
            }
            if (i <= j) {
                swap(nums[i], nums[j]);
            }
        }
        swap(nums[l], nums[j]);
        qsort2(nums, l, j - 1);
        qsort2(nums, j + 1, u);
    }
}

到目前為止我們看到的快速排序都是圍繞數組的第一個元素進行劃分的。對于隨機輸入,這樣做沒問題;但對于某些常見輸出,這種做法需要的時間和空間都偏多。例如,雖然數組已經按升序排好了,那么它就會先圍繞最小的元素進行劃分,然后是第二小的元素,以此類推,總共需要O(n2)的時間。隨機選擇劃分元素就可以得到好得多的性能,我們通過把x[l]與x[l...u]中的一個隨機項相交換來實現這一點:

swap(l, randint(l, u));

我們的快速排序花費了大量的時間來排序很小的子數組。如果用插入排序之類的簡單方法來排序這些很小的子數組,程序的速度會很快。我們將qsort2中的第一個if語句改為:

if (u - l >= cutoff)

其中cutoff是一個小整數。程序結束后,數組并不是有序的,而是被組合成一塊一塊隨機排列的值,并且滿足這樣的條件:某一塊中的元素小于它右邊任何塊中的元素。我們必須通過另一種排序算法對塊的內部進行排序。由于數組是幾乎有序的,因此插入排序比較適用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • quicksort可以說是應用最廣泛的排序算法之一,它的基本思想是分治法,選擇一個pivot(中軸點),將小于pi...
    黎景陽閱讀 457評論 0 1
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,214評論 0 52
  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,742評論 0 15
  • 總結一下常見的排序算法。 排序分內排序和外排序。內排序:指在排序期間數據對象全部存放在內存的排序。外排序:指在排序...
    jiangliang閱讀 1,369評論 0 1
  • 本文轉載自http://tech.glowing.com/cn/如有侵犯,立馬刪除原文鏈接:實現iOS 9 Tas...
    木菀閱讀 350評論 0 0