春招的時候在某養豬場面試,面試官問了一個問題:「如何用256M內存的機器對一個2G的數據進行排序」。以前沒看過這方面的內容,想了一下說用歸併排序,而後簡略的說了一下個人想法。如今再來看書裏關於外部排序的內容,當時的大方向沒錯,可是剩下的具體實現、外部空間複雜度計算、時間複雜度計算和優化等都沒考慮到位。面試
由於計算機的外部訪問是很是慢的(相對比從內存讀數據),若是使用和「把數據所有讀入內存而後排序」相同的算法,再加上外部存儲例如磁帶是隻能順序訪問,那麼任何算法都須要(N^2)次外部數據訪問,將是很是可怕的耗時。因此須要有專門用於外部排序的算法。算法
使用歸併排序的思想,簡單的雙路歸併須要四盤磁帶(就是外部存儲)。最初的數據在T_a1上,內存爲M,就是每次可使用排序算法對M個數據進行排序。優化
示例:spa
初始狀態:排序
T_a1 | 81 94 11 96 12 35 17 99 28 58 41 75 15 |
---|---|
T_a2 | |
T_b1 | |
T_b2 |
第1,第2步以後:隊列
T_a1 | |||
---|---|---|---|
T_a2 | |||
T_b1 | 11 81 94 | 17 28 99 | 15 |
T_b2 | 12 35 96 | 41 58 75 |
第3,第4步以後:內存
T_a1 | 11 12 35 81 94 96 | 15 |
---|---|---|
T_a2 | 17 28 41 58 75 99 | |
T_b1 | ||
T_b2 |
重複這個從Ta1 Ta2歸併到Tb1 Tb2,從Tb1 Tb2歸併到Ta1 Ta2的過程:table
T_a1 | |
---|---|
T_a2 | |
T_b1 | 11 12 17 28 35 51 58 75 81 94 96 99 |
T_b2 | 15 |
T_a1 | 11 12 15 17 28 35 51 58 75 81 94 96 99 |
---|---|
T_a2 | |
T_b1 | |
T_b2 |
完成!效率
咱們「從Ta1 Ta2歸併到Tb1 Tb2,從Tb1 Tb2歸併到Ta1 Ta2」這個過程用了3趟。由於第一次順串的長度爲M,在二路歸併的狀況下,每次將順串的長度延長一倍,須要次數爲:基礎
上面的簡單算法就是二路合併,咱們將其擴展到通常狀態——k路合併。
k路合併須要2k盤磁帶,每次將順串的長度擴充爲原來的k倍。在合併的時候,在k個元素中發現最小值是比二路合併複雜的地方,可使用優先隊列。多路合併和二路合併區別不大,就不舉例子了。k路合併須要的趟數是:
在上面的多路合併中,k-路合併須要2k盤磁帶。使用多相合並後,只使用2k-1盤磁帶也能夠達到相同的效果,能夠節省外部存儲空間。下面看如何用三盤磁帶完成2-路合併:
能夠優化一下,讓每次合併完成以後自然造成兩個磁帶有順串,一個爲空的情景:
第一步分配的策略是:若是總順串的數量是斐波那契數F_N,那麼將順串分解成F(N-1)和 F(N-2)。若是不是斐波那契數,須要用一些啞順串(dummy run)來填補磁帶。
將上面三盤磁帶完成2-路合併擴展到k-路的多相合並,順串分解使用k階斐波那契數列:
5階斐波那契數列就是:
0,0,0,0,1,1,2,4,8,16,31,61……
上面的排序算法中,第一步順串的生成都使用了常規內存排序的方法,每次能夠生成和內存容量同樣大的有序數列。在替換選擇算法中,無序數列平都可以生成2M長度的順串。
替換選擇在一些狀況下,若是說大部分的數都是逆序的,效果並不比表標準算法好。可是,若是輸入數據是大體順序的,那麼能夠第一步就產生很長的順串,減小來回歸併的趟數。