外部排序:
一、定義問題
外部排序指的是大文件的排序,即待排序的記錄存儲在外存儲器上,待排序的文件無法一次裝入內存,需要在內存和外部存儲器之間進行多次數據交換,以達到排序 整個文件的目的。外部排序最常用的算法是多路歸並排序,即將原文件分解成多個能夠一次性裝入內存的部分,分別把每一部分調入內存完成排序。然后,對已經排 序的子文件進行多路歸並排序。
二、處理過程
(1)按可用內存的大小,把外存上含有n個記錄的文件分成若干個長度為L的子文件,把這些子文件依次讀入內存,並利用有效的內部排序方法對它們進行排序,再將排序后得到的有序子文件重新寫入外存;
(2)對這些有序子文件逐趟歸並,使其逐漸由小到大,直至得到整個有序文件為止。
先從一個例子來看外排序中的歸並是如何進行的?
假設有一個含10000 個記錄的文件,首先通過10 次內部排序得到10 個初始歸並段R1~R10 ,其中每一段都含1000 個記錄。然后對它們作如圖10.11 所示的兩兩歸並,直至得到一個有序文件為止 如下圖
三 、多路歸並排序算法以及敗者樹
多路歸並排序算法在常見數據結構書中都有涉及。從2路到多路(k路),增大k可以減少外存信息讀寫時間,但k個歸並段中選取最小的記錄需要比較k-1次, 為得到u個記錄的一個有序段共需要(u-1)(k-1)次,若歸並趟數為s次,那么對n個記錄的文件進行外排時,內部歸並過程中進行的總的比較次數為 s(n-1)(k-1),也即(向上取整)(logkm)(k-1)(n-1)=(向上取整)(log2m/log2k)(k-1)(n-1),而(k- 1)/log2k隨k增而增因此內部歸並時間隨k增長而增長了,抵消了外存讀寫減少的時間,這樣做不行,由此引出了“敗者樹”tree of loser的使用。在內部歸並過程中利用敗者樹將k個歸並段中選取最小記錄比較的次數降為(向上取整)(log2k)次使總比較次數為(向上取整) (log2m)(n-1),與k無關。
敗者樹是完全二叉樹, 因此數據結構可以采用一維數組。其元素個數為k個葉子結點、k-1個比較結點、1個冠軍結點共2k個。ls[0]為冠軍結點,ls[1]--ls[k- 1]為比較結點,ls[k]--ls[2k-1]為葉子結點(同時用另外一個指針索引b[0]--b[k-1]指向)。另外bk為一個附加的輔助空間,不 屬於敗者樹,初始化時存着MINKEY的值。
多路歸並排序算法的過程大致為:
1):首先將k個歸並段中的首元素關鍵字依次存入b[0]--b[k-1]的葉子結點空間里,然后調用CreateLoserTree創建敗者樹,創建完畢之后最小的關鍵字下標(即所在歸並段的序號)便被存入ls[0]中。然后不斷循環:
2)把ls[0]所存最小關鍵字來自於哪個歸並段的序號得到為q,將該歸並段的首元素輸出到有序歸並段里,然后把下一個元素關鍵字放入上一個元素本來所 在的葉子結點b[q]中,調用Adjust順着b[q]這個葉子結點往上調整敗者樹直到新的最小的關鍵字被選出來,其下標同樣存在ls[0]中。循環這個 操作過程直至所有元素被寫到有序歸並段里。
外部排序之多路歸並
該外部排序上場了.
外部排序干嘛的?
- 內存極少的情況下,利用分治策略,利用外存保存中間結果,再用多路歸並來排序;
- map-reduce的嫡系.
1.分
內存中維護一個極小的核心緩沖區bigdata
按行讀入,搜集到memBuffer
中的數據調用內排進行排序,排序后將有序結果寫入磁盤文件 循環利用
2.合
現在有了n個有序的小文件,怎么合並成1個有序的大文件?
把所有小文件讀入內存,然后內排?
(⊙o⊙)…
no!
利用如下原理進行歸並排序:
我們舉個簡單的例子:
文件1:3,6,9
文件2:2,4,8
文件3:1,5,7第一回合:
文件1的最小值:3 , 排在文件1的第1行
文件2的最小值:2,排在文件2的第1行
文件3的最小值:1,排在文件3的第1行
那么,這3個文件中的最小值是:min(1,2,3) = 1
也就是說,最終大文件的當前最小值,是文件1、2、3的當前最小值的最小值,繞么?
上面拿出了最小值1,寫入大文件.
第二回合:
文件1的最小值:3 , 排在文件1的第1行
文件2的最小值:2,排在文件2的第1行
文件3的最小值:5,排在文件3的第2行
那么,這3個文件中的最小值是:min(5,2,3) = 2
將2寫入大文件.也就是說,最小值屬於哪個文件,那么就從哪個文件當中取下一行數據.(因為小文件內部有序,下一行數據代表了它當前的最小值)
敗者樹可以改善時間復雜度:
勝者樹與敗者樹
勝者樹和敗者樹都是完全二叉樹,是樹形選擇排序的一種變型。每個葉子結點相當於一個選手,每個中間結點相當於一場比賽,每一層相當於一輪比賽。
不同的是,勝者樹的中間結點記錄的是勝者的標號;而敗者樹的中間結點記錄的敗者的標號。
勝者樹與敗者樹可以在log(n)的時間內找到最值。任何一個葉子結點的值改變后,利用中間結點的信息,還是能夠快速地找到最值。在k路歸並排序中經常用到。
一、勝者樹
勝者樹的一個優點是,如果一個選手的值改變了,可以很容易地修改這棵勝者樹。只需要沿着從該結點到根結點的路徑修改這棵二叉樹,而不必改變其他比賽的結果。
Fig. 1
Fig.1是一個勝者樹的示例。規定數值小者勝。
1. b3 PK b4,b3勝b4負,內部結點ls[4]的值為3;
2. b3 PK b0,b3勝b0負,內部結點ls[2]的值為3;
3. b1 PK b2,b1勝b2負,內部結點ls[3]的值為1;
4. b3 PK b1,b3勝b1負,內部結點ls[1]的值為3。.
當Fig. 1中葉子結點b3的值變為11時,重構的勝者樹如Fig. 2所示。
1. b3 PK b4,b3勝b4負,內部結點ls[4]的值為3;
2. b3 PK b0,b0勝b3負,內部結點ls[2]的值為0;
3. b1 PK b2,b1勝b2負,內部結點ls[3]的值為1;
4. b0 PK b1,b1勝b0負,內部結點ls[1]的值為1。.
Fig. 2
二、敗者樹
敗者樹是勝者樹的一種變體。在敗者樹中,用父結點記錄其左右子結點進行比賽的敗者,而讓勝者參加下一輪的比賽。敗者樹的根結點記錄的是敗者,需要加一個結點來記錄整個比賽的勝利者。采用敗者樹可以簡化重構的過程。
Fig. 3
Fig. 3是一棵敗者樹。規定數大者敗。
1. b3 PK b4,b3勝b4負,內部結點ls[4]的值為4;
2. b3 PK b0,b3勝b0負,內部結點ls[2]的值為0;
3. b1 PK b2,b1勝b2負,內部結點ls[3]的值為2;
4. b3 PK b1,b3勝b1負,內部結點ls[1]的值為1;
5. 在根結點ls[1]上又加了一個結點ls[0]=3,記錄的最后的勝者。
敗者樹重構過程如下:
· 將新進入選擇樹的結點與其父結點進行比賽:將敗者存放在父結點中;而勝者再與上一級的父結點比較。
· 比賽沿着到根結點的路徑不斷進行,直到ls[1]處。把敗者存放在結點ls[1]中,勝者存放在ls[0]中。
Fig. 4
Fig. 4是當b3變為13時,敗者樹的重構圖。
注意,敗者樹的重構跟勝者樹是不一樣的,敗者樹的重構只需要與其父結點比較。對照Fig. 3來看,b3與結點ls[4]的原值比較,ls[4]中存放的原值是結點4,即b3與b4比較,b3負b4勝,則修改ls[4]的值為結點3。同理,以此 類推,沿着根結點不斷比賽,直至結束。
由上可知,敗者樹簡化了重構。敗者樹的重構只是與該結點的父結點的記錄有關,而勝者樹的重構還與該結點的兄弟結點有關。
二 使用敗者樹加快合並排序
外部排序最耗時間的操作時磁盤讀寫,對於有m個初始歸並段,k路平衡的歸並排序,磁盤讀寫次數為
|logkm|,可見增大k的值可以減少磁盤讀寫的次數,但增大k的值也會帶來負面效應,即進行k路合並
的時候會增加算法復雜度,來看一個例子。
把n個整數分成k組,每組整數都已排序好,現在要把k組數據合並成1組排好序的整數,求算法復雜度
u1: xxxxxxxx
u2: xxxxxxxx
u3: xxxxxxxx
.......
uk: xxxxxxxx
算法的步驟是:每次從k個組中的首元素中選一個最小的數,加入到新組,這樣每次都要比較k-1次,故
算法復雜度為O((n-1)*(k-1)),而如果使用敗者樹,可以在O(logk)的復雜度下得到最小的數,算法復雜
度將為O((n-1)*logk), 對於外部排序這種數據量超大的排序來說,這是一個不小的提高。