演算法快學筆記(四):快速排序的原理與實現
1. 原理介紹
快速排序是一種排序演算法,速度比選擇排序快得多,其主要基於“分而治之”的思想對集合進行排序,本文將對該演算法進行分析。
2. 分而治之(D&C)的思想
D&C主要指利用遞迴的方式來不斷的縮小需要處理問題的規模,最終使問題容易解決。使用D&C解決問題的過程包括兩個步驟。
(1) 找出遞迴的終止條件,這種條件必須儘可能簡單(稱為基線
條件)。
(2) 不斷將問題分解(或者說縮小規模),直到符合遞迴的終止條件(稱為歸納條件)。
為了便於理解,舉個例子。
假設有一個數組A=[2,4,6], 想要統計A所有元素的和,可以使用兩種方式
- 遍歷A,並將結果一個一個的加起來
- 使用D&C思想,通過不斷縮小集合的規模,最終把每次遞迴的結果加起來,流程如下
程式碼如下:
// 使用dc的思想統計一個數組元素的和
func Sum(ints [] int) int {
if len(ints)==1{
//當集合只有一個元素時,返回結果
return ints[0]
}
subArr := ints[1:]
//每次遞迴的時候,傳個Sum的集合數量都比上一次少一個元素
return ints[0]+Sum(subArr)
}
3. 快速排序的原理
快速排序的流程大概分為兩步:
-
確定遞迴的終止條件:對於排序操作而言,如果陣列為空或只包含一個元素,則只需原樣返回陣列且不用排序。因此陣列為空或只包含一個元素可以作為迴圈結束的條件
-
如果沒有滿足遞迴終止條件,需要將陣列分解,並做以下操作,直到滿足遞迴終止條件。
2.1. 從陣列中選擇一個元素,這個元素被稱為基準值
2.2. 將陣列分成兩個子陣列:小於基準值的元素和大於基準值的元素
2.3. 對步驟2產生的兩個子陣列再執行快速排序操作
假設要對[2,1,3,5,4]進行排序,選擇了3為基準值,則流程大概如下:
對於快速排序,在基線條件中,我證明這種演算法對空陣列或包含一個
元素的陣列管用。在歸納條件中,如果快速排序對包含一個元素的陣列管用,對包含兩個元素的陣列也將管用;如果它對包含兩個元素的陣列管用,對包含三個元素的陣列也將管用,以此類推。因此,可以說明快速排序對任何長度的陣列都管用。
4. 執行時間分析
快速排序的平均情況執行的時間複雜度為O(n log n),但在最糟糕的情況下其複雜度將為O(n^2),下面對兩種情況進行分析
4.1 最糟糕情況
下面來看看到底啥時候才會出現最糟糕的情況,考慮如下的排序:
快速排序的效能高度依賴於你選擇的基準值。假設你總是將第一個元素用作基準值,且要處
理的陣列是有序的。由於快速排序演算法不檢查輸入陣列是否有序,因此它依然嘗試對其進行排序,需要進行N層操作,每層需要比較N個數。所以演算法複雜度為O(n^2)
4.2 最佳情況
考慮如下排序:
該圖也是對有序陣列進行排序,但每次都選擇中間的元素作為基準值,此時遞迴的次數變成了3次(logN),每次也是需要比較N個元素,所以演算法複雜度為O(n log n)
5. 程式碼實現
為了簡單演示,程式碼都始終選用第一個元素作為基準值
Python版本:
def quicksort(array):
if len(array) < 2:
return array
else:
pivot = array[0]
less = [i for i in array[1:] if i <= pivot]
greater = [i for i in array[1:] if i > pivot]
return quicksort(less) + [pivot] + quicksort(greater)
print quicksort([3,5,6,2,6,2])
總結
- D&C將問題逐步分解。使用D&C處理列表時,基線條件很可能是空陣列或只包含一個元
素的陣列。 - 快速排序的平均執行時間為O(n log n)。
- 當陣列有序,始終選擇第一元素作為基準值時,快速排序將出現最糟情況
- 當陣列有序,始終選擇中間元素作為基準值時,快速排序將出現最佳情況