100億數據找出最大的1000個數字

這是互聯網領域一個比較經典的算法問題(top k),如何在巨大的數據中找出最大,或者訪問量最高的前10個,前100個或者前1000個數據。比如在2億用戶記錄中找出信用等級最高的,在上億個搜索詞彙中找出被搜索次數最高的10個關鍵字。前提是數據存儲在文件中

一般遇到這個問題,第一反應會想到排序,但是稍微對內存有點了解的人立刻都會否定這個答案,大量的數據導入內存且不說內存夠不夠,就算足夠服務器上其他的服務都不需要內存了嗎?那麼接下來的思路就是,如果不允許全部導入內存,還要找出最大的1000個數字,那就要用到外部排序(選擇)的算法了。

解決的思路有下面幾個:

1.採用小頂堆算法,我們知道完全二叉樹有幾個非常重要的特性,就是假如該二叉樹中總共有N個節點,那麼該二叉樹的深度就是log2N,對於小頂堆來說移動根元素到 底部或者移動底部元素到根部只需要log2N,相比N來說時間複雜度優化太多了(1億的logN值是26-27的一個浮點數)。基本的思路就是先從文件中取出1000個元素構建一個小頂堆數組k,然後依次對剩下的100億-1000個數字進行遍歷m,如果m大於小頂堆的根元素,即k[0],那麼用m取代k[0],對新的數組進行重新構建組成一個新的小頂堆。這個算法的時間複雜度是O((100億-1000)log(1000)),即O((N-M)logM),空間複雜度是M

這個算法優點是性能尚可,空間複雜度低,IO讀取比較頻繁,對系統壓力大。

2.採用分區法,將100億個數據分成1000個分區,每個分區上1000萬個數據,在每個分區上上再細分成100個分區,即總共分成1000*100個分區,然後啓動多線程進行處理,各個分區上採用小頂堆算法取出最大的1000個數據,分層進行合併然後重新計算不同層上的最大1000個數,最終遞歸到最上層。但linux系統上一個進程能啓動的默認線程數是1024個,所以要麼調整最大線程數,要麼在線程調用處做一些處理,比如一個線程完成一個分區之後再去處理相鄰的分區,或者在分區的時候把所有的分區數目限制在1024之內。這個算法切合了map-reduce的思想,利用了多線程和多處理器的優勢,減少了多餘的比較和IO讀取,性能比第一種會更好但算法更復雜一點,要考慮的情況也更多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章