基本上全部正而八經的算法教材都會解釋像快速排序quicksort和堆排序heapsort這樣的排序算法有多快,但並不須要複雜的數學就能證實你能夠逐漸趨近的速度有多快。html
關於標記的一個嚴肅說明:算法
大多數計算機專業的科學家使用大寫字母 O 標記來指代「趨近,直到到達一個常數比例因子」,這與數學專業所指代的意義是有所區別的。這裏我使用的大 O 標記的含義與計算機教材所指相同,但至少不會和其餘數學符號混用。數組
先來看個特例,即每次比較兩個值大小的算法(快速排序、堆排序,及其它通用排序算法)。這種思想後續能夠擴展至全部排序算法。ide
假設有 4 個互不相等的數,且順序隨機,那麼,能夠經過只比較一對數字完成排序嗎?顯然不能,證實以下:根據定義,要對該數組排序,須要按照某種順序從新排列數字。換句話說,你須要知道用哪一種排列方式?有多少種可能的排列?第一個數字能夠放在四個位置中的任意一個,第二個數字能夠放在剩下三個位置中的任意一個,第三個數字能夠放在剩下兩個位置中的任意一個,最後一個數字只有剩下的一個位置可選。這樣,共有 4×3×2×1=4!=24 種排列可供選擇。經過一次比較大小,只能產生兩種可能的結果。若是列出全部的排列,那麼「從小到大」排序對應的多是第 8 種排列,按「從大到小」排序對應的多是第 24 種排列,但沒法知道何時須要的是其它 22 種排列。性能
經過 2 次比較,能夠獲得 2×2=4 種可能的結果,這仍然不夠。只要比較的次數少於 5(對應 25 = 32 種輸出),就沒法完成 4 個隨機次序的數字的排序。若是 W(N)
是最差狀況下對 N
個不一樣元素進行排序所須要的比較次數,那麼,ui
兩邊取以 2 爲底的對數,得:編碼
N!
的增加近似於 NN
(參閱 Stirling 公式),那麼,spa
這就是最差狀況下從輸出計數的角度得出的 O(N log N)
上限。設計
使用一些信息論知識,就能夠從上面的討論中獲得一個更有力的結論。下面,使用排序算法做爲信息傳輸的編碼器:code
任取一個數,好比 15
從 4 個數字的排列列表中查找第 15 種排列
對這種排列運行排序算法,記錄全部的「大」、「小」比較結果
用二進制編碼發送比較結果
接收端從新逐步執行發送端的排序算法,須要的話能夠引用發送端的比較結果
如今接收端就能夠知道發送端如何從新排列數字以按照須要排序,接收端能夠對排列進行逆算,獲得 4 個數字的初始順序
接收端在排列表中檢索發送端的原始排列,指出發送端發送的是 15
確實,這有點奇怪,但確實能夠。這意味着排序算法遵循着與編碼方案相同的定律,包括理論所證實的不存在通用的數據壓縮算法。算法中每次比較發送 1 比特的比較結果編碼數據,根據信息論,比較的次數至少是能表示全部數據的二進制位數。更技術語言點,平均所需的最小比較次數是輸入數據的香農熵,以比特爲單位。熵是衡量信息等不可預測量的數學度量。
包含 N
個元素的數組,元素次序隨機且無偏時的熵最大,其值爲 log2 N!
個比特。這證實 O(N log N)
是一個基於比較的對任意輸入排序的最優平均值。
以上都是理論說法,那麼實際的排序算法如何作比較的呢?下面是一個數組排序所需比較次數均值的圖。我比較的是理論值與快速排序及 Ford-Johnson 合併插入排序 的表現。後者設計目的就是最小化比較次數(總體上沒比快速排序快多少,由於生活中相對於最大限度減小比較次數,還有更重要的事情)。又由於合併插入排序merge-insertion sort是在 1959 年提出的,它一直在調整,以減小了一些比較次數,但圖示說明,它基本上達到了最優狀態。
一點點理論導出這麼實用的結論,這感受真棒!
證實了:
若是數組能夠是任意順序,在最壞狀況下至少須要 O(N log N)
次比較。
數組的平均比較次數最少是數組的熵,對隨機輸入而言,其值是 O(N log N)
。
注意,第 2 個結論容許基於比較的算法優於 O(N log N)
,前提是輸入是低熵的(換言之,是部分可預測的)。若是輸入包含不少有序的子序列,那麼合併排序的性能接近 O(N)
。若是在肯定一個位以前,其輸入是有序的,插入排序性能接近 O(N)
。在最差狀況下,以上算法的性能表現都不超出 O(N log N)
。
基於比較的排序在實踐中是個有趣的特例,但從理論上講,計算機的 CMP 指令與其它指令相比,並無什麼特別之處。在下面兩條的基礎上,前面兩種情形均可以擴展至任意排序算法:
大多數計算機指令有多於兩個的輸出,但輸出的數量仍然是有限的。
一條指令有限的輸出意味着一條指令只能處理有限的熵。
這給出了 O(N log N)
對應的指令下限。任何物理上可實現的計算機都只能在給定時間內執行有限數量的指令,因此算法的執行時間也有對應 O(N log N)
的下限。
通常意義上的 O(N log N)
下限,放在實踐中來看,若是聽人說到任何更快的算法,你要知道,它確定以某種方式「做弊」了,其中確定有圈套,即它不是一個能夠處理任意大數組的通用排序算法。可能它是一個有用的算法,但最好看明白它字裏行間隱含的東西。
一個廣爲人知的例子是基數排序radix sort算法,它常常被稱爲 O(N)
排序算法,但它只能處理全部數字都能放入 k
比特的狀況,因此實際上它的性能是 O(kN)
。
什麼意思呢?假如你用的 8 位計算機,那麼 8 個二進制位能夠表示 28=256 個不一樣的數字,若是數組有上千個數字,那麼其中必有重複。對有些應用而言這是能夠的,但對有些應用就必須用 16 個二進制位來表示,16 個二進制位能夠表示 216=65,536 個不一樣的數字。32 個二進制位能夠表示 232=4,294,967,296 不一樣的數字。隨着數組長度的增加,所須要的二進制位數也在增加。要表示 N 個不一樣的數字,須要 k ≥ log2 N
個二進制位。因此,只有容許數組中存在重複的數字時, O(kN)
才優於 O(N log N)
。
通常意義上輸入數據的 O(N log N)
的性能已經說明了所有問題。這個討論不那麼有趣由於不多須要在 32 位計算機上對幾十億整數進行排序,若是有誰的需求超出了 64 位計算機的極限,他必定沒有告訴別人。
【責任編輯:龐桂玉 TEL:(010)68476606】