前兩天面試3面學長問我的這個問題(想說teg的3個面試學長都是好和藹,希望能完成最後一面,各方面原因造成我無比想去鵝場的心已經按捺不住了),這個問題還是建立最小堆比較好一些。
先拿10000個數建堆,然後一次新增剩餘元素,如果大於堆頂的數(10000中最小的),將這個數替換堆頂,並調整結構使之仍然是乙個最小堆,這樣,遍歷完後,堆中的10000個數就是所需的最大的10000個。建堆時間複雜度是o(mlogm),演算法的時間複雜度為o(nmlogm)(n為10億,m為10000)。
優化的方法:可以把所有10億個資料分組存放,比如分別放在1000個檔案中。這樣處理就可以分別在每個檔案的10^6個資料中找出最大的10000個數,合併到一起在再找出最終的結果。
以上就是面試時簡單提到的內容,下面整理一下這方面的問題:
針對top k類問題,通常比較好的方案是分治+trie樹/hash+小頂堆(就是上面提到的最小堆),即先將資料集按照hash方法分解成多個小資料集,然後使用trie樹活著hash統計每個小資料集中的query詞頻,之後用小頂堆求出每個資料集中出現頻率最高的前k個數,最後在所有top k中求出最終的top k。
最容易想到的方法是將資料全部排序,然後在排序後的集合中進行查詢,最快的排序演算法的時間複雜度一般為o(nlogn),如快速排序。但是在32位的機器上,每個float型別佔4個位元組,1億個浮點數就要占用400mb的儲存空間,對於一些可用記憶體小於400m的計算機而言,很顯然是不能一次將全部資料讀入記憶體進行排序的。其實即使記憶體能夠滿足要求(我機器記憶體都是8gb),該方法也並不高效,因為題目的目的是尋找出最大的10000個數即可,而排序卻是將所有的元素都排序了,做了很多的無用功。
第二種方法為區域性淘汰法,該方法與排序方法類似,用乙個容器儲存前10000個數,然後將剩餘的所有數字——與容器內的最小數字相比,如果所有後續的元素都比容器內的10000個數還小,那麼容器內這個10000個數就是最大10000個數。如果某一後續元素比容器內最小數字大,則刪掉容器內最小元素,並將該元素插入容器,最後遍歷完這1億個數,得到的結果容器中儲存的數即為最終結果了。此時的時間複雜度為o(n+m^2),其中m為容器的大小,即10000。
第三種方法是分治法,將1億個資料分成100份,每份100萬個資料,找到每份資料中最大的10000個,最後在剩下的100*10000個資料裡面找出最大的10000個。如果100萬資料選擇足夠理想,那麼可以過濾掉1億資料裡面99%的資料。100萬個資料裡面查詢最大的10000個資料的方法如下:用快速排序的方法,將資料分為2堆,如果大的那堆個數n大於10000個,繼續對大堆快速排序一次分成2堆,如果大的那堆個數n大於10000個,繼續對大堆快速排序一次分成2堆,如果大堆個數n小於10000個,就在小的那堆裡面快速排序一次,找第10000-n大的數字;遞迴以上過程,就可以找到第1w大的數。參考上面的找出第1w大數字,就可以類似的方法找到前10000大數字了。此種方法需要每次的記憶體空間為10^6*4=4mb,一共需要101次這樣的比較。
第四種方法是hash法。如果這1億個書裡面有很多重複的數,先通過hash法,把這1億個數字去重複,這樣如果重複率很高的話,會減少很大的記憶體用量,從而縮小運算空間,然後通過分治法或最小堆法查詢最大的10000個數。
第五種方法採用最小堆。首先讀入前10000個數來建立大小為10000的最小堆,建堆的時間複雜度為o(mlogm)(m為陣列的大小即為10000),然後遍歷後續的數字,並於堆頂(最小)數字進行比較。如果比最小的數小,則繼續讀取後續數字;如果比堆頂數字大,則替換堆頂元素並重新調整堆為最小堆。整個過程直至1億個數全部遍歷完為止。然後按照中序遍歷的方式輸出當前堆中的所有10000個數字。該演算法的時間複雜度為o(nmlogm),空間複雜度是10000(常數)。
實際上,最優的解決方案應該是最符合實際設計需求的方案,在時間應用中,可能有足夠大的記憶體,那麼直接將資料扔到記憶體中一次性處理即可,也可能機器有多個核,這樣可以採用多執行緒處理整個資料集。
下面針對不容的應用場景,分析了適合相應應用場景的解決方案。
(1)單機+單核+足夠大記憶體
如果需要查詢10億個查詢次(每個佔8b)中出現頻率最高的10個,考慮到每個查詢詞佔8b,則10億個查詢次所需的記憶體大約是10^9 * 8b=8gb記憶體。如果有這麼大記憶體,直接在記憶體中對查詢次進行排序,順序遍歷找出10個出現頻率最大的即可。這種方法簡單快速,使用。然後,也可以先用hashmap求出每個詞出現的頻率,然後求出頻率最大的10個詞。
(2)單機+多核+足夠大記憶體
這時可以直接在記憶體總使用hash方法將資料劃分成n個partition,每個partition交給乙個執行緒處理,執行緒的處理邏輯同(1)類似,最後乙個執行緒將結果歸併。
該方法存在乙個瓶頸會明顯影響效率,即資料傾斜。每個執行緒的處理速度可能不同,快的執行緒需要等待慢的執行緒,最終的處理速度取決於慢的執行緒。而針對此問題,解決的方法是,將資料劃分成c×n個partition(c>1),每個執行緒處理完當前partition後主動取下乙個partition繼續處理,知道所有資料處理完畢,最後由乙個執行緒進行歸併。
(3)單機+單核+受限記憶體
這種情況下,需要將原資料檔案切割成乙個乙個小檔案,如次啊用hash(x)%m,將原檔案中的資料切割成m小檔案,如果小檔案仍大於記憶體大小,繼續採用hash的方法對資料檔案進行分割,知道每個小檔案小於記憶體大小,這樣每個檔案可放到記憶體中處理。採用(1)的方法依次處理每個小檔案。
(4)多機+受限記憶體
這種情況,為了合理利用多台機器的資源,可將資料分發到多台機器上,每台機器採用(3)中的策略解決本地的資料。可採用hash+socket方法進行資料分發。
從實際應用的角度考慮,(1)(2)(3)(4)方案並不可行,因為在大規模資料處理環境下,作業效率並不是首要考慮的問題,演算法的擴充套件性和容錯性才是首要考慮的。演算法應該具有良好的擴充套件性,以便資料量進一步加大(隨著業務的發展,資料量加大是必然的)時,在不修改演算法框架的前提下,可達到近似的線性比;演算法應該具有容錯性,即當前某個檔案處理失敗後,能自動將其交給另外乙個執行緒繼續處理,而不是從頭開始處理。
top k問題很適合採用mapreduce框架解決,使用者只需編寫乙個map函式和兩個reduce 函式,然後提交到hadoop(採用mapchain和reducechain)上即可解決該問題。具體而言,就是首先根據資料值或者把資料hash(md5)後的值按照範圍劃分到不同的機器上,最好可以讓資料劃分後一次讀入記憶體,這樣不同的機器負責處理不同的數值範圍,實際上就是map。得到結果後,各個機器只需拿出各自出現次數最多的前n個資料,然後彙總,選出所有的資料中出現次數最多的前n個資料,這實際上就是reduce過程。對於map函式,採用hash演算法,將hash值相同的資料交給同乙個reduce task;對於第乙個reduce函式,採用hashmap統計出每個詞出現的頻率,對於第二個reduce 函式,統計所有reduce task,輸出資料中的top k即可。
直接將資料均分到不同的機器上進行處理是無法得到正確的結果的。因為乙個資料可能被均分到不同的機器上,而另乙個則可能完全聚集到乙個機器上,同時還可能存在具有相同數目的資料。
(1)有10000000個記錄,這些查詢串的重複度比較高,如果除去重複後,不超過3000000個。乙個查詢串的重複度越高,說明查詢它的使用者越多,也就是越熱門。請統計最熱門的10個查詢串,要求使用的記憶體不能超過1gb。
(2)有10個檔案,每個檔案1gb,每個檔案的每一行存放的都是使用者的query,每個檔案的query都可能重複。按照query的頻度排序。
(3)有乙個1gb大小的檔案,裡面的每一行是乙個詞,詞的大小不超過16個位元組,記憶體限制大小是1mb。返回頻數最高的100個詞。
(4)提取某日訪問**次數最多的那個ip。
(5)10億個整數找出重複次數最多的100個整數。
(6)搜尋的輸入資訊是乙個字串,統計300萬條輸入資訊中最熱門的前10條,每次輸入的乙個字串為不超過255b,記憶體使用只有1gb。
(7)有1000萬個身份證號以及他們對應的資料,身份證號可能重複,找出出現次數最多的身份證號。
在海量資料中查詢出重複出現的元素或者去除重複出現的元素也是常考的問題。針對此類問題,一般可以通過位圖法實現。例如,已知某個檔案內包含一些**號碼,每個號碼為8位數字,統計不同號碼的個數。
本題最好的解決方法是通過使用位圖法來實現。8位整數可以表示的最大十進位制數值為99999999。如果每個數字對應於位圖中乙個bit位,那麼儲存8位整數大約需要99mb。因為1b=8bit,所以99mbit折合成記憶體為99/8=12.375mb的記憶體,即可以只用12.375mb的記憶體表示所有的8位數**號碼的內容。
**:
海量資料中找出前k大數(topk問題)
前兩天面試3面學長問我的這個問題 想說teg的3個面試學長都是好和藹,希望能完成最後一面,各方面原因造成我無比想去鵝場的心已經按捺不住了 這個問題還是建立最小堆比較好一些。先拿10000個數建堆,然後一次新增剩餘元素,如果大於堆頂的數 10000中最小的 將這個數替換堆頂,並調整結構使之仍然是乙個最...
在海量資料中找到最大的前K個數(top K問題)
問題分析 資料是海量的,可能達到10億或者100億以上,只需要找最大的前100個數。所以將資料一次性排序然後取前100個是不太可取的操作。做了很多無用功,並且記憶體一次性也載入不了海量資料。解決方案 方案一 堆。一般說在很多資料中取前多少個資料,我們都會想到堆,這裡我們使用堆來解決問題。首先取k個數...
海量資料中找top K專題
這種題目就是分治 堆排序。為啥分治?因為數太多了,全部載入進記憶體不夠用,所以分配到多台機器中,或者多個檔案中,但具體分成多少份,視情況而定,只要保證滿足記憶體限制即可。什麼,如何分?hash num numoffiles。為啥堆排序?首先堆排序是一種選擇排序,比一般的選擇排序時間複雜度要低,額外的...