【數據結構】快排

什麼是快速排序

快速排序簡介

快速排序(英文名:Quicksort,有時候也叫做劃分交換排序)是一個高效的排序算法,由Tony Hoare在1959年發明(1961年公佈)。當情況良好時,它可以比主要競爭對手的歸併排序和堆排序快上大約兩三倍。這是一個分治算法,而且它就在原地排序

所謂原地排序,就是指在原來的數據區域內進行重排,就像插入排序一般。而歸併排序就不一樣,它需要額外的空間來進行歸併排序操作。爲了在線性時間與空間內歸併,它不能在線性時間內實現就地排序,原地排序對它來說並不足夠。而快速排序的優點就在於它是原地的,也就是說,它很節省內存

引用一張來自維基百科的能夠非常清晰表示快速排序的示意圖如下:


快速排序的分治思想

由於快速排序採用了分治算法,所以:

一、分解:本質上快速排序把數據劃分成幾份,所以快速排序通過選取一個關鍵數據,再根據它的大小,把原數組分成兩個子數組:第一個數組裏的數都比這個主元數據小或等於,而另一個數組裏的數都比這個主元數據要大或等於。

二、解決:用遞歸來處理兩個子數組的排序。 (也就是說,遞歸地求上面圖示中左半部分,以及遞歸地求上面圖示中右半部分。)

三、合併:因爲子數組都是原址排序,所以不需要合併操作,通過上面兩步後數組已經排好序了。

所以快速排序的主要思想是遞歸與劃分

如何劃分

當然最重要的是它的複雜度是線性的,也就是Θ(n)個劃分的子程序。

Partition(A,p,q)   // A[p,..q] 
1   x=A[p]   // pivot=A[p] 主元 
2   i=p 
3   for j=p+1 to q
4       do if A[j]<=x
5          then i=i+1 
6             exch A[i]<->A[j] 
7   exch A[p]<->A[i] 
8   return i // i pivot 

這就是劃分的僞代碼,基本的結構就是一個for循環語句,中間加上了一個if條件語句,它實現了對子數組A[p...q]的原址排序。

剛開始時i等於pj等於p+1。在這個循環中查找i下標的數據,如果它比x大,那就將其存放到“>=x”區域並將j加1後進行下一次循環。而如果它比x小,那就要做些動作來維持循環不變量了。將i的下標加1後將下標i對應的數據和下標j所對應的數據互換位置。然後再移動區域的界限並開始下一次循環。

那麼這個算法在n個數據下的運行時間大約是O(n),因爲它幾乎把每個數都比較了一遍,而每個步驟所需的時間都爲O(1)

上面這幅圖詳細的描述了Partition過程,每一行後也加了註釋。

將遞歸的思想作用於劃分上

有了上面這些準備工作,再加上分治的思想實現快速排序的僞代碼也是很簡單的。

Quicksort(A,p,q) 
1   if p<q 
2     then r=Partition(A,p,q)   
3          Quicksort(A,p,r-1) 
4          Quicksort(A,r+1,q) 

爲了排序一個數組A的全部元素,初始調用時Quicksort(A,1,A.length)

快速排序的算法分析

相信通過前面的諸多實踐,大家也發現了快速排序的運行時間依賴於Partition過程,也就是依賴於劃分是否平衡,而歸根結底這還是由於輸入的元素決定的。

如果劃分是平衡的,那麼快速排序算法性能就和歸併排序一樣。

如果劃分是不平衡的,那麼快速排序的性能就接近於插入排序。

怎樣是最壞的劃分

1)輸入的元素已經排序或逆向排序
2)每個劃分的一邊都沒有元素

也就是說當劃分產生的兩個子問題分別包含了n-1個元素和0個元素時,快速排序的最壞情況就發生了。

T(n)=T(0)+T(n1)+\Theta(n)=Θ(1)+T(n1)+Θ(n)=Θ(n1)+Θ(n)=Θ(n2)

這是一個等差級數,就和插入排序一樣。它並不比插入排序快,因爲當同樣是輸入元素已經逆向排好序時,插入算法的運行時間爲Θ(n)。但快速排序仍舊是一個優秀的算法,這是因爲在平均情況下它已經很高效。

我們爲最壞情況畫一個遞歸樹。

這是一課高度不平衡的遞歸樹,圖中左邊的那些T(0)的運行時間都爲Θ(1),而總共有n個。

所以算法的中運行時間爲:


T(n)=Θ(n)+Θ(n2)=Θ(n2)

最壞劃分的算法分析

通過上面的圖示我們知道了在最壞情況下快速排序的複雜度是Θ(n2),但以圖示的方式並不是一種嚴謹的證明方式,我們應該使用代入法來證明它。

當輸入規模爲n時,時間T(n)有如下遞歸式:


T(n)=max0rn1(T(r)+T(nr1))+Θ(n)


除去主元后,在Partition函數中生成的兩個子問題的規模的和爲n-1,所以r的規模纔是0到n-1。

假設T(n)cn2成立,其中c爲常數這個大家都知道的。於是上面的遞歸式爲:


T(n)max0rn1(cr2+c(nr1)2)+Θ(n)cmax0rn1(r2+(nr1)2)+Θ(n)


1)而r2+(nr1)2對於r的二階導數爲正,所以在區間0rn1的右端點取得最大值。


於是有max0rn1(r2+(nr1)2)(n1)2=n22n+1,所以對於T(n)有:


T(n)cn2c(2n1)+Θ(n)


最終因爲我們可以選擇一個足夠大的c,來使得c(2n1)大於Θ(n),所以有T(n)=O(n2)


2)r2+(nr1)2對於r的二階導數爲正,所以在區間0rn1的左端點取得最小值。


於是有max0rn1(r2+(nr1)2)(n1)2=n22n+1,所以對於T(n)有:



T(n)cn2c(2n1)+Θ(n)


同樣我們也可以選擇一個足夠小的c,來使得c(2n1)小於Θ(n),所以有T(n)=Ω(n2)


綜上這兩點得到T(n)=Θ(n2)

怎樣是最好的劃分

當Partition將數組分爲n/2n/2兩個部分時是最高效的。此時有:

T(n)=2T(n/2)+Θ(n)=Θ(nlgn)

怎樣是平衡的劃分

快速排序的平均運行時間更接近於其最好情況,而非最壞情況。

此處有一個經典的示例,將數組按19的比例進行劃分會怎樣呢?這種劃分看似很不平衡,但真的會因此而影響效率麼?

其中此時的遞歸式是:

T(n)=T(110n)+T(910n)+Θ(n)

這裏依舊通過遞歸樹來觀察一番。

因爲每次都減少十分之一,需要減多少次才能達到n呢,也恰好也是以10爲底對數的定義。所以左側的高度爲log10n了,相應的右側的高度爲log109n

所有那些葉子加在一起也只有Θ(n),所以有:

T(n)cnlog109n+Θ(n)

其實T(n)的下界也漸近爲nlgn,所以總時間爲:

T(n)=Θ(nlgn)

只要劃分是常數比例的,算法的運行時間總是O(nlgn)

隨機化快速排序

隨機算法的思想

在前面分析快速排序的平均情況性能時,是建立在輸入數據的所有排列都是等概率的條件下的,但在實際工程中往往不會總出現這種良好的情況。

【算法】3 由招聘問題看隨機算法中我們介紹了隨機算法,它使得對於所有的輸入都有着較好的期望性能,因此隨機化快速排序在有大量數據輸入的情況下是一種更好的排序算法。

以下是隨機化快速排序的好處:

1)其運行時間不依賴與輸入序列的順序

2)無需對輸入序列的分佈做任何假設

3)沒有 一種特別的輸入會引起最差的運行情況

4)最差的情況由隨機數產生器決定

隨機抽樣技術

現在我們來使用一種叫做隨機抽樣(random sampling)的隨機化技術,使用該技術就不再始終採用A[p]作爲主元,而是從A[p…q]中隨機選擇一個元素作爲主元。

爲了達到這一目的,首先將A[p]與從A[p...q]中隨機選出的一個元素交換。

通過對序列p...q的隨機抽樣,我們可以保證主元元素x=A[p]是等概率地從子數組的qp+1個元素中選取的。

因爲主元元素是隨機選擇的,我們可以期望在平均情況下對輸入數組的劃分是比較均衡的。所以對前面的兩份僞代碼做如下修改:

RANDOMIZED-PARTITION(A,p,q)
1   i=RANDOM(p,q)
2   exchange A[p] with A[i]
3   return PARTITION(A,p,q)

RANDOMIZED-QUICKSORT(A,p,q)
1   if p<q
2       r=RANDOMIZED-PARTITION(A,p,q)
3       RANDOMIZED-QUICKSORT(A,p,r-1)
4       RANDOMIZED-QUICKSORT(A,r+1,q)

有了隨機抽樣技術後再也不用擔心快速排序遇到最壞劃分的情況啦,所以說隨機化快速排序的期望運行時間是O(nlgn)



感謝您的訪問,希望對您有所幫助。 歡迎大家關注、收藏以及評論。


爲使本文得到斧正和提問,轉載請註明出處:
http://blog.csdn.net/nomasp



發佈了65 篇原創文章 · 獲贊 26 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章