快速排序: 平均情況最優分治法和比較排序算法

快速排序(英語:Quicksort),又稱分区交換排序(partition-exchange sort),是一種排序演算法,最早由東尼·霍爾提出。在平均狀況下,排序 n 個項目要   O ( n log ⁡ n ) (大O符号)次比較。在最壞狀況下則需要 O ( n 2 ) )} 次比較,但這種狀況並不常見。事實上,快速排序 Θ ( n log ⁡ n ) 通常明顯比其他演算法更快,因為它的內部循环可以在大部分的架構上很有效率地達成。

快速排序
快速排序: 演算法, 优化的排序演算法, 正規分析
使用快速排序法對一列數字進行排序的過程
概况
類別排序算法
資料結構不定
复杂度
平均時間複雜度
最坏时间复杂度
最优时间复杂度
空間複雜度根據實現的方式不同而不同
最佳解有时是
相关变量的定义

演算法

快速排序使用分治法策略來把一個序列分為较小和较大的2个子序列,然后递归地排序两个子序列。

步驟為:

  1. 挑选基准值:從數列中挑出一個元素,稱為「基準」(pivot),
  2. 分割:重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(与基准值相等的數可以到任何一邊)。在這個分割結束之後,对基准值的排序就已经完成,
  3. 递归排序子序列:递归地将小於基准值元素的子序列和大於基准值元素的子序列排序。

递归到最底部的判断条件是數列的大小是零或一,此时该数列显然已经有序。

选取基准值有数种具体方法,此选取方法对排序的时间性能有决定性影响。

在简单的伪代码中,此算法可以被表示为:

 function quicksort(q)  {      var list less, pivotList, greater      if length(q) ≤ 1           return q      else       {          select a pivot value pivot from q          for each x in q except the pivot element          {              if x < pivot then add x to less              if x ≥ pivot then add x to greater          }          add pivot to pivotList          return concatenate(quicksort(less), pivotList, quicksort(greater))      }  } 

原地(in-place)分割的版本

快速排序: 演算法, 优化的排序演算法, 正規分析 
原地(In-place)分割版本的快速排序。

上面簡單版本的缺點是,它需要快速排序: 演算法, 优化的排序演算法, 正規分析 的額外儲存空間,也就跟归并排序一樣不好。額外需要的記憶體空間配置,在實際上的實作,也會極度影響速度和快取的效能。有一個比較複雜使用原地(in-place)分割算法的版本,且在好的基準選擇上,平均可以達到快速排序: 演算法, 优化的排序演算法, 正規分析 空間的使用複雜度。

 function partition(a, left, right, pivotIndex)  {      pivotValue = a[pivotIndex]      swap(a[pivotIndex], a[right]) // 把pivot移到結尾      storeIndex = left      for i from left to right-1      {          if a[i] < pivotValue           {              swap(a[storeIndex], a[i])              storeIndex = storeIndex + 1           }      }      swap(a[right], a[storeIndex]) // 把pivot移到它最後的地方      return storeIndex  } 

這是原地分割演算法,它分割了標示為"左邊(left)"和"右邊(right)"的序列部份,藉由移動小於a[pivotIndex]的所有元素到子序列的開頭,留下所有大於或等於的元素接在他們後面。在這個過程它也為基準元素找尋最後擺放的位置,也就是它回傳的值。它暫時地把基準元素移到子序列的結尾,而不會被前述方式影響到。由於演算法只使用交換,因此最後的數列與原先的數列擁有一樣的元素。要注意的是,一個元素在到達它的最後位置前,可能會被交換很多次。

一旦我們有了這個分割演算法,要寫快速排列本身就很容易:

 procedure quicksort(a, left, right)      if right > left          select a pivot value a[pivotIndex]          pivotNewIndex := partition(a, left, right, pivotIndex)          quicksort(a, left, pivotNewIndex-1)          quicksort(a, pivotNewIndex+1, right) 

這個版本經常會被使用在命令式語言中,像是C語言

优化的排序演算法

快速排序是二叉查找树(二元搜尋樹)的一個空間最佳化版本。不是循序地把数据项插入到一個明確的樹中,而是由快速排序組織這些数据項到一個由递归调用所隐含的樹中。這兩個演算法完全地產生相同的比較次數,但是順序不同。对于排序算法的稳定性指标,原地分割版本的快速排序算法是不稳定的。其他变种是可以通过牺牲性能和空间来维护稳定性的。

快速排序的最直接競爭者是堆排序。堆排序通常比快速排序稍微慢,但是最壞情況的執行時間總是快速排序: 演算法, 优化的排序演算法, 正規分析 。快速排序是經常比較快,除了introsort變化版本外,仍然有最壞情況效能的機會。如果事先知道堆排序將會是需要使用的,那麼直接地使用堆排序比等待introsort再切換到它還要快。堆排序也擁有重要的特點,僅使用固定額外的空間(堆排序是原地排序),而即使是最佳的快速排序變化版本也需要快速排序: 演算法, 优化的排序演算法, 正規分析 的空間。然而,堆排序需要有效率的隨機存取才能變成可行。

快速排序也與归并排序競爭,這是另外一種遞迴排序算法,但有壞情況快速排序: 演算法, 优化的排序演算法, 正規分析 執行時間的優勢。不像快速排序或堆排序,归并排序是一個穩定排序,且可以輕易地被採用在链表和儲存在慢速存取媒體上像是磁碟儲存網路連接儲存的非常巨大數列。儘管快速排序可以被重新改寫使用在链串列上,但是它通常會因為無法隨機存取而導致差的基準選擇。归并排序的主要缺點,是在最佳情況下需要快速排序: 演算法, 优化的排序演算法, 正規分析 額外的空間。

正規分析

從一開始快速排序平均需要花費快速排序: 演算法, 优化的排序演算法, 正規分析 時間的描述並不明顯。但是不難觀察到的是分割運算,陣列的元素都會在每次迴圈中走訪過一次,使用快速排序: 演算法, 优化的排序演算法, 正規分析 的時間。在使用結合(concatenation)的版本中,這項運算也是快速排序: 演算法, 优化的排序演算法, 正規分析 

在最好的情況,每次我們執行一次分割,我們會把一個數列分為兩個幾近相等的片段。這個意思就是每次遞迴调用處理一半大小的數列。因此,在到達大小為一的數列前,我們只要作快速排序: 演算法, 优化的排序演算法, 正規分析 次巢狀的调用。這個意思就是调用樹的深度是快速排序: 演算法, 优化的排序演算法, 正規分析 。但是在同一階層的兩個程序调用中,不會處理到原來數列的相同部份;因此,程序调用的每一階層總共全部僅需要快速排序: 演算法, 优化的排序演算法, 正規分析 的時間(每個调用有某些共同的額外耗費,但是因為在每一階層僅僅只有快速排序: 演算法, 优化的排序演算法, 正規分析 個调用,這些被歸納在快速排序: 演算法, 优化的排序演算法, 正規分析 係數中)。結果是這個演算法僅需使用快速排序: 演算法, 优化的排序演算法, 正規分析 時間。

另外一個方法是為快速排序: 演算法, 优化的排序演算法, 正規分析 設立一個遞迴關係式,也就是需要排序大小為快速排序: 演算法, 优化的排序演算法, 正規分析 的數列所需要的時間。在最好的情況下,因為一個單獨的快速排序调用牽涉了快速排序: 演算法, 优化的排序演算法, 正規分析 的工作,加上對快速排序: 演算法, 优化的排序演算法, 正規分析 大小之數列的兩個遞迴调用,這個關係式可以是:

快速排序: 演算法, 优化的排序演算法, 正規分析 

解決這種關係式型態的標準数学归纳法技巧告訴我們快速排序: 演算法, 优化的排序演算法, 正規分析 

事實上,並不需要把數列如此精確地分割;即使如果每個基準值將元素分開為99%在一邊和1%在另一邊,调用的深度仍然限制在快速排序: 演算法, 优化的排序演算法, 正規分析 ,所以全部執行時間依然是快速排序: 演算法, 优化的排序演算法, 正規分析 

然而,在最壞的情況是,兩子數列擁有大各為快速排序: 演算法, 优化的排序演算法, 正規分析 快速排序: 演算法, 优化的排序演算法, 正規分析 ,且调用樹(call tree)變成為一個快速排序: 演算法, 优化的排序演算法, 正規分析 個巢狀(nested)呼叫的線性連串(chain)。第快速排序: 演算法, 优化的排序演算法, 正規分析  次呼叫作了快速排序: 演算法, 优化的排序演算法, 正規分析 的工作量,且快速排序: 演算法, 优化的排序演算法, 正規分析 遞迴關係式為:

快速排序: 演算法, 优化的排序演算法, 正規分析 

這與插入排序选择排序有相同的關係式,以及它被解為快速排序: 演算法, 优化的排序演算法, 正規分析 

亂數快速排序的期望複雜度

亂數快速排序有一個值得注意的特性,在任意輸入資料的狀況下,它只需要快速排序: 演算法, 优化的排序演算法, 正規分析 的期望時間。是什麼讓隨機的基準變成一個好的選擇?

假設我們排序一個數列,然後把它分為四個部份。在中央的兩個部份將會包含最好的基準值;他們的每一個至少都會比25%的元素大,且至少比25%的元素小。如果我們可以一致地從這兩個中央的部份選出一個元素,在到達大小為1的數列前,我們可能最多僅需要把數列分割快速排序: 演算法, 优化的排序演算法, 正規分析 次,產生一個快速排序: 演算法, 优化的排序演算法, 正規分析 演算法。

不幸地,亂數選擇只有一半的時間會從中間的部份選擇。出人意外的事實是這樣就已經足夠好了。想像你正在翻轉一枚硬幣,一直翻轉一直到有快速排序: 演算法, 优化的排序演算法, 正規分析 次人頭那面出現。儘管這需要很長的時間,平均來說只需要快速排序: 演算法, 优化的排序演算法, 正規分析 次翻動。且在快速排序: 演算法, 优化的排序演算法, 正規分析 次翻動中得不到快速排序: 演算法, 优化的排序演算法, 正規分析 次人頭那面的機會,是像天文數字一樣的非常小。藉由同樣的論證,快速排序的遞迴平均只要快速排序: 演算法, 优化的排序演算法, 正規分析 的呼叫深度就會終止。但是如果它的平均呼叫深度是快速排序: 演算法, 优化的排序演算法, 正規分析 且每一階的呼叫樹狀過程最多有快速排序: 演算法, 优化的排序演算法, 正規分析 個元素,則全部完成的工作量平均上是乘積,也就是快速排序: 演算法, 优化的排序演算法, 正規分析 

平均複雜度

即使如果我們無法隨機地選擇基準數值,對於它的輸入之所有可能排列,快速排序仍然只需要快速排序: 演算法, 优化的排序演算法, 正規分析 時間。因為這個平均是簡單地將輸入之所有可能排列的時間加總起來,除以快速排序: 演算法, 优化的排序演算法, 正規分析 這個因數,相當於從輸入之中選擇一個隨機的排列。當我們這樣作,基準值本質上就是隨機的,導致這個演算法與亂數快速排序有一樣的執行時間。

更精確地說,對於輸入順序之所有排列情形的平均比較次數,可以藉由解出這個遞迴關係式可以精確地算出來。

快速排序: 演算法, 优化的排序演算法, 正規分析 

在這裡,快速排序: 演算法, 优化的排序演算法, 正規分析 是分割所使用的比較次數。因為基準值是相當均勻地落在排列好的數列次序之任何地方,總和就是所有可能分割的平均。

這個意思是,平均上快速排序比理想的比較次數,也就是最好情況下,只大約比較糟39%。這意味著,它比最壞情況較接近最好情況。這個快速的平均執行時間,是快速排序比其他排序演算法有實際的優勢之另一個原因。

空間複雜度

被快速排序所使用的空間,依照使用的版本而定。使用原地(in-place)分割的快速排序版本,在任何遞迴呼叫前,僅會使用固定的額外空間。然而,如果需要產生快速排序: 演算法, 优化的排序演算法, 正規分析 巢狀遞迴呼叫,它需要在他們每一個儲存一個固定數量的資訊。因為最好的情況最多需要快速排序: 演算法, 优化的排序演算法, 正規分析 次的巢狀遞迴呼叫,所以它需要快速排序: 演算法, 优化的排序演算法, 正規分析 的空間。最壞情況下需要快速排序: 演算法, 优化的排序演算法, 正規分析 次巢狀遞迴呼叫,因此需要快速排序: 演算法, 优化的排序演算法, 正規分析 的空間。

然而我們在這裡省略一些小的細節。如果我們考慮排序任意很長的數列,我們必須要記住我們的變數像是leftright,不再被認為是佔據固定的空間;也需要快速排序: 演算法, 优化的排序演算法, 正規分析 對原來一個快速排序: 演算法, 优化的排序演算法, 正規分析 項的數列作索引。因為我們在每一個堆疊框架中都有像這些的變數,實際上快速排序在最好跟平均的情況下,需要快速排序: 演算法, 优化的排序演算法, 正規分析 空間的位元數,以及最壞情況下快速排序: 演算法, 优化的排序演算法, 正規分析 的空間。然而,這並不會太可怕,因為如果一個數列大部份都是不同的元素,那麼數列本身也會佔據快速排序: 演算法, 优化的排序演算法, 正規分析 的空間位元組。

非原地版本的快速排序,在它的任何遞迴呼叫前需要使用快速排序: 演算法, 优化的排序演算法, 正規分析 空間。在最好的情況下,它的空間仍然限制在快速排序: 演算法, 优化的排序演算法, 正規分析 ,因為遞迴的每一階中,使用與上一次所使用最多空間的一半,且

    快速排序: 演算法, 优化的排序演算法, 正規分析 

它的最壞情況是很恐怖的,需要

    快速排序: 演算法, 优化的排序演算法, 正規分析 

空間,遠比數列本身還多。如果這些數列元素本身自己不是固定的大小,這個問題會變得更大;舉例來說,如果數列元素的大部份都是不同的,每一個將會需要大約快速排序: 演算法, 优化的排序演算法, 正規分析 為原來儲存,導致最好情況是快速排序: 演算法, 优化的排序演算法, 正規分析 和最壞情況是快速排序: 演算法, 优化的排序演算法, 正規分析 的空間需求。

選擇的關連性

選擇算法(selection algorithm)可以選取一個數列的第快速排序: 演算法, 优化的排序演算法, 正規分析 個最小值;一般而言這是比排序還簡單的問題。一個簡單但是有效率的選擇算法與快速排序的作法相當類似,除了對兩個子數列都作遞迴呼叫外,它僅僅針對包含想要的元素之子數列作單一的結尾遞迴(tail recursive)呼叫。這個小改變降低了平均複雜度到線性或是快速排序: 演算法, 优化的排序演算法, 正規分析 時間,且讓它成為一個原地算法。這個算法的一種變化版本,可以讓最壞情況下降為快速排序: 演算法, 优化的排序演算法, 正規分析 (參考選擇算法來得到更多資訊)。

相反地,一旦我們知道一個最壞情況的快速排序: 演算法, 优化的排序演算法, 正規分析 選擇算法是可以利用的,我們在快速排序的每一步可以用它來找到理想的基準(中位數),得到一種最坏情況下快速排序: 演算法, 优化的排序演算法, 正規分析 執行時間的變化版本。然而在實際的實作中,這種版本一般而言相當慢。

實作範例

Julia (程式語言)

# Julia Sample : QuickSort  function QuickSort(A) i,j = 1,length(A)  if j > i rndv = A[rand(i:j)]     Left,Right = i,j     while Left <= Right while A[Left] < rndv Left += 1 end  while A[Right] > rndv Right -= 1 end  if Left <= Right A[Left], A[Right] = A[Right], A[Left]                 Left += 1                 Right -= 1             end end quicksort!(A,i,Right) quicksort!(A,Left,j) end return A end  # Main Code A = [16,586,1,31,354,43,3] println(A)               # Original Array println(QuickSort(A))    # Quick Sort Array 

外部連結

參考

  • Hoare, C. A. R. "Partition: Algorithm 63," "Quicksort: Algorithm 64," and "Find: Algorithm 65." Comm. ACM 4, 321-322, 1961
  • R. Sedgewick. Implementing quicksort programs, Communications of the ACM, 21(10):847-857, 1978.
  • David Musser. Introspective Sorting and Selection Algorithms, Software Practice and Experience vol 27, number 8, pages 983-993, 1997
  • A. LaMarca and R. E. Ladner. "The Influence of Caches on the Performance of Sorting." Proceedings of the Eighth Annual ACM-SIAM Symposium on Discrete Algorithms, 1997. pp. 370–379.
  • Faron Moller. Analysis of Quicksort. CS 332: Designing Algorithms. Department of Computer Science, University of Wales Swansea.
  • Steven Skiena. Lecture 5 - quicksort. CSE 373/548 - Analysis of Algorithms. Department of Computer Science. SUNY Stony Brook.

註腳

1. David M. W. Powers. Parallelized Quicksort with Optimal Speedup页面存档备份,存于互联网档案馆). Proceedings of International Conference on Parallel Computing Technologies. Novosibirsk. 1991.

Tags:

快速排序 演算法快速排序 优化的排序演算法快速排序 正規分析快速排序 選擇的關連性快速排序 實作範例快速排序 外部連結快速排序 參考快速排序 註腳快速排序大O符号排序算法東尼·霍爾

🔥 Trending searches on Wiki 中文:

中越战争康熙帝脱氧核糖核酸Z-LibraryWhatsApp三毛 (作家)白鹿 (演員)財閥家的小兒子再見·枕邊人彭雪芬英国张凌赫Build Up宋江 (演員)WING STARSRoxyASCII苦力 (電視劇)2015年莫三比克啤酒中毒事件伊斯兰国呼罗珊省埃隆·马斯克一人之下瑪嘉烈與大衛系列 綠豆歡迎來到實力至上主義的教室 (動畫)LE SSERAFIM沙丘 (2021年電影)GitHub朴信惠EunhaKCM龍珠超朝鲜战争刘少奇你的婚禮馬來西亞公共假期黃仁勳汪小菲美亞廚具JO1Steam我的出走日記周处胡锦涛二十大离场事件名偵探柯南:100萬美元的五稜星王沪宁张若昀乌克兰Lexus哥吉拉 II 怪獸之王UNINE愛愛內含光七葉蘭體能之巔:百人大挑戰Aqours長月燼明伊莉討論區咒術迴戰TOMORROW X TOGETHERV Jump周深此時此刻 (電視劇)AOA法轮功彭于晏4chan三体II:黑暗森林KARD王世堅兩女一杯雅凜神病爭議鄧紫棋黑暗荣耀ONE PIECE曾蔭權周庭SiriFinally🡆 More