如何使用256M內存對2G數據進行排序——外部排序算法

春招的時候在某養豬場面試,面試官問了一個問題:「如何用256M內存的機器對一個2G的數據進行排序」。以前沒看過這方面的內容,想了一下說用歸併排序,而後簡略的說了一下個人想法。如今再來看書裏關於外部排序的內容,當時的大方向沒錯,可是剩下的具體實現、外部空間複雜度計算、時間複雜度計算和優化等都沒考慮到位。面試

由於計算機的外部訪問是很是慢的(相對比從內存讀數據),若是使用和「把數據所有讀入內存而後排序」相同的算法,再加上外部存儲例如磁帶是隻能順序訪問,那麼任何算法都須要(N^2)次外部數據訪問,將是很是可怕的耗時。因此須要有專門用於外部排序的算法。算法

  • 第一部分:介紹基於歸併排序的簡單算法
  • 第二部分:在簡單算法的基礎上讓其支持多路歸併能夠提升效率
  • 第三部分:首先在簡單算法上應用多相合並,能夠節約外部存儲的空間,而後擴展到多路歸併上
  • 第四部分:針對用於歸併的順串進行改造,在特定的狀況下能夠提升算法效率。

簡單算法

使用歸併排序的思想,簡單的雙路歸併須要四盤磁帶(就是外部存儲)。最初的數據在T_a1上,內存爲M,就是每次可使用排序算法對M個數據進行排序。優化

  1. 依次從T_a1上讀入M數據,進行排序。
  2. 而後交替的輸出到T_b1和T_b2上。每組排過序的記錄叫作一個順串
  3. 將 T_b1和T_b2的第一個順串取出來將二者合併(過程參考歸併算法),將結果輸出到T_a1上。
  4. 繼續上一個步驟,交替的輸出到T_a1和T_a2上。直到T_b1或T_b2爲空。若是剩下一個順串,拷貝到適當的磁帶上。
  5. 這樣咱們在T_a1和T_a2上獲得長度爲M的順串,重複上面的過程,知道獲得長度爲N的順串。

示例: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-路合併:

  1. T_1上有34個順串長度的數據,能夠選擇排序後在T_二、T_3上分別輸出17個順串。
  2. 合併輸出到T _1上,T_1上有17個順串。
  3. 將8個順串從T_1拷貝到T_2上,而後合併到T_3上,這時候T_3有9個順串。
  4. 每次拷貝二分之一個順串到一個空的磁帶上,而後合併到剩下的那個空的磁帶上。

能夠優化一下,讓每次合併完成以後自然造成兩個磁帶有順串,一個爲空的情景:

  1. 把T_1上的數據排序後,把21個順串放到T_2上面,13個放到T_3上。
  2. 合併以後,T_3爲空,T_1上有13個順串,T_2上有8個順串。
  3. 合併,T_2空,T_3上有8個順串,T_1上有5個。重複這個過程。

第一步分配的策略是:若是總順串的數量是斐波那契數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長度的順串。

  1. M個數據被讀入內存,並放到一個優先隊列中。執行一次DeleteMin把最小記錄輸出到磁帶上。
  2. 內存空出一個位置,在從磁帶讀入下一個記錄,若是比剛剛輸出的數據要大,放入優先隊列。不然把這個新元素存入優先隊列的死區(dead space)。
  3. 重複上一個步驟,直到優先隊列的大小爲0,結束第一個順串的構造。
  4. 使用死區中的全部元素創建一個新的優先隊列,重複1,2,3的過程。

替換選擇在一些狀況下,若是說大部分的數都是逆序的,效果並不比表標準算法好。可是,若是輸入數據是大體順序的,那麼能夠第一步就產生很長的順串,減小來回歸併的趟數。

相關文章
相關標籤/搜索