隨機化快排 期望執行時間證明

2021-09-19 11:10:58 字數 3674 閱讀 3561

快速排序是比較排序的一種,其最壞執行時間 $\theta (n^2)$,期望執行時間為 $\theta(nlgn)$。並且能夠原址排序。實際中的很多排序都由此優化而來。

本文主要不對演算法的程式實現進行討論,主要關注其隨機化,及背後的數學證明。

在《演算法導論》上,快速排序用 python 表示大致如下:

def quicksort(arr,start,end):

pivot = end

i = partition(arr,pivot,start)

if (i > start + 1):

quicksort(arr,start,i - 1)

if (i < end - 1):

quicksort(arr,i + 1,end)

def partition(arr,pivot,start):

x = arr[pivot]

i = start - 1

for index in range(start,pivot):

if (arr[index] <= x):

i = i + 1

swap(arr,i,index)

swap(arr,i + 1,pivot)

return i + 1

def swap(arr,i,j):

tem = arr[i]

arr[i] = arr[j]

arr[j] = tem

需要注意的是關鍵的子程式 partition:即選定主元(pivot),對「部分」陣列進行重整,將比主元小的元素置於其前,比主元大的置於其後。

如上的快速排序並不能保證較好的最壞執行時間。設想當陣列倒序時,如上程式的執行時間就到 $n^2$ 這個數量級了。

為了能夠盡量地獲得期望的執行時間 $\theta(nlgn)$,應當在演算法中引入隨機性,減少最壞情況發生的概率。而引入隨機性的位置正是子程式 partition。可以看到在上述的快速排序中,partition 過程中是比較暴力地把 pivot 定為子陣列的最後乙個元素。

那麼只要我們加入類似這樣的步驟:

i = randomselect(arr,pivot,start)

swap(arr,i,pivot)

就能保證作為主元的元素是理論上隨機的了。

在保證其隨機性之後,就到了本文的重點:如何證明快排的期望執行時間為 $\theta(nlgn)$。

猜出這個值倒是並不難。畢竟接觸排序問題的時候分治思想是那麼普遍,式子裡不帶個對數都覺得不自在吧(逃

這一部分有抄書之嫌,有《演算法導論》在手的童鞋當然可以自己去看啦~

在整個快速排序的過程中,耗時的部分大致有二:

迴圈內 partition 中主元和其它元素的比較操作

迴圈外的常數級操作

存在變數的是第一部分,因為一次 partition 選擇的主元是由上一次決定的。而上一次 partition 後所在陣列的位置無法確定(主元的選擇本身就是隨機的嘛)。在最壞的情況下,主元依舊會落在陣列的頭部或尾部,如果一直都這麼「倒霉」,那麼每次迭代無非是對 $n - 1$ 個元素重複上次操作,並最終導向 $n^2$ 的數量級。

演算法導論的證明方法是整體性,而不侷限於單次的 partition 過程。書中的證明出發點是:

1.每次 partition 過程中,只有主元有和其它元素比較的機會,並且主元在此次過程後,就不會再和任何其它元素比較了(迭代時主元被排除在外了)。

2.一旦某次 partition 後兩個元素被主元分開成兩個部分,那麼也不會再發生比較了。

假設研究的物件是陣列 $a$ ,並且將 $a$ 中的每個元素定義為 $z_1,z_2,z_3 ... z_n$。這裡的 $z_i$ 表示為 a 中 第 $i$ 小的元素。

接著我們定義乙個集合

$$z_=\\lbrace z_i,z_,z_,...,z_j\rbrace $$

為 zi 和 zj 之間的元素集合。

需要格外注意的是,定義$z_$並不意味著將整個陣列 $a$ 按順序排列了。集合$z_$中位於$zi$和$zj$之間的元素可以分布於陣列的任何乙個位置。

此外,定義元素$i$和元素$j$發生比較的指示器隨機變數為:

$$x_ = i\lbrace z_i 和z_j發生比較 \rbrace$$

那麼總的比較次數 x 可表示為:

$$x = \sum_^ \sum_}^x_$$ $$

基於以上兩點,在整個迭代過程裡,$z_i$和 $z_j$ 發生比較的可能性只存在於,對於集合 $z_ij$ 中所有的元素,在某次 partition 操作中,$z_i$ 和 $z_j$ 中的任意乙個被「第乙個」選擇為主元,除此之外,這兩個元素都將被主元一分為二,不再有比較的機會。

所以對於 $z_i$ 和 $z_j$ ,其發生(一次)比較的概率可表示為:

$$pr\lbrace z_i 為 z_ 中第乙個被選出的主元\rbrace + pr\lbrace z_j 為 z_ 中第乙個被選出的主元 \rbrace$$

比較容易得出,$z_i$ 和 $z_j$ 被選為主元的概率同是

$$\frac$$

那麼考慮期望 $e(x)$ 就可以表示為:

$$x = \sum_^ \sum_}^\frac$$

令 $k = j - i$,上式可以轉化為:

$$x = \sum_^ \sum_^\frac$$

而$$x = \sum_^ \sum_^\frac < x = \sum_^ \sum_^\frac$$

調和級數本身是發散的,但是其與$lgn$的差所構成的通項:

$$x_n=1+\frac+\frac+...+\frac-lnn$$

隨著$n$趨向於無窮大,是收斂的。(選擇$lnn$是為了之後證明方便)

我們要用到的定式是

若:$$\sum_^|x_n-x_|$$

收斂,那麼

$$\sum_^(x_n-x_)$$

也收斂,從而:

$$\sum_^(x_n-x_) + x_1$$

自然存在乙個極限,而上式展開之後即為

$$ \lbrace x_n\rbrace $$

回到這個式子:

$$x_n=1+\frac+\frac+...+\frac-lnn$$

利用上面的公式,我們求$|x_n-x_|$,得到:

$$|x_n-x_|=\frac-[lnn - ln(n-1)]$$

使用一下拉格朗日中值定理:對於某個數 $m$ 在 $n-1$ 和 $n$之間,存在:

$$\fracf'(x)=lnn - ln(n-1)$$

當然,這裡

$$f(x) = f'(x)$$

那麼,利用$(n-m)<1$,得到:

$$|x_n-x_|=\frac < \frac$$

那麼:$$\sum_^|x_n-x_|$$

是收斂的就是顯而易見的了。

這樣我們可以認為:

$$x_n=1+\frac+\frac+...+\frac = \theta (lnn)$$

從而$$x_n=1+\frac+\frac+...+\frac = o(lgn)$$

也是易得的。

根據以上推論,稍作調整,就能得出:

$$\sum_^ \sum_^\frac=o(nlgn)$$

隨機化快速排序的期望時間得證。

手寫nth element模板(隨機化版快排)

特殊資料下會被卡成n 2 系統的nth element並不會,因為穩定的sort大概用到了3種方法以保持其穩定性 特殊資料下會被卡成n 2 系統的nth element並不會,因為穩定的sort大概用到了3種方法以保持其穩定性 特殊資料下會被卡成n 2 系統的nth element並不會,因為穩定的...

快速排序和隨機化快排學習

0.基本思想 選定基準值 通常為當前陣列的第乙個元素 經過一系列比較和交換,將比基準值小的放在其左邊,將比基準值大的放在其右邊,這稱為是一次迴圈。include using namespace std void qsort int arr,int low,int high 從右向左找比key小的值 ...

隨機化快排找第k大的值215

在未排序的陣列中找到第 k 個最大的元素。請注意,你需要找的是陣列排序後的第 k 個最大的元素,而不是第 k 個不同的元素。示例 1 輸入 3,2,1,5,6,4 和 k 2 輸出 5 示例 2 輸入 3,2,3,1,2,4,5,5,6 和 k 4 輸出 4 說明 你可以假設 k 總是有效的,且 1...