今天看到一篇賴永浩大牛的部落格,由一道筆試題目談演算法優化。http://blog.csdn.net/lanphaday/archive/2008/12/19/3547776.aspx
。題目原題是從10億個浮點數中找出最大的一萬個,賴的文章主要是講如何去優化演算法,並不是側重在這道題目,所以對其進行了簡化。
題目:從1億個整數中找出最大的一萬個。
賴永浩的解法:
基本思想是維持乙個陣列記錄當前最大的一萬個數,每來乙個新數將其與陣列中最小的數比較、替換。該陣列初始先排序,第一次交換後陣列就分成了兩部分,後面9999個有序,最小的是1號元素,前面的部分無序。找當前最小元素需要遍歷無序部分和有序部分的第乙個。當無序部分長度達到600時,就對無序部分排序,然後兩部分合併重新得到完整有序陣列。
(開始我覺得600這數字太大,後來試驗果然這樣最優,之所以看起來比較大是因為,每次合併也需要時間,所以不能頻繁排序)
我的想法一:用堆
這也是經典的題目了,一般來說用堆來做。這兩種解法其實一樣,只是找最小值時候不同罷了。我感覺賴的解法不如堆高效,就試了下,確實堆要快很多。在我的機子上賴的解法要1000ms,用堆只要550ms。(破機器,我開始以為我**也的不好,後來拷賴的**也是比他的時間慢幾倍)
後來發現其中250ms用在一億次的迴圈體上(原因是資料太簡單了,具體見下文),所以用了迴圈展開,展開5次,時間變成了390ms。
我的想法二:
找第k個數有中位數之中位數法(傳說中的來自聖經的演算法第五名:bfprt)。這種方法k很大的時候很快,這裡找一億個數中的第一萬個,可以修改一下。將一億個數100個一組共100w組,每組找出最大的,然後從這100w個數中找出最大的1w個,整體一億個數最大的1w個就在對應的1w組共計100w個數中。
我的想法三:
可以建立65536個計數器,記錄每個資料段有多少個數,countarr[ sourcearr[i]>>16 ]++;。一遍遍歷之後就可以知道第一萬個數在哪個資料段的第多少個數。然後整體進行再進行一次遍歷,這次countarr[ sourcearr[i] & 0xffff]++,另外sourcearr[i]所在資料段整體都在前1萬的直接輸出。
這個方法缺點是要兩次遍歷,如果資料量太大不能一次讀入記憶體,就需要兩次磁碟讀取,這是堅決不能接受的。上一種方法中也可能要讀兩次,但第二次只需要讀一小部分資料。
有人提出用4g/8的bitmap,直接找出前1萬個,這不可行,因為數字可能有重複,這樣只能找出乙個範圍,仍要二次遍歷。
方法的比較
在寫後面的解法時無意中發現乙個一億的空迴圈就要250ms!堆竟然如此快?檢查了下,因為資料生成太簡單了sourcearr[i] = (rand()<<16) + rand()迴圈一億次,產生的隨機數有一點不隨機的地方在於分布太平均了,導致總的替換次數僅為10w次左右。在這種情況下維持乙個一萬的最大陣列是非常明智的選擇,不管是用堆還是賴的解法。(極端的例子,簡單地直接隨機生成1w個0到9之間的數,找出其中第1k小的,則不是0就是1,只用統計下0的個數就知道了)實際的資料不一定是這樣,換個複雜點的隨機數生成就不一樣了,而後面兩種方法都不太受資料分布的影響。所以要找最優的解法要根據實際資料的情況。
回歸原問題:10億個浮點數中找出最大的一萬個
浮點數就不能採用最後一種方法了,其它幾種方法仍然可行。不過最佳答案估計還是分組,每組100w,找出最大的1w,然後各組合併。這樣還可以做並行處理,很符合mapreduce。
《每天一道筆試題》(1)
上海華為的一道關於指標方面的程式設計題 int a nsize 其中隱藏著若干0,其餘非0整數,寫乙個函式int func int a,int nsize 使a把0移至後面,非0整數移至陣列前面並保持有序,返回值為原資料中第乙個元素為0的下標。盡可能不使用輔助空間且考慮效率及異常問題,注釋規範且給出...
也論從1億個整數中找出最大的1萬個(下)
改寫adjust heap函式,讓要插入值與插入點的孩子節點進行比較判斷。template class distance,class tp,class compare inline void adjust heap tp first,distance holeindex,distance len,d...
從1億個ip中找出訪問次數最多的IP
問題一 怎麼在海量資料中找出重複次數最多的乙個 演算法思想 方案1 先做hash,然後求模對映為小檔案,求出每個小檔案中重複次數最多的乙個,並記錄重複次數。然後找出上一步求出的資料中重複次數最多的乙個就是所求 如下 問題二 日誌中記錄了使用者的ip,找出訪問次數最多的ip。演算法思想 ip位址最多有...