快速排序- 维基百科,自由的百科全书
文章推薦指數: 80 %
快速排序(英語:Quicksort),又稱分区交換排序(partition-exchange sort),簡稱快排,一種排序算法,最早由東尼·霍爾提出。
在平均狀況下,排序 n {\displaystyle ...
快速排序
平均情況最優分治法和比較排序算法
語言
監視
編輯
此條目已列出參考文獻,但因為沒有文內引註而使來源仍然不明。
(2020年8月29日)請加上合適的文內引註來改善這篇條目。
快速排序(英語:Quicksort),又稱分割區交換排序(partition-exchangesort),簡稱快排,一種排序演算法,最早由東尼·霍爾提出。
在平均狀況下,排序
n
{\displaystylen}
個項目要
O
(
n
log
n
)
{\displaystyle\O(n\logn)}
(大O符號)次比較。
在最壞狀況下則需要
O
(
n
2
)
{\displaystyleO(n^{2})}
次比較,但這種狀況並不常見。
事實上,快速排序
Θ
(
n
log
n
)
{\displaystyle\Theta(n\logn)}
通常明顯比其他演算法更快,因為它的內部迴圈(innerloop)可以在大部分的架構上很有效率地達成。
快速排序使用快速排序法對一列數字進行排序的過程概況類別排序演算法資料結構不定複雜度平均時間複雜度
Θ
(
n
log
n
)
{\displaystyle\Theta(n\logn)}
最壞時間複雜度
Θ
(
n
2
)
{\displaystyle\Theta(n^{2})}
最佳時間複雜度
Θ
(
n
log
n
)
{\displaystyle\Theta(n\logn)}
空間複雜度根據實現的方式不同而不同最佳解有時是相關變數的定義
目次
1演算法
1.1原地(in-place)分割的版本
2優化的排序演算法
3正規分析
3.1亂數快速排序的期望複雜度
3.2平均複雜度
3.3空間複雜度
4選擇的關連性
5外部連結
6參考
7註腳
演算法編輯
快速排序使用分治法(Divideandconquer)策略來把一個序列(list)分為較小和較大的2個子序列,然後遞迴地排序兩個子序列。
步驟為:
挑選基準值:從數列中挑出一個元素,稱為「基準」(pivot),
分割:重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(與基準值相等的數可以到任何一邊)。
在這個分割結束之後,對基準值的排序就已經完成,
遞迴排序子序列:遞迴地將小於基準值元素的子序列和大於基準值元素的子序列排序。
遞迴到最底部的判斷條件是數列的大小是零或一,此時該數列顯然已經有序。
選取基準值有數種具體方法,此選取方法對排序的時間效能有決定性影響。
在簡單的虛擬碼中,此演算法可以被表示為:
functionquicksort(q)
{
varlistless,pivotList,greater
iflength(q)≤1
returnq
else
{
selectapivotvaluepivotfromq
foreachxinqexceptthepivotelement
{
ifx
最佳化的排序演算法編輯
快速排序是二元搜尋樹(二元搜尋樹)的一個空間最佳化版本。
不是循序地把資料項插入到一個明確的樹中,而是由快速排序組織這些資料項到一個由遞迴呼叫所隱含的樹中。
這兩個演算法完全地產生相同的比較次數,但是順序不同。
對於排序演算法的穩定性指標,原地分割版本的快速排序演算法是不穩定的。
其他變種是可以通過犧牲效能和空間來維護穩定性的。
快速排序的最直接競爭者是堆積排序(Heapsort)。
堆積排序通常比快速排序稍微慢,但是最壞情況的執行時間總是
O
(
n
log
n
)
{\displaystyleO(n\logn)}
。
快速排序是經常比較快,除了introsort變化版本外,仍然有最壞情況效能的機會。
如果事先知道堆積排序將會是需要使用的,那麼直接地使用堆積排序比等待introsort再切換到它還要快。
堆積排序也擁有重要的特點,僅使用固定額外的空間(堆積排序是原地排序),而即使是最佳的快速排序變化版本也需要
Θ
(
log
n
)
{\displaystyle\Theta(\logn)}
的空間。
然而,堆積排序需要有效率的隨機存取才能變成可行。
快速排序也與合併排序(Mergesort)競爭,這是另外一種遞迴排序演算法,但有壞情況
O
(
n
log
n
)
{\displaystyleO(n\logn)}
執行時間的優勢。
不像快速排序或堆積排序,合併排序是一個穩定排序,且可以輕易地被採用在連結串列(linkedlist)和儲存在慢速存取媒體上像是磁碟儲存或網路連接儲存的非常巨大數列。
儘管快速排序可以被重新改寫使用在鏈串列上,但是它通常會因為無法隨機存取而導致差的基準選擇。
合併排序的主要缺點,是在最佳情況下需要
Ω
(
n
)
{\displaystyle\Omega(n)}
額外的空間。
正規分析編輯
從一開始快速排序平均需要花費
O
(
n
log
n
)
{\displaystyleO(n\logn)}
時間的描述並不明顯。
但是不難觀察到的是分割運算,陣列的元素都會在每次迴圈中走訪過一次,使用
O
(
n
)
{\displaystyleO(n)}
的時間。
在使用結合(concatenation)的版本中,這項運算也是
O
(
n
)
{\displaystyleO(n)}
。
在最好的情況,每次我們執行一次分割,我們會把一個數列分為兩個幾近相等的片段。
這個意思就是每次遞迴呼叫處理一半大小的數列。
因此,在到達大小為一的數列前,我們只要作
log
n
{\displaystyle\logn}
次巢狀的呼叫。
這個意思就是呼叫樹的深度是
O
(
log
n
)
{\displaystyleO(\logn)}
。
但是在同一階層的兩個程式呼叫中,不會處理到原來數列的相同部份;因此,程式呼叫的每一階層總共全部僅需要
O
(
n
)
{\displaystyleO(n)}
的時間(每個呼叫有某些共同的額外耗費,但是因為在每一階層僅僅只有
O
(
n
)
{\displaystyleO(n)}
個呼叫,這些被歸納在
O
(
n
)
{\displaystyleO(n)}
係數中)。
結果是這個演算法僅需使用
O
(
n
log
n
)
{\displaystyleO(n\logn)}
時間。
另外一個方法是為
T
(
n
)
{\displaystyleT(n)}
設立一個遞迴關係式,也就是需要排序大小為
n
{\displaystylen}
的數列所需要的時間。
在最好的情況下,因為一個單獨的快速排序呼叫牽涉了
O
(
n
)
{\displaystyleO(n)}
的工作,加上對
n
/
2
{\displaystylen/2}
大小之數列的兩個遞迴呼叫,這個關係式可以是:
T
(
n
)
=
O
(
n
)
+
2
T
(
n
/
2
)
{\displaystyleT(n)=O(n)+2T(n/2)}
解決這種關係式型態的標準數學歸納法技巧告訴我們
T
(
n
)
=
O
(
n
log
n
)
{\displaystyleT(n)=O(n\logn)}
。
事實上,並不需要把數列如此精確地分割;即使如果每個基準值將元素分開為99%在一邊和1%在另一邊,呼叫的深度仍然限制在
100
log
n
{\displaystyle100\logn}
,所以全部執行時間依然是
O
(
n
log
n
)
{\displaystyleO(n\logn)}
。
然而,在最壞的情況是,兩子數列擁有大各為
1
{\displaystyle1}
和
n
−
1
{\displaystylen-1}
,且呼叫樹(calltree)變成為一個
n
{\displaystylen}
個巢狀(nested)呼叫的線性連串(chain)。
第
i
{\displaystylei}
次呼叫作了
O
(
n
−
i
)
{\displaystyleO(n-i)}
的工作量,且
∑
i
=
0
n
(
n
−
i
)
=
O
(
n
2
)
{\displaystyle\sum_{i=0}^{n}(n-i)=O(n^{2})}
遞迴關係式為:
T
(
n
)
=
O
(
n
)
+
T
(
1
)
+
T
(
n
−
1
)
=
O
(
n
)
+
T
(
n
−
1
)
{\displaystyleT(n)=O(n)+T(1)+T(n-1)=O(n)+T(n-1)}
這與插入排序和選擇排序有相同的關係式,以及它被解為
T
(
n
)
=
O
(
n
2
)
{\displaystyleT(n)=O(n^{2})}
。
亂數快速排序的期望複雜度編輯
亂數快速排序有一個值得注意的特性,在任意輸入資料的狀況下,它只需要
O
(
n
log
n
)
{\displaystyleO(n\logn)}
的期望時間。
是什麼讓隨機的基準變成一個好的選擇?
假設我們排序一個數列,然後把它分為四個部份。
在中央的兩個部份將會包含最好的基準值;他們的每一個至少都會比25%的元素大,且至少比25%的元素小。
如果我們可以一致地從這兩個中央的部份選出一個元素,在到達大小為1的數列前,我們可能最多僅需要把數列分割
2
log
2
n
{\displaystyle2\log_{2}n}
次,產生一個
O
(
n
log
n
)
{\displaystyleO(n\logn)}
演算法。
不幸地,亂數選擇只有一半的時間會從中間的部份選擇。
出人意外的事實是這樣就已經足夠好了。
想像你正在翻轉一枚硬幣,一直翻轉一直到有
k
{\displaystylek}
次人頭那面出現。
儘管這需要很長的時間,平均來說只需要
2
k
{\displaystyle2k}
次翻動。
且在
k
{\displaystylek}
次翻動中得不到
k
{\displaystylek}
次人頭那面的機會,是像天文數字一樣的非常小。
藉由同樣的論證,快速排序的遞迴平均只要
2
(
2
log
2
n
)
{\displaystyle2(2\log_{2}n)}
的呼叫深度就會終止。
但是如果它的平均呼叫深度是
O
(
log
n
)
{\displaystyleO(\logn)}
且每一階的呼叫樹狀過程最多有
n
{\displaystylen}
個元素,則全部完成的工作量平均上是乘積,也就是
O
(
n
log
n
)
{\displaystyleO(n\logn)}
。
平均複雜度編輯
即使如果我們無法隨機地選擇基準數值,對於它的輸入之所有可能排列,快速排序仍然只需要
O
(
n
log
n
)
{\displaystyleO(n\logn)}
時間。
因為這個平均是簡單地將輸入之所有可能排列的時間加總起來,除以
n
{\displaystylen}
這個因數,相當於從輸入之中選擇一個隨機的排列。
當我們這樣作,基準值本質上就是隨機的,導致這個演算法與亂數快速排序有一樣的執行時間。
更精確地說,對於輸入順序之所有排列情形的平均比較次數,可以藉由解出這個遞迴關係式可以精確地算出來。
C
(
n
)
=
n
−
1
+
1
n
∑
i
=
0
n
−
1
(
C
(
i
)
+
C
(
n
−
i
−
1
)
)
=
2
n
ln
n
=
1.39
n
log
2
n
{\displaystyleC(n)=n-1+{\frac{1}{n}}\sum_{i=0}^{n-1}(C(i)+C(n-i-1))=2n\lnn=1.39n\log_{2}n}
。
在這裡,
n
−
1
{\displaystylen-1}
是分割所使用的比較次數。
因為基準值是相當均勻地落在排列好的數列次序之任何地方,總和就是所有可能分割的平均。
這個意思是,平均上快速排序比理想的比較次數,也就是最好情況下,只大約比較糟39%。
這意味著,它比最壞情況較接近最好情況。
這個快速的平均執行時間,是快速排序比其他排序演算法有實際的優勢之另一個原因。
空間複雜度編輯
被快速排序所使用的空間,依照使用的版本而定。
使用原地(in-place)分割的快速排序版本,在任何遞迴呼叫前,僅會使用固定的額外空間。
然而,如果需要產生
O
(
log
n
)
{\displaystyleO(\logn)}
巢狀遞迴呼叫,它需要在他們每一個儲存一個固定數量的資訊。
因為最好的情況最多需要
O
(
log
n
)
{\displaystyleO(\logn)}
次的巢狀遞迴呼叫,所以它需要
O
(
log
n
)
{\displaystyleO(\logn)}
的空間。
最壞情況下需要
O
(
n
)
{\displaystyleO(n)}
次巢狀遞迴呼叫,因此需要
O
(
n
)
{\displaystyleO(n)}
的空間。
然而我們在這裡省略一些小的細節。
如果我們考慮排序任意很長的數列,我們必須要記住我們的變數像是left和right,不再被認為是佔據固定的空間;也需要
O
(
log
n
)
{\displaystyleO(\logn)}
對原來一個
n
{\displaystylen}
項的數列作索引。
因為我們在每一個堆疊框架中都有像這些的變數,實際上快速排序在最好跟平均的情況下,需要
O
(
log
2
n
)
{\displaystyleO(\log_{2}n)}
空間的位元數,以及最壞情況下
O
(
n
log
n
)
{\displaystyleO(n\logn)}
的空間。
然而,這並不會太可怕,因為如果一個數列大部份都是不同的元素,那麼數列本身也會佔據
O
(
n
log
n
)
{\displaystyleO(n\logn)}
的空間位元組。
非原地版本的快速排序,在它的任何遞迴呼叫前需要使用
O
(
n
)
{\displaystyleO(n)}
空間。
在最好的情況下,它的空間仍然限制在
O
(
n
)
{\displaystyleO(n)}
,因為遞迴的每一階中,使用與上一次所使用最多空間的一半,且
∑
i
=
0
∞
n
2
i
=
2
n
{\displaystyle\sum_{i=0}^{\infty}{\frac{n}{2^{i}}}=2n}
。
它的最壞情況是很恐怖的,需要
∑
i
=
0
n
(
n
−
i
+
1
)
=
Θ
(
n
2
)
{\displaystyle\sum_{i=0}^{n}(n-i+1)=\Theta(n^{2})}
空間,遠比數列本身還多。
如果這些數列元素本身自己不是固定的大小,這個問題會變得更大;舉例來說,如果數列元素的大部份都是不同的,每一個將會需要大約
O
(
log
n
)
{\displaystyleO(\logn)}
為原來儲存,導致最好情況是
O
(
n
log
n
)
{\displaystyleO(n\logn)}
和最壞情況是
O
(
n
2
log
n
)
{\displaystyleO(n^{2}\logn)}
的空間需求。
選擇的關連性編輯
選擇演算法(selectionalgorithm)可以選取一個數列的第
k
{\displaystylek}
個最小值;一般而言這是比排序還簡單的問題。
一個簡單但是有效率的選擇演算法與快速排序的作法相當類似,除了對兩個子數列都作遞迴呼叫外,它僅僅針對包含想要的元素之子數列作單一的結尾遞迴(tailrecursive)呼叫。
這個小改變降低了平均複雜度到線性或是
Θ
(
n
)
{\displaystyle\Theta(n)}
時間,且讓它成為一個原地演算法。
這個演算法的一種變化版本,可以讓最壞情況下降為
O
(
n
)
{\displaystyleO(n)}
(參考選擇演算法來得到更多資訊)。
相反地,一旦我們知道一個最壞情況的
O
(
n
)
{\displaystyleO(n)}
選擇演算法是可以利用的,我們在快速排序的每一步可以用它來找到理想的基準(中位數),得到一種最壞情況下
O
(
n
log
n
)
{\displaystyleO(n\logn)}
執行時間的變化版本。
然而在實際的實作中,這種版本一般而言相當慢。
外部連結編輯
維基教科書中的相關電子教學:演算法實現/排序/快速排序Interactivequicksort(頁面存檔備份,存於網際網路檔案館)
multidimensionalquicksortinjava參考編輯
Hoare,C.A.R."Partition:Algorithm63,""Quicksort:Algorithm64,"and"Find:Algorithm65."Comm.ACM4,321-322,1961
R.Sedgewick.Implementingquicksortprograms,CommunicationsoftheACM,21(10):847-857,1978.
DavidMusser.IntrospectiveSortingandSelectionAlgorithms,SoftwarePracticeandExperiencevol27,number8,pages983-993,1997
A.LaMarcaandR.E.Ladner."TheInfluenceofCachesonthePerformanceofSorting."ProceedingsoftheEighthAnnualACM-SIAMSymposiumonDiscreteAlgorithms,1997.pp. 370–379.
FaronMoller.AnalysisofQuicksort.CS332:DesigningAlgorithms.DepartmentofComputerScience,UniversityofWalesSwansea.
StevenSkiena.Lecture5-quicksort.CSE373/548-AnalysisofAlgorithms.DepartmentofComputerScience.SUNYStonyBrook.註腳編輯
1.DavidM.W.Powers.ParallelizedQuicksortwithOptimalSpeedup(頁面存檔備份,存於網際網路檔案館).ProceedingsofInternationalConferenceonParallelComputingTechnologies.Novosibirsk.1991.
取自「https://zh.wikipedia.org/w/index.php?title=快速排序&oldid=71758251」
延伸文章資訊
- 1快速排序Quicksort
Quicksort 是一個非常熱門且應用廣泛的排序法,相對簡單的實作就可達到O(nlogn) 的平均時間複雜度。雖然最差時間複雜度與bubble sort 同為O(n2),但這種情形非常少見。
- 2QuickSort (With Code in Python/C++/Java/C) - Programiz
Quicksort is an algorithm based on divide and conquer approach in which an array is split into su...
- 3【TBS Learning】演算法-六種排序法之五:快速排序法(quick sort)
- 4快速排序- 维基百科,自由的百科全书
快速排序(英語:Quicksort),又稱分区交換排序(partition-exchange sort),簡稱快排,一種排序算法,最早由東尼·霍爾提出。在平均狀況下,排序 n {\display...
- 5Comparison Sort: Quick Sort(快速排序法)
Quick Sort, Merge Sort, Heap Sort, Insertion Sort, Selection Sort. best case, NlogN, NlogN, NlogN...