David MacKay:用信息論解釋 '快速排序'、'堆排序' 本質與差異
這篇文章是David MacKay利用信息論,來對快排、堆排的本質差異導致的性能差異進行的比較。
信息論是非常強大的,它並不只是一個用來分析理論最優決策的工具。
從信息論的角度來分析算法效率是一件很有趣的事,它給我們分析排序算法帶來了一種新的思路。
運用了信息論的概念,我們很容易理解為什麽快排的速度那麽快,以及它的缺陷在哪裏。
由於個人能力不足,對於本文的理解可能還是有點偏差。
而且因為翻譯的困難,這篇譯文有很多地方並沒有翻譯出來,還是使用了原文的句子。
所以建議大家還是閱讀原文Heapsort, Quicksort, and Entropy
但是如果有朋友看了下面這篇譯文,並且有更好的翻譯方法,歡迎各位留言,不勝感激!!!
----------------------------------------------------------譯文如下------------------------------------------------------------------
堆排,快排,與信息熵
有很多的網絡文章對於堆排與快排進行了比較分析。
它們中大部分的觀點都是,“這兩種算法的平均時間效率都漸進於NlogN,但在實際測試中,一個好的快排的實現效率通常能夠打敗堆排。”
有些人進一步分析,並且給出了一些定量的說明:”堆排的平均比較次數是快排的兩倍,但是堆排避免了在低概率下表現出的的災難性的效率退化的情況。”
但是,很少有人會問這樣的問題“為什麽堆排使用了兩倍於快排的比較次數?”人們費了很大的努力,試圖創造一種具有兩種算法優先的排序算法,比如:’introspective sort‘---這個算法提供了快排的遞歸實現,並且在遞歸深度很深的時候,選擇堆排來實現。
Paul Hsieh也深入地比較了快排與堆排。他說:“我懷疑堆排的實際效率遠遠好過人們印象中的認識,並且有一些實際結果證明了這一點。”在他的測試中,最好的編譯器(對於堆排或者快排來說都一樣)產生的一個結果就是:在總的CPU執行時間上,堆排比快排要快20%。
而實際上,在排序3000個對象的時候,堆排平均使用了61000次的比較,而快排只用了22000次的比較。為什麽比較次數多的堆排,反而CPU執行的時間會少呢?關於這一點,可以看Paul Hsieh
而我想要討論的問題是:為什麽堆排的比較次數會比快排的比較次數多呢?Paul Hsieh說過:“讓我倍受打擊的是,我無法看出為什麽堆排會比快排慢。而且我也還沒有聽到或者讀過一個權威的解釋。”
在預期的平均信息量(expected information content)的基礎上,我們可以得到一個簡單的解釋。為了讓大家更容易讀懂,我們先介紹一些其他的預備知識。
首先來看看經典的稱重問題:
給定12個外表看起來一樣的球,其中11個球的重量相同,還有1個球可能更重或者更輕。你需要做的事情是,使用一個沒有刻度的天平,每次在天平的兩端放相同數量的球,你的任務是設計一種策略,使得能夠找出這個特殊的球,並且使得使用天平的次數最少。
很多人是通過反復嘗試,不斷摸索的方式來找到解決方案的(本例可以使用3次比較得到特殊的球)。這種試錯的方法顯然太繁瑣復雜,這裏有一個更好的方法。
想要減少嘗試的次數,我們需要增加平均一次嘗試所能夠獲得的信息。
Shannon已經向我們展示了一種好的方法來定義從結果所能獲得的期望的信息量,叫做結果的信息熵。(如果想要了解信息熵,或者更深入的討論稱重問題,可以看我寫的書:Information Theory, Inference, and Learning Algorithms)。
通過每次總選擇一次信息熵最大的嘗試,我們可以快速地解決這個稱重問題。
排序與bits
如果一個排序是一個比較排序,而且每一次的比較都產生兩個概率相同的結果,那麽一次比較所得到的平均信息量是1bit。
排序N個對象所需要的信息量;恰好是logN!個bits(假設對象沒有優先級信息)。
使用Stiring的方法,這個總的信息量是:T =N log2N -N log2 e.
任何排序算法的平均比較次數不會小於T。並且,當比較的結果是等可能(結果的兩種情況概率相同)時,這個排序的平均比較次數將接近於T。
那麽,是什麽原因導致了“在一般情況下,堆排的速度會比快排慢”?
很顯然,這是因為堆排的比較產生的結果不是等概率的。我們將在下面解釋這個問題。
附帶的說明一下,標準的快排隨機算法也有同樣的缺陷。但是,令人氣憤的是,很多算法學習者都會說“在大部分情況下,快排隨機算法使用了O(NlogN)次的比較”,並且,他們丟掉了常數因子。多麽令人哭笑不得的符號’O’!當有人辯解說“我們關心的是當N很大的情況下的漸進表現”,這是多麽愚蠢的表達。這樣的表達好像在說,我們並不需要關心一個4NlogN的算法和一個NlogN算法之間的差別!
但是,如果我們計算出了這個NlogN算法的常數因子,或者,我們將目光轉向算法的本質的時候,一切將變的非常有意思。這裏,我將給出最終的一個結果,並且證明它的正確性。一個隨機快排的算法,它的平均時間消耗是N loge1/2N。
實際的消耗會比理想情況要大。但是,在理想情況下,我們將T約等於N log2N,將常數1/log21.649約等於1.39。如果我們關心比較次數,那麽我們還需要找到一個比快排更好的算法。
More here... You can see thhat quicksort has unbalanced probabilities by imagining the last few comparisons of numbers with a pivot. If the preceding 100 comparisons have divided 70 on one side and 30 on the other side, then it seems a good prediction that the next comparison with the pivot has a probability of 0.7 of going the first way and 0.3 of going the other.
回歸到堆排序
堆排序的比較是低效率的,因為,它可能需要將底下的數移動到頂上,並且允許他們順沿下去,與更大的數交換位置。。。。。。。。。
為什麽堆排會這麽做呢?難道沒有人想過一個更加漂亮的方法,來將兩個子堆中的最大值移動到堆的頂部?
下面這種方法怎麽樣:
改進的堆排序(可能之前就有人想過這個方法)
1、將元素放入一個有效的最大堆中
2、刪除堆的頂部,產生一個空缺位置’V’
|
Modified Heapsort (surely someone already thought of this) |
|
1 |
Put items into a valid max-heap |
|
2 |
Remove the top of the heap, creating a vacancy ‘V‘ |
|
3 |
Compare the two sub-heap leaders directly below V, and promote the biggest one into the vacancy. Recursively repeat step 3, redefining V to be the new vacancy, until we reach the bottom of the heap. |
(This is just like the sift operation of heapsort, except that we‘ve effectively promoted an element, known to be the smaller than all others, to the top of the heap; this smallest element can automatically trickle down without needing to be compared with anything.) |
4 |
Go to step 2 |
|
|
|
Disadvantage of this approach: it doesn‘t have the pretty in-place property of heapsort. But we could obtain this property again by introducing an extra swap at the end, swapping the ‘smallest‘ element with another element at the bottom of the heap, the one which would have been removed in heapsort, and running another sift recursion from that element upwards. |
讓我們稱這個算法為:Fast Heapsort.這不是一個原地算法,but, just like Heapsort, it extracts the sorted items one at a time from the top of the heap.
Fast Heapsort的表現
我評估了在隨機排列的情況下Fast Heapsort的表現。Performance was measured solely on the basis of the number of binary comparisons required. [Fast Heapsort does require extra bookkeeping, so the CPU comparison will come out differently.]
Performance of Fast Heapsort. |
I haven‘t proved that Fast Heapsort comes close to maximizing the entropy at each step, but it seems reasonable to imagine that it might indeed do so asymptotically. After all, Heapsort‘s starting heap is rather like an organization in which the top dog has been made president, and the top dogs in divisions A and B have been made vice-president; a similar organization persists all the way down to the lowest level. The president originated in one of those divisions, and got his job by sequentially deposing his bosses.
Now if the boss leaves and needs to be replaced by the best person in the organization, we‘ll clearly need to compare the two vice-presidents; the question is, do we expect this to be a close contest? We have little cause to bet on either vice-president, a priori. There are just two asymmetries in the situation: first, the retiring president probably originated in one of the two divisions; and second, the total numbers in those two divisions may be unequal. VP A might be the best of slightly morepeople than VP B; the best of a big village is more likely to beat the best of a small village. And the standard way of making a heap can make rather lop-sided binary trees. In an organization with 23 people, for example, division A will contain (8+4+2+1)=15 people, and division B just (4+2+1)=7.
為了讓堆排序能夠更快,我建議下面兩種改進:
1、讓堆更平衡。Destroy the elegant heap rules, that i‘s children are at (2i) and (2i+1), and that a heap is filled from left to right. Will this make any difference? Perhaps not; perhaps it‘s like a Huffman algorithm with some free choices.
2、提供信息論的觀點來初始化堆信息處理。Does the opening Heapify routine make comparisons that have entropy significantly less than one bit?
回到快排
我們同樣可以給出快速排序的信息熵處理。快排很浪費,因為它堅持在兩種可能結果不同等時繼續做比較。大約有一半的時間,快排都用了一個差的主元—差的原因是這個主元是在在四分間距之外的—並且,一旦這個主元是差的主元,那麽每個同這個主元進行比較產生的信息量將小於1bit。
一個簡單的方法能夠減少快排由於選擇差的主元而產生的低效率,這個方法叫做“三值取中法”。這種算法的思想是:每次隨機取三個數,並且選擇中間的數作為主元。這種方法,使得選擇到差主元的概率大大降低。但是我們還可以比這個做的更好。讓我們回到分析快排產生信息的最開始。
當我們隨機選擇一個主元,並且將它同其他隨機數進行比較時,很顯然,這個結果的信息熵是1bit。這是一個漂亮的開始。但是,當我們將其他數跟這個主元進行比較時,我們卻做了一次差的比較。如果第一個元素比較的結果是比主元大,那麽,主觀上,第二個數比主元數大的概率也較大。實際上,第二個數比主元大的概率大約是2/3(這是一個漂亮的貝葉斯定理,當N=3, N=4時,2/3的概率是準確的;或許對於所有的N,這個概率都是準確的)。(1/3,2/3)的信息熵 = 0.918,所以,第一次比較是性能還不算太差—只有8%的無用信息。根據信息熵,我們需要在第二次的比較中對比其他兩個元素。但是,讓我們繼續使用快排。如果第一次兩個元素都比主元大,那麽下一個元素比主元大的概率是3/4(信息熵為:0.811 bits)。這種情況的出現頻率將會在超過一半的時間裏繼續增加。
表1顯示,將五個元素與主元進行比較之後,
|
|||||||||||||||||||||||||||||||||||||
Table 1 |
表2顯示,每一次快排的叠代所產生的平均信息熵。
|
||||||||||||||||||||||||||||||||||||
Table 2 |
當使用快排的時候,我們可以通過計算去做一些合理的決定。比如,我們可以選擇繼續使用這個主元,或者從那些跟當前主元比較過的數中選擇一個數作為主元(這種代價有點高)。‘三值取中法‘的起始處理可以看做是這樣的:跟標準的快速排序算法一樣,我們先用兩個元素分別跟主元進行比較。如果我們到達(1,1)的情形,它發生的概率是1/3,那麽我們就繼續使用這個主元。如果我們到達(0,2)或 (2,0)的情形,就可以判斷這是一個差的主元,並棄用這個主元。然後,我們從跟當前主元比較過的兩個元素中選擇一個中間值,作為新的主元。這個主元的選擇會花費一個額外的比較時間,但是對於由此所能夠獲得的利益來說,這個花費可以忽略。
我們可以將“三值取中法”放到一個客觀的基礎上,使用信息論,並且,我們可以將它進行推廣。
假設,我們已經使用一個隨機選擇的主元比較了M-1個數(總數為N個),並且我們到達了(m1,m2)的情形。1、我們可以選擇繼續使用這個主元。使用這個主元帶來的下一次的比較的信息熵為H_2(p)(這裏的p = (m1+1)/(m1+m2+2))。2、我們也可以寄希望於一個中指查找算法,這個算法可以找到M個數的中值並且設為主元。中值可以在一個線性的代價(cM)內找到(Median can be found with an expected cost proportional to M。比如:快排的花費是4M。
我們可以評估一下這兩種選擇帶來的時間消耗。
如果我們使用這個主元繼續進行(N-M)次的比較,那麽得到的期望信息大約是R = (N-M)H_2(p) bits。(這並不是很準確,如果想要精確求的,可以使用積分的方法來找這個精確值)。
如果我們選擇的是在隨後的連續叠代中使用新的主元(這樣的消耗將很大—這提示我們應該使用一個新的算法—首先使用一個排序算法來找到多個主元),這個花費大約是(N-M)+4(M-1),得到的期望信息大約是R‘ = (N-M)H_2(p‘) bits,(這裏的p‘是一個新的主元)。
If we approximate R‘ \simeq N-M then finding the new pivot has the better return-to-cost ratio if
i.e. 1/ ( 1+4(M-1)/(N-M) ) > H_2(p)
我們可以這麽做,如果N-M的值很大,那麽我們就有更充分的理由去棄用這個主元。
Further modifying quicksort, we can plan ahead: it‘s almost certainly a good idea to perfectly sort a M randomly selected points and find their median, where M is roughly sqrt(N/log(N)), and use that fairly accurate median as the pivot for the first iteration.
Summary: ‘median-of-3‘ is a good idea, but even better (for all N greater than 70 or so) is ‘median-of-sqrt(N/log(N))‘. If you retain the sorted subset from iteration to iteration, you‘ll end up with something rather like a Self-balancing binary search tree.
參考文獻:
Nice explanation of randomized median-finding in O(n) time
deterministic median-finding.
David MacKay December 2005http://www.aims.ac.za/~mackay/sorting/sorting.html from: http://blog.csdn.net/cyh_24/article/details/8094318
David MacKay:用信息論解釋 '快速排序'、'堆排序' 本質與差異