【專欄】數學之美番外篇:快排為什么那樣快(2)

排序的本質可以這樣來表述:一組未排序的N個數字,它們一共有N!種重排,其中只有一種排列是滿足題意的(譬如從大到小排列)。換句話說,排序問題的可能性一共有N!種。

排序

用前面看問題的視角,排序的本質可以這樣來表述:一組未排序的N個數字,它們一共有N!種重排,其中只有一種排列是滿足題意的(譬如從大到小排列)。換句話說,排序問題的可能性一共有N!種。任何基于比較的排序的基本操作單元都是“比較a和b”,這就相當于猜數字游戲里面的一個問句,顯然這個問句的答案只能是“是”或“否”,一個只有兩種輸出的問題最多只能將可能性空間切成兩半,根據上面的思路,最佳切法就是切成1/2和1/2。也就是說,我們希望在比較了a和b的大小關系之后,如果發現a<b的話剩下的排列可能性就變成N!/2,如果發現a>b也是剩下N!/2種可能性。由于假設每種排列的概率是均等的,所以這也就意味著支持a<b的排列一共有N!/2個,支持a>b的也是N!/2個,換言之,a<b的概率等于a>b的概率。

我們希望每次在比較a和b的時候,a<b和a>b的概率是均等的,這樣我們就能保證無論如何都能將可能性縮小為原來的一半了!最優下界。

一個直接的推論是,如果每次都像上面這樣的完美比較,那么N個元素的N!種可能排列只需要log_2{N!}就排查完了,而log_2{N!}近似于NlogN。這正是快排的復雜度。

作者:劉未鵬 出版:電子工業出版社 

為什么堆排比快排慢

回顧一下堆排的過程:

1.建立最大堆(堆頂的元素大于其兩個兒子,兩個兒子又分別大于它們各自下屬的兩個兒子…以此類推)

2.將堆頂的元素和最后一個元素對調(相當于將堆頂元素(最大值)拿走,然后將堆底的那個元素補上它的空缺),然后讓那最后一個元素從頂上往下滑到恰當的位置(重新使堆最大化)。

3.重復第2步。

這里的關鍵問題就在于第2步,堆底的元素肯定很小,將它拿到堆頂和原本屬于最大元素的兩個子節點比較,它比它們大的可能性是微乎其微的。實際上它肯定小于其中的一個兒子。而大于另一個兒子的可能性非常小。于是,這一次比較的結果就是概率不均等的,根據前面的分析,概率不均等的比較是不明智的,因為它并不能保證在糟糕情況下也能將問題的可能性削減到原本的1/2??梢韵胂褚环N極端情況,如果a肯定小于b,那么比較a和b就會什么信息也得不到——原本剩下多少可能性還是剩下多少可能性。

在堆排里面有大量這種近乎無效的比較,因為被拿到堆頂的那個元素幾乎肯定是很小的,而靠近堆頂的元素又幾乎肯定是很大的,將一個很小的數和一個很大的數比較,結果幾乎肯定是“小于”的,這就意味著問題的可能性只被排除掉了很小一部分。

這就是為什么堆排比較慢(堆排雖然和快排一樣復雜度都是O(NlogN)但堆排復雜度的常系數更大)。

MacKay也提供了一個修改版的堆排:每次不是將堆底的元素拿到上面去,而是直接比較堆頂(最大)元素的兩個兒子,即選出次大的元素。由于這兩個兒子之間的大小關系是很不確定的,兩者都很大,說不好哪個更大哪個更小,所以這次比較的兩個結果就是概率均等的了。具體參考這里。

為什么快排其實也不是那么快

我們考慮快排的過程:隨機選擇一個元素做“軸元素”,將所有大于軸元素的移到左邊,其余移到右邊。根據這個過程,快排的第一次比較就是將一個元素和軸元素比較,這個時候顯而易見的是,“大于”和“小于”的可能性各占一半。這是一次漂亮的比較。

然而,快排的第二次比較就不那么高明了:我們不妨令軸元素為pivot,第一次比較結果是a1<pivot,那么可以證明第二次比較a2也小于pivot的可能性是2/3!這容易證明:如果a2>pivot的話,那么a1,a2,pivot這三個元素之間的關系就完全確定了——a1<pivot<a2,剩下來的元素排列的可能性我們不妨記為P(不需要具體算出來)。而如果a2<pivot呢?那么a1和a2的關系就仍然是不確定的,也就是說,這個分支里面含有兩種情況:a1<a2<pivot,以及a2<a1<pivot。對于其中任一種情況,剩下的元素排列的可能性都是P,于是這個分支里面剩下的排列可能性就是2P。所以當a2<pivot的時候,還剩下2/3的可能性需要排查。

再進一步,如果第二步比較果真發現a2<pivot的話,第三步比較就更不妙了,模仿上面的推理,a3<pivot的概率將會是3/4!

這就是快排也不那么快的原因,因為它也沒有做到每次比較都能將剩下的可能性砍掉一半。

基排為什么又那么快呢?

傳統的解釋是:基排不是基于比較的,所以不具有后者的局限性。話是沒錯,但其實還可以將它和基于比較的排序做一個類比。

基排的過程也許是源于我們理順一副牌的過程:如果你有N(N<=13)張牌,亂序,如何理順呢?我們假象桌上有十三個位置,然后我們將手里的牌一張一張放出去,如果是3,就放在位置3上,如果是J,就放在位置11上,放完了之后從位置1到位置13收集所有的牌(沒有牌的位置上不收集任何牌)。

我們可以這樣來理解基排高效的本質原因:假設前i張牌都已經放到了它們對應的位置上,第i+1張牌放出去的時候,實際上就相當于“一下子”就確立了它和前i張牌的大小關系,用O(1)的操作就將這張牌正確地插入到了前i張牌中的正確位置上,這個效果就相當于插入排序的第i輪原本需要比較O(i)次的,現在只需要O(1)了。

但是,為什么基排能夠達到這個效果呢?上面只是解釋了過程,解釋了過程不代表解釋了本質。

當i張牌放到位之后,放置第i+1張牌的時候有多少種可能性?大約i+1種,因為前i張牌將13個位置分割成了i+1個區間——第i+1張牌可以落在任意一個區間。所以放置第i+1張牌就好比是詢問這樣一個問題:“這張牌落在哪個區間呢?”而這個問題的答案有i+1種可能性?所以它就將剩下來的可能性均分成了i+1份(換句話說,砍掉了i/i+1的可能性?。?。再看看基于比較的排序吧:由于每次比較只有兩種結果,所以最多只能將剩下的可能性砍掉一半。

這就是為什么基排要快得多。而所有基于比較的排序都逃脫不了NlogN的宿命。

信息論!信息論?

本來呢,MacKay寫那篇文章《Information Theory: Inference and Learning Algorithms》是想用信息論來解釋為什么堆排慢,以及為什么快排也慢的。MacKay在他的文章中的解釋是,只有提出每種答案的概率都均等的問題,才能獲得最大信息量。然 而,仔細一想,其實這里信息論并不是因,而是果。這里不需要用信息論就完全能夠解釋,而且更明白。信息論只是對這個解釋的一個形式化。當然,信息論在其它 地方還是有應用的。但這里其實用不著信息論這么重量級的東西(也許具體計算一些數據的時候是需要的),而是只需要一種看問題的本質視角:將排序問題看成和 猜數字一樣,是通過問問題來縮小/排除(narrow down)結果的可能性區間,這樣一來,就會發現,“最好的問題”就是那些能夠均分所有可能性的問題,因為那樣的話不管問題的答案如何,都能排除掉k-1/k(k為問題的答案有多少種輸出——猜數字里面是2,稱球里面是3)種可能性,而不均衡的問題總會有一個或一些答案分支排除掉的可能性要小于k-1/k。于是策略的下界就被拖累了。

小結

這的確是“小結”,因為兩點:

1.這個問題可以有信息論的理論解釋,而信息論則是一個相當大的領域了。

2.文中提到的這種看問題的視角除了用于排序、稱球,還能夠運用到哪些問題上(比如搜索)。

(待續;此文的修訂版已收錄《暗時間》一書,由電子工業出版社2011年8月出版。作者于2009年7月獲得南京大學計算機系碩士學位,現在微軟亞洲研究院創新工程中心從事軟件研發工程師工作。)

網絡編輯:謝小跳

{{ isview_popup.firstLine }}{{ isview_popup.highlight }}

{{ isview_popup.secondLine }}

{{ isview_popup.buttonText }}
午夜宅男在线,中视在线直播,毛片网站在线,福利在线网址