1. 程式人生 > >圖解演算法學習筆記(四):快速排序

圖解演算法學習筆記(四):快速排序

本章內容:學習分而治之,快速排序

1) 示例1:

假設你是農場主,有一小塊土地,你要將這塊地均勻分成方塊,且分出的方塊儘可能大。如何分?

你要將這塊地均勻分成方塊,且分出的方塊要儘可能大。顯然,下面的分法不符合要求。

此時,你應該使用D&C策略(divide and conquer)。包括兩步驟:

(1) 找出基線條件,這種條件必須儘可能簡單。

(2)不斷將問題分解(或者說縮小規模),直到符合基線條件。

下面就來使用D&C找出問題的解決方案。首先,找出基線條件。最容易處理的情況是,一條邊的長度是另一邊的整數倍。

現在找出遞迴條件,這正是D&C的用武之地。每次遞迴都必須縮小問題的規模,如何縮小問題的規模呢,首先,找出這塊地可容納的最大方塊。

如圖,劃出了兩個方塊,同時餘下一小塊地。現在是頓悟時刻,何不對餘下的那一小塊地使用相同的演算法呢?

這裡有一個關鍵的地方,就是適用於這小快地的最大方塊,也是適用於整塊地的最大方案。感興趣的可以查查歐幾里得演算法。

接下來就是使用同樣演算法。直到餘下土地為方塊。

           

      

現在我們找到了最大方塊,如下圖:

2)快速排序

快速排序是一種常用的排序演算法,比選擇排序快得多,例如,C語言標準庫的函式qsort實現的就是快速排序。

對排序演算法來說,最簡單的陣列什麼樣呢?就是根本不需要排序的資料。

因此,基線條件為陣列為空或只包含一個元素

。在這種情況,只需返回陣列:

def quicksort(array):
    if len(array) < 2:
        return array

我們來看更長的陣列。對包含兩個元素的陣列進行排序也很容易:

包含三個元素呢?

現在介紹快速排序的工作原理:首先,從陣列中選擇一個元素,這個元素被稱為基準值(pivot).

我們暫時將陣列的第一個元素用作基準值.接下來,找出比基準值小的元素以及比基準值大的元素。

這被稱為分割槽(partitioning)。這裡只進行了分割槽,得到的兩個子陣列是無序的。如何對子陣列進行排序呢?對於包含兩個元素的陣列以及空陣列,快速排序知道如何將它們排序。因此對這兩個子陣列進行快速排序,再合併結果,就得到一個有序陣列!

quicksort([15, 10]) + [33] + quicksort([])
> [10, 15, 33]

現在我們知道了如何對包含三個元素的陣列進行排序了:

(1)選準基準值。

(2)將陣列分成兩個子陣列:小於基準值的元素和大於基準值的元素。

(3)對這兩個子陣列進行快速排序。

下面是快速排序的程式碼:

def quicksort(array):
    if len(array) < 2:
        return array
    else:
        pivot = array[0]
        less = [i for i in array if i<= pivot]
        greater = [i for in array if i > pivot]
        return quicksort(less) + [pivot] + quicksort(greater)

3) 再談大O表示法

我們再來看看常見的大O執行時間:

這裡需要說明的是,在平均情況下,快速排序的執行時間為O(n logn)。

快速排序的效能高度依賴於你選擇的基準值。來看下面這樣一個有序陣列,每次都選擇第一個元素為基準值,來看看快速排序過程,

現在選擇中間元素作為基準值,看看排序過程:

第一個示例展示的是最糟情況,棧長為O(n),第二個示例展示的是最佳情況,棧長為O(log n)。與此同時,在呼叫棧的每層都涉及全部8個元素,運算元為O(n)。

現在可以得出快速排序的執行時間為O(n logn)(最佳情況),最佳情況也是平均情況。

4)小結

  • D&C將問題逐步分解,使用D&C處理列表時,基線條件很可能是空陣列或只包含一個元素的陣列;
  • 實現快速排序時,請隨機選地選擇用做基準值的元素,快速排序的平均執行時間為O(n log n);
  • 大O表示法中的常量有時候事關重大,這就是快速排序比合並排序快的原因所在;
  • 比較簡單查詢和二分查詢時,常量幾乎無關緊要,因為列表很長時,O(log n)的速度為O(n)塊很多。