海量資料場景面試題:出現頻率最高的 100 個詞

柒墨轩發表於2024-07-18

題目描述

假如有一個 1G 大小的檔案,檔案裡每一行是一個詞,每個詞的大小不超過 16 bytes,要求返回出現頻率最高的 100 個詞。記憶體限制是 10M。

解法1

由於記憶體限制,所以我們沒有辦法一次性把大檔案裡面的所有內容一次性讀取到記憶體中去。

對此我們可以採用分治的策略來實現,把一個大檔案分解成多個小檔案,保證每個檔案的大小小於 10 M,進而直接將單個小檔案讀取到記憶體中進行處理。

第一步,首先遍歷一遍大檔案,對遍歷到的每個詞 x ,執行 hash(x)% 500,將結果為 i 的詞存放到檔案 f(i)中,遍歷結束之後,可以得到 500 個小檔案,每個小檔案的大小為 2 M 左右;
第二步,接著統計每個小檔案中出現頻率最高的 100 個詞。可以用 HashMap 來實現,其中 key 為詞,value 為該次出現的頻率。(示意程式碼)

BufferedReader br = new BufferedReader(new FileReader(fin));
String line = null;
while ((line = br.readLine()) != null) {
    if(map.containsKey(line)){
        map.put(line,map.get(x) + 1)
    }else{
        map.put(line,1);
    }
}

br.close();

對於遍歷到的詞 x,如果在 map 中不存在,則執行 map.put(x,1)。

若存在,則執行 map.put(x,map.get(x) + 1),將該詞出現的次數 + 1。

第三步,在第二步中找出了每個檔案出現頻率最高的 100 個詞之後,透過維護一個小頂堆來找出所有小檔案中出現頻率最高的 100 個詞。

具體方法是,遍歷第一個檔案,把第一個檔案中出現頻率最高的 100 個詞構建成一個小頂堆。

如果第一個檔案中詞的個數小於 100,可以繼續遍歷第二個檔案,直到構建好有 100 個結點的小頂堆為止。

繼續遍歷其他小檔案,如果遍歷到的詞的出現次數大於堆頂詞的出現次數,可以用新遍歷道的詞替換堆頂的詞,然後重新調整這個堆位小頂堆。

當遍歷完所有小檔案後,這個小頂堆中的詞就是出現頻率最高的 100 個詞。

總結

總結一下,這個解法的主要思路如下:採用分治的思想,進行雜湊取餘使用 HashMap 統計每個小檔案單詞出現的次數使用小頂堆,遍歷步驟 2 中的小檔案,找到詞頻 top 100 的單詞。

很容易就會發現一個問題,如果第二步中,如果這個 1 G 的大檔案中有某個詞的頻率太高,可能導致小檔案大小超過 10 M,這種情況該怎麼處理呢?

在此疑問上,我們提出了第二種解法。

解法2

第一步:使用多路歸併排序堆大檔案進行排序,這樣的話,相同的單詞一定是緊挨著的。

多路歸併排序對大檔案排序的步驟如下:將檔案按照順序切分成大小不超過 2 M 的小檔案,總共 500 個小檔案使用 10 MB 記憶體分別對 500 個小檔案中的單詞進行排序使用一個大小為 500 的堆,對 500 個小檔案進行多路排序,然後將結果寫到一個大檔案中。

其中第三步,對 500 個小檔案進行多路排序的思路如下:初始化一個最小堆,大小就是有序小檔案的個數 500。堆中的每個節點存放每個有序小檔案對應的輸入流。按照每個有序檔案中的下一行資料對所有檔案輸入流進行排序,單詞小的輸入檔案流放在堆頂。拿出堆頂的輸入流,並且將下一行資料寫入到最終排序的檔案中,如果拿出來的輸入流還有資料的話,那麼就將這個輸入流再次新增到棧中。否則說明該檔案輸入流中沒有資料了,那麼可以關閉這個流。迴圈這個過程,直到所有檔案輸入流中沒有資料為止。

第二步:初始化一個 100 個節點的小頂堆,用於儲存 100 個出現頻率最高的單詞。遍歷整個檔案,一個單詞一個單詞地從檔案中讀取出來,並且進行計數。等到遍歷的單詞和上一個單詞不同的話,那麼上一個單詞及其頻率如果大於堆頂的詞的頻率,那麼放在堆中。否則不放。

最終,小頂堆就是出現頻率前 100 的單詞了。

小結

解法 2 相對於解法 1,其更加嚴謹,如果某個詞詞頻過高或者整個檔案都是同一個單詞的話,解法 1 不適用。

相關文章