- 作為最常用和好用的排序方法, 快速排序是每個工程師必須隨時能夠手寫出代碼和解釋其運行原理的算法
快速排序算法
- Quick-Sort(A, p, r)
if p<r
q = Partition(A, p, r)
Quick-Sort(A, p, q-1)
Quick-Sort(A. q+1, r)
- Partition(A, p, r)
q = RANDOM(p, r)
swap A[q] and A[r] //現在A[r]是一個隨機挑選的值
i = p-1 // i指向分界線左側最后一個元素, 其初始值定在p-1表示還沒有任何小于q的元素
for j=p to r-1 //區分已經分好的未分好的區域的指針不斷向右移動
if A[j]<=A[r]
i++ //讓小于q的分界線指針向右移動
swap A[i] with A[j] //發現A[j]是比q所在值小的話, 把A[j]換到分界線的左側第一個位置, 即A[i]位置
swap A[i+1] and A[r] //交換分界線右側第一個位置的元素, 保證q左側的<=q, q右側的>=q
return q
快排時間復雜度分析
CLRS告訴我們, 快速排序在不斷遞歸的過程中, 遞歸樹的每一層花費的時間都是Partition的時間, 因此整體的時間復雜度T(n)取決于整個算法到底進行了多少次Partition, 而每次Partition的時間復雜度又是不相等的 -- Partition的時間復雜度取決于其中的for循環進行的次數, 而 for循環主要的操作就是比較大小.
所以, 整個算法的T(n)最后在研究進行了幾次比較大小操作的問題
-
對于輸入數組A的規模為n的情況, 我們已經知道的:
- 每個元素對(pair)最多只比較一次, 而且這次比較只出現在pair中有一個元素被選中為q (A[q] becomes partition pivot).
- 反過來說, 在選q的時候, 如果一個pair<ai, aj> (i<j且都∈[current-p, current-r])中的元素都沒被選中, 而是有一個i<=x<=j的的元素被選為pivot, 那么ai和aj將永遠失去相互比較的機會. 注意, 此處如果[i, j]區間外的元素被選中, 由于a[i], a[j]可能被分配到同一邊, 仍存在相互比較的機會, 這對寫概率公式很重要
- Ind{[i, j]區間中A[i], A[j]外的其他元素不被選中} = Ind{a[i]或者a[j]被選中作為pivot} == Ind{在[i, j]中i被選中} + Ind{在[i, j]中j被選中}
- E[Ind{在[i, j]中i被選中}] = E[Ind{在[i, j]中j被選中}] = 1/(j-i+1)
- 規模為n的情況下, 且元素對不考慮排序, 會有C(n, 2)= (n(n-1))/(21) 個pair的組合, 一般為了算期望, 我們會改寫成 Σ[in-1]Σ[j=i+1n] 形式 (容易證明得到這兩個寫法是完全等價的) 來遍歷得出所有可能的組合, 即pair的數目
假設X為總的比較次數的隨機變量 E[X] = Σ[in-1]Σ[j=i+1n] (Ind{a[i]或者a[j]被選中作為pivot}) = Σ[in-1]Σ[j=i+1n](2 / (j-i+1) ) = O(nlgn)