對快速排序的簡單分析


開篇

在實際的過程中,總需要對一些數據進行排序,在眾多的排序算法中,快速排序是較為常用的排序算法之一。而網上對於快速排序的中文資料還不是很全。寫這篇博文主要記錄一些自己對於快速排序的了解,以及對快速排序的性能的分析。我將在這里記錄下我對快速排序的認識和學習過程 ,用盡可能簡單明了的敘述來闡述我的理解。

快速排序基於算法中很重要的思想是 分治。所以會先介紹一下分治思想,然后對算法原理進行介紹,接着會分析算法的性能並對算法作進一步的討論。

 注:為了便於說明問題,本博文中會用到部分《introduction to algorithm》中的圖片。

關鍵詞:快速排序、分治、遞歸

“大事化小”——從分治說起

分治? 

分治法是算法中常用的策略之一,很多算法都是基於分治法的,今天要說的快速排序也一樣。為了能更好的理解快速排序,先簡單的介紹一下分治法。

顧名思義,分治,可理解為分而治之。就是把原問題(遞歸地)分解為多個子問題(一般是和原問題本質相同的問題,只是規模上的縮小,如果現在不能理解請看后文解釋),解決這些子問題,合並其結果,獲得原問題的解。

簡單的說就是“大事化小”    把復雜的問題分為多個簡單問題,解決了這些簡單問題,原問題也就隨之解決了。

如何分治

從上面的分析中可知道,用分治的思想解決問題的步驟大致為:

分解(Divide):將原問題分為一系列子問題

解決(Conquer):遞歸的解決子問題。如果子問題足夠小,直接解決子問題

合並(Combine):將子問題的結果合並為原問題的

借助下圖,可更清晰的了解分治的思想:

如上圖所示,原問題是規模為 n 的問題,在樹的第一層,把問題分為規模為n/2的兩個子問題,如果解決了這兩個子問題,把它們合並就能得到原問題的解。

現在來看其中的一個子問題,為了解決他們,又把它分為兩個規模更小的問題n/4。解決了規模為n/4的問題,合並之就能得到規模 n/2 的問題的解。

按照上面的思想,把原問題遞歸的分解為規模小的問題,然后合並之就能得到原問題的解。

到現在對分治的思想應該有了一定的認識,其實分治思想可謂博大精深,不是三言兩語能討論清楚的,這里這說明一個基本思想,就不做深入討論了。

快速排序原理

基本思想:

上文已經說過快速排序是基於分治思想的,把問題的規模遞歸的變小,然后依次解決子問題,自后得到原問題的解。

既然是基於分治思想,那么快速排序步驟也和分治一樣:

我們假設原問題是要對數組 A[p,r] 中的數據進行排序

分解(Divide):

將數組 A[p,-------,r] 分為兩個數組 A[p,....,q-1 ] 和 A[q+1,.....r]  使得 數組 A[p,....,q-1 ]中的每一個數都小於q  ,數組 A[p,....,q-1 ]中的每一個數都大於q。

其實這步的關鍵就是找到那個 q 然后遍歷數組,把小於 q 的元素放在 q 的左邊,大於q 的元素放在右邊。這樣就使得 q 左邊的元素都小於 q  右邊的元素。

當問題被分解的足夠小,當 q 左邊只有一個元素 a ,q 右邊也只有一個元素 b 的時候,那么  a  q   b 就是一個有序數組,其實這也就完成了一次排序。

解決(conquer):

(遞歸調用快速排序),對數組 A[p,....,q-1 ] 和 A[q+1,.....r]  進行排序。對於其中的一個數組將被分為更小的數組,直到數組內數據有序。

隨着問題規模的減小,數組內的元素也在減小,當數組內元素只有3 個的時候,它的下一次分解會產生兩個規模為 1 的問題,這也就是上面說的“數組內部有序”的狀態了。

下面是一個圖示:

假設問題一直被分解,直到上圖中數組有三個元素的狀態 ,這個狀態再分解就得到箭頭下方所指的狀態,可以發現,這個狀態已經是有序的了,直接合並,就能得到有序序列。

合並(combine):

如上文所說,兩個數組都是經過排序的(其實每個數組內只有一個元素了,所以也不存在什么排序),所以直接合並就能得到有序的數組。

 

算法說明

算法

下面是快速排序的算法說明:

 

快速排序的函數是"QUICKSORT()"該函數有三個參數,

第一個參數A 表示要排序的數組,也就是給該函數傳入要排序的數組的指針。

第二個參數p 和 第三個參數 r 標記出了要排序的數組的范圍,即:這函數將數組A 的第p個到第 r 個參數排序。

下面是對這個算法的分析:

算法的第1行判斷要排序的數組是范圍是否合法,p 表示的是開始的位置, r表示的是結束的位置,所以只有p<r 才能進行排序。

第2 行:其實就是一個問題分解的過程,從數組中選一個元素q(可能是任意選擇的,也可能存在其他的選擇方式);

然后將數組A中的元素分為兩部分:小於q的部分[p....q-1]放在q的左邊,和大於q的部分[q+1....r]放在q 的右邊。至此,原來要排序的數組A[p...r]被分為了兩部分。

只要按照上面所做的,再對這兩個新產生是數組進行排序就行了。也就是第3 和第4行所做的事情。

分治思想的體現:

從中也可以看出分治的思想,算法中的第2行通過q 把原問題分解為兩個規模較小的問題,注意:只是規模縮小了,問題的本質並沒有改變,對於被縮小后的問題,還是要進行排序。第3 、4 用同樣的方法來處理問題。因為問題的本質沒變,只是規模的縮小,所以還是可以調用解原問題的那個函數,只要修改參數就可以了。這時候我們就能更好的理解函數"QUICKSORT"了,它有三個參數,后面的兩個參數正是用來控制問題規模的。可能有人已經看出來了,這里還體現出遞歸的思想:在解決的過程中調用自身。當然了 ,對於遞歸這里就不做深入討論了。

關鍵部分:

在上面的算法中,其實最關鍵的還是第2 行的那個Partition()。正是這個函數,將問題分解成了問題本質不變而規模變小的子問題,這個函數的實現也是這個算法的關鍵。

基本思路:

Partition(A,p,r)的目的是將從數組A[p....r]中選一個數q,將小於q的元素放在q左邊,大於q的放在右邊。可以先自己思考 一下這個算法能怎樣實現。

一種簡單的想法是:申請一個大小為(r-q)的空間B[  ],遍歷數組A[p...r],將每一元素和q比較,如果小於q 就從左邊放入新申請的空間中,如果大於,就從右邊放入。

最后把A[p...r]中的內容用b[  ]中的內容替換。當然,這是最直觀的思維,這樣做明顯的空間和時間復雜度都不好。所以這不是快速排序中所采用的策略。

下面是快速排序所使用的Partition(A,p,r)的實現:

我的建議是:最好自己先分析一下這個算法,也很值得分析。我覺得它對空間和時間的處理真的很妙。畫一個圖會對分析很有幫助。

下面對這個函數的實現做一些簡單分析:

第1行,函數選擇x=A[r]來作為分界點,也就是上面所討論的q。通過它把數組分為兩部分。

第2行,定義了變量i,i  是一個維護“小於區”的指針。i 左邊的元素都是小於分界點x 的元素。每當發現一個小於x的元素,就把它放在i 的后面,同時 i++;

第3行,for 循環並定義變量 j ,j 遍歷整個數組,並和分界點x 進行比較(第4行)如果元素A[j]<=x,那么就把這個元素加入到小於分界點x的區域。同時i ++,

具體的實現就是5、6行的功能。

第7行,已經完成遍歷,小於分界點x 的元素都在i 左邊的區域中,右邊的區域都是大於x 的,所以只要將分界點元素加入到他們中間即可。

實例是學習知識的最好途徑!

本例將描述該算法對一個包含8個 元素的數組的操作過程。具體的操作過程如下圖所示,函數中的變量在途中都已標出。

可結合算法和上文的算法分析來看這個圖,思路就會變得清晰了。

 

 

算法性能分析

通過上面的算法分析已經知道,如果能“盡快地”把原問題分為規模小的問題(可直接求解的問題),那么它的效率是比較好的。如果每次划分都能平均的將規模縮小一半,那么這種划分就是能最快到達目的的。而這種划分的結果直接和分界點 q 相關,如果每次划分時的q 選的足夠好,也就是小於q 的元素個數等於 大於 q 的元素個數,那么這將是最好的情況。

當然現實中的情況不可能是這樣,因為q 的選擇往往是隨機的。而且如果專門為選擇一個合適的 q 又用一個函數來實現,那么算法的效率將得不到保障。

總結下上面所說的就是:快速排序的運行時間與划分是否對稱有關。

最壞情況:

最壞情況也就是要划分最多次數。只要每次划分都把規模為n的問題分解為 n-1 和 1 。這種情況每一次的划分都出現這種極不對稱的划分,它的效率將是最低的。

假設對規模為n 的問題的划分代價為f(n).

那么,對於規模為n 的問題的時間為:T(n)=T(n-1)+T(1)+f(n)。

T(1): 數組中只有一個元素,已經是最小,不用繼續分解規模,所以划分時間f(1)=0;

所以 T(n)=T(n-1)+f(n)

這樣看來, 如果將每一層的遞歸代價加起來,每分解一次,其時間復雜度為f(n*n)   (n的平方)

如果每一層的划分都是極不對稱的,那么算法的運行時間就是:f(n*n) 。

最佳情況:

上文中已經說過最佳情況,對於規模為n 的問題,最佳情況分為兩個規模為n/2 的問題。表達其運行時間的遞歸式為:

T(n)<=2T(n/2)+f(n)

該遞歸式的解為:T(n)=O(n lg n).

划分的兩端都是對稱的,所以從漸進意義上看,算法運行的就更快了。

平衡的划分:

最佳情況和最壞情況都是實際情況中的兩個極端,在實際中比較少見,所以這里討論一下兩者的折中。這里假設每次划分的過程總是產生9:1 的划分,應該比較“接近”實際情況了。

這時,快速排序的時間遞歸式為:

T(n)<=T(9n/10)+T(n/10)+cn

這里,我們顯示的寫出了f(n)中的常數c。下圖顯示了這個遞歸過程的遞歸樹:

請注意這個樹的每一層代價都是cn ,直到圖中的倒數第三行未知,在此之前各層的代價至多為cn,后面的代價總是小於cn,

所以才有T(n)<=T(9n/10)+T(n/10)+cn。

這種情況下,快排的復雜度總為O(n lg n).從漸進意義上來說,這和平均划分的效果是一樣的。

小結

 關於快速排序的具體算法是現在網上有很多,這里就不寫出來了,關鍵的是掌握其中的思想。

即:遞歸、分治。

 

參看資料:算法導論

如有轉載請注明出處:http://www.cnblogs.com/yanlingyin/

一條魚、尹雁鈴@ 博客園 2012-4-16

 E-mail:yanlingyin@yeah.net


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM