資料結構學習(C++)續——排序【6】內部排序總結 (轉)

amyz發表於2007-11-28
資料結構學習(C++)續——排序【6】內部排序總結 (轉)[@more@]

基數排序本文後面將會提到,我覺得將其和前面的排序演算法放在一起比較有些不倫不類。前面介紹了四類排序方法,每種都有基本型和改進型。對於內部排序,我們最關心的當然是速度,這也是為什麼快排受歡迎的原因。考慮到快排的缺陷,有時候我們可能會用堆排或者希爾排序、歸併排序。

上面可能是選擇排序方法最直接的思路了(我們的選擇範圍也不算廣,就那幾個翻過來調過去的,好一點的,綜合一下搞一個“雜牌”),出於“賭徒”的思維,我們大多數人可能用快排包打天下——出最壞情況?我沒那麼倒黴吧?大不了再加一個“三者取中”(或者隨機選取)。但是,有時候速度並不是一切,我們還要考慮排序的穩定性。

前面沒有涉及穩定性,主要是考慮每提到一種演算法都要說一些本來並不是十分重要的“是否穩定”實在是干擾讀者思路,並且前面的測試也無法反映穩定性。現在我們放在一起來說。排序的穩定性是指,對於相同的關鍵字,排序完成後他們的次序是否發生了改變,維持原狀是穩定,否則就是不穩定的。實際上就是說,對一個多關鍵字序列,多次排序結果是否能夠累積——多次排序能否最終達到預期的有序。打個比方,首先我們按學號對學生排序得到一個序列(在實際的應用中,初始序列總是這樣的,我們根本不需要排序),然後按照成績排序,對於成績相同的學生,我們希望學號靠前的排在前邊(預期的有序),如果排序是不穩定的,就會破壞前面按照學號排好的序列,從而導致得不到最終的預期序列。注意到是“預期有序”,在排成績的例子中,如果我們不要求相同成績的學號有序,排序是否穩定就無關緊要了。

什麼排序演算法是穩定的呢?首先看一下是什麼導致了“不穩定”。注意到前邊的四類方法都有穩定的演算法(這是現有結論,不要問我是怎麼來的,反正是就是是了,^_^),排序的思路應該不是不穩定的因素。排序肯定有記錄的移動(或者指標的修改),移動方法有平移(直插排序)、(起泡排序)、重排(表插、歸併)。仔細觀察一下,就會發現,不相鄰位置記錄的交換是導致不穩定的因素。這樣一來,凡是有這個隱患的排序演算法都是不穩定的,對於原來穩定的演算法,如果採用了這樣的交換策略,也會導致不穩定,比如直接選擇排序對於連結串列來說是穩定的,但對於陣列來說就是不穩定的,然而,如果採用平移來代替原來的交換,那麼對於陣列也是穩定(估計沒人願意把本來的交換1次改成平移一堆)。

另外對於本來穩定的演算法,當更改關鍵字判斷條件後,比如大於變為大於等於,也會導致不該移動的移動,從而穩定的演算法變成不穩定的,但這種低階的失誤不在我們討論範圍——蓄意把穩定改不穩定,沒帶來的提升,誰這樣做誰有病。

當了解穩定性的本質之後,就可以看透基數排序了。

基數排序

當初聽到可以突破O(nlogn)下限的排序方法很驚奇,實際看過之後不過爾爾,我們日常生活中經常在應用,只是我們沒有注意到。我們都玩過“十二月”的排陣遊戲吧,當我們抽到6的時候就會把他放在6號位置上(第1排第6個位置),如果一切順利,最後就會得到12疊排,依次是1、2……12,可以看到,排序實現了。再來看看0~999內整數的排序,假設數字互不重複,最為直接的就是,建一個1000大小的陣列a[],如果是1,就放到a[1],如果是400,就放到a[400],將數字都放好之後,從a[0]到a[999]重新讀一遍,排序就完成了。

很清楚的,這裡使用了雜湊查詢技術,而雜湊表的最佳查詢效能是O(1),整體上看,上面的0~999無重複數字的分配的時間複雜度就為O(n)了。當有重複數字的時候,這裡使用的處理衝突的辦法是鏈地址法——將所有的重複數字組成一個連結串列掛在對應的位置。很顯然的,這個過程只不過是連結串列的重排,因此是穩定的(非要寫成不穩定的我也沒辦法)。

在上面的“分配-收集”的基礎上,就可以完成基數排序。討論基數排序是否穩定實際上是件很可笑的事情,因為基數排序能工作的前提就是必須是穩定的——它是多關鍵字排序的累積結果,如果其中有不穩定的操作,整個結果也就是錯的。

對於單關鍵字,要麼可以一字排開的分配,然後收集,時間複雜度為O(n+r)(最後的收集過程O(r));要麼嫌附加儲存太多,也可以拆成多個關鍵字,附加儲存成指數下降(不是上升^_^),自然的就要多分配收集幾次。因為高位的關鍵字決定序列的最終順序,所以必須最後做高位的分配收集,基數排序一般來說都是LSD(最低位優先)的。

另外不要一看到用基數排序對整數排序就想到百、十、個位分解,注意到“基數”這個概念,你用多少基數分解都可以,比如1000進1的“1000進位制”。例程不給了,因為基數排序的限制條件太苛刻了。

關於外部排序

這個看似很神秘,但只要知道了“歸併”的分段有序變整段有序的作用,就明白這樣的任務也是能夠完成的,剩下的是如何提高。

提到,我們總想到“操作時間”遠小於“讀盤時間”,但現在的技術使得讀盤時間已經越來越短了,我自己的感受是對40MB的整數排序不會比從讀40MB內容來得快(在我的機器上內排一千萬亂序整數的時間是18s,可能是我演算法寫的不好)。但提高速度的不二法門就是減少不必要的慢速裝置的資訊流量,就像Cache於記憶體,記憶體於硬碟。所有我們能夠想到的提高外排速度的方法,也不外乎減少記憶體和外存的資訊流量。這裡邊採用的技術就有增加歸併路數、增加初始歸併段長度,最佳歸併樹等等。

然而實際上,對於1000個以上的資料,我們從來不會自己去管理,都是借用了事。也就是說,如果不去寫資料庫,估計也用不到外排;而如果寫資料庫,外排的知識也不過是九牛一毛。

相對於內排的常用,外排或許不是必須掌握的技術,學習它的目的更應該是為我們提供了一個如何解決“記憶體不夠”的思路,以及如何提高外存效能的思路。

沒有見過真實模擬的例程,也就不獻醜了。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-985640/,如需轉載,請註明出處,否則將追究法律責任。

相關文章