海量數據的處理【位圖】【分治】【hashmap】【大頂堆】

目錄

如何找出排名前 500 的數?

題目描述

解答思路

如何按照 query 的頻度排序?

題目描述

解答思路

方法總結

如何從 5 億個數中找出中位數?

題目描述

解答思路

方法總結

如何統計不同電話號碼的個數?

題目描述

解答思路

方法總結

如何查詢最熱門的查詢串?

題目描述

解答思路

方法總結

如何在大量的數據中判斷一個數是否存在?

題目描述

解答思路

方法總結

如何在大量的數據中找出不重複的整數?

題目描述

解答思路

方法總結

如何找出某一天訪問百度網站最多的 IP?

題目描述

解答思路

方法總結

如何從大量數據中找出高頻詞?

題目描述

解答思路

方法總結

如何從大量的 URL 中找出相同的 URL?

題目描述

解答思路

方法總結

總結:

不同數據類型字節數


如何找出排名前 500 的數?

題目描述

有 20 個數組,每個數組有 500 個元素,並且有序排列。如何在這 20*500 個數中找出前 500 的數?

解答思路

對於 TopK 問題,最常用的方法是使用堆排序。對本題而言,假設數組降序排列,可以採用以下方法:

首先建立大頂堆,堆的大小爲數組的個數,即爲 20,把每個數組最大的值存到堆中。

接着刪除堆頂元素,保存到另一個大小爲 500 的數組中,然後向大頂堆插入刪除的元素所在數組的下一個元素。

重複上面的步驟,直到刪除完第 500 個元素,也即找出了最大的前 500 個數。

爲了在堆中取出一個數據後,能知道它是從哪個數組中取出的,從而可以從這個數組中取下一個值,可以把數組的指針存放到堆中,對這個指針提供比較大小的方法。

 https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-rank-top-500-numbers.md


如何按照 query 的頻度排序?

題目描述

有 10 個文件,每個文件大小爲 1G,每個文件的每一行存放的都是用戶的 query,每個文件的 query 都可能重複。要求按照 query 的頻度排序。

解答思路

如果 query 的重複度比較大,可以考慮一次性把所有 query 讀入內存中處理;如果 query 的重複率不高,那麼可用內存不足以容納所有的 query,這時候就需要採用分治法或其他的方法來解決。

方法一:HashMap 法

如果 query 重複率高,說明不同 query 總數比較小,可以考慮把所有的 query 都加載到內存中的 HashMap 中。接着就可以按照 query 出現的次數進行排序。

方法二:分治法

分治法需要根據數據量大小以及可用內存的大小來確定問題劃分的規模

對於這道題,可以順序遍歷 10 個文件中的 query,通過 Hash 函數 hash(query) % 10 把這些 query 劃分到 10 個小文件中。之後對每個小文件使用 HashMap 統計 query 出現次數,根據次數排序並寫入到零外一個單獨文件中。

接着對所有文件按照 query 的次數進行排序,這裏可以使用歸併排序(由於無法把所有 query 都讀入內存,因此需要使用外排序)。

方法總結

  • 內存若夠,直接讀入進行排序;
  • 內存不夠,先劃分爲小文件,小文件排好序後,整理使用外排序進行歸併。

https://github.com/doocs/advanced-java/blob/master/docs/big-data/sort-the-query-strings-by-counts.md


如何從 5 億個數中找出中位數?

題目描述

從 5 億個數中找出中位數。數據排序後,位置在最中間的數就是中位數。當樣本數爲奇數時,中位數爲 第 (N+1)/2 個數;當樣本數爲偶數時,中位數爲 第 N/2 個數與第 1+N/2 個數的均值。

解答思路

如果這道題沒有內存大小限制,則可以把所有數讀到內存中排序後找出中位數。但是最好的排序算法的時間複雜度都爲 O(NlogN)。這裏使用其他方法。

方法一:雙堆法

維護兩個堆,一個大頂堆,一個小頂堆。大頂堆中最大的數小於等於小頂堆中最小的數;保證這兩個堆中的元素個數的差不超過 1。

若數據總數爲偶數,當這兩個堆建好之後,中位數就是這兩個堆頂元素的平均值。當數據總數爲奇數時,根據兩個堆的大小,中位數一定在數據多的堆的堆頂

class MedianFinder {
    
    private PriorityQueue<Integer> maxHeap;
    private PriorityQueue<Integer> minHeap;

    /** initialize your data structure here. */
    public MedianFinder() {
        maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
        minHeap = new PriorityQueue<>(Integer::compareTo);
    }
    
    public void addNum(int num) {
        if (maxHeap.isEmpty() || maxHeap.peek() > num) {
            maxHeap.offer(num);
        } else {
            minHeap.offer(num);
        }
        
        int size1 = maxHeap.size();
        int size2 = minHeap.size();
        if (size1 - size2 > 1) {
            minHeap.offer(maxHeap.poll());
        } else if (size2 - size1 > 1) {
            maxHeap.offer(minHeap.poll());
        }
    }
    
    public double findMedian() {
        int size1 = maxHeap.size();
        int size2 = minHeap.size();
        
        return size1 == size2 
            ? (maxHeap.peek() + minHeap.peek()) * 1.0 / 2
            : (size1 > size2 ? maxHeap.peek() : minHeap.peek());
    }
}

見 LeetCode No.295:https://leetcode.com/problems/find-median-from-data-stream/

以上這種方法,需要把所有數據都加載到內存中。當數據量很大時,就不能這樣了,因此,這種方法適用於數據量較小的情況。5 億個數,每個數字佔用 4B,總共需要 2G 內存。如果可用內存不足 2G,就不能使用這種方法了,下面介紹另一種方法。

方法二:分治法

分治法的思想是把一個大的問題逐漸轉換爲規模較小的問題來求解。

對於這道題,順序讀取這 5 億個數字,對於讀取到的數字 num,如果它對應的二進制中最高位爲 1,則把這個數字寫到 f1 中,否則寫入 f0 中。通過這一步,可以把這 5 億個數劃分爲兩部分,而且 f0 中的數都大於 f1 中的數(最高位是符號位)。

劃分之後,可以非常容易地知道中位數是在 f0 還是 f1 中。假設 f1 中有 1 億個數,那麼中位數一定在 f0 中,且是在 f0 中,從小到大排列的第 1.5 億個數與它後面的一個數的平均值。

提示,5 億數的中位數是第 2.5 億與右邊相鄰一個數求平均值。若 f1 有一億個數,那麼中位數就是 f0 中從第 1.5 億個數開始的兩個數求得的平均值。

對於 f0 可以用次高位的二進制繼續將文件一分爲二,如此劃分下去,直到劃分後的文件可以被加載到內存中,把數據加載到內存中以後直接排序,找出中位數。

注意,當數據總數爲偶數,如果劃分後兩個文件中的數據有相同個數,那麼中位數就是數據較小的文件中的最大值與數據較大的文件中的最小值的平均值。

方法總結

分治法,真香!

https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-mid-value-in-500-millions.md


如何統計不同電話號碼的個數?

題目描述

已知某個文件內包含一些電話號碼,每個號碼爲 8 位數字,統計不同號碼的個數。

解答思路

這道題本質還是求解數據重複的問題,對於這類問題,一般首先考慮位圖法。

對於本題,8 位電話號碼可以表示的號碼個數爲 10^8 個,即 1 億個。我們每個號碼用一個 bit 來表示,則總共需要 1 億個 bit,內存佔用約 100M。

思路如下

申請一個位圖數組,長度爲 1 億,初始化爲 0。然後遍歷所有電話號碼,把號碼對應的位圖中的位置置爲 1。遍歷完成後,如果 bit 爲 1,則表示這個電話號碼在文件中存在,否則不存在。bit 值爲 1 的數量即爲 不同電話號碼的個數。

方法總結

求解數據重複問題,記得考慮位圖法

https://github.com/doocs/advanced-java/blob/master/docs/big-data/count-different-phone-numbers.md


如何查詢最熱門的查詢串?

題目描述

搜索引擎會通過日誌文件把用戶每次檢索使用的所有查詢串都記錄下來,每個查詢串的長度不超過 255 字節。

假設目前有 1000w 個記錄(這些查詢串的重複度比較高,雖然總數是 1000w,但如果除去重複後,則不超過 300w 個)。請統計最熱門的 10 個查詢串,要求使用的內存不能超過 1G。(一個查詢串的重複度越高,說明查詢它的用戶越多,也就越熱門。)

解答思路

每個查詢串最長爲 255B,1000w 個串需要佔用 約 2.55G 內存,因此,我們無法將所有字符串全部讀入到內存中處理。

方法一:分治法

分治法依然是一個非常實用的方法。

劃分爲多個小文件,保證單個小文件中的字符串能被直接加載到內存中處理,然後求出每個文件中出現次數最多的 10 個字符串;最後通過一個小頂堆統計出所有文件中出現最多的 10 個字符串。

方法可行,但不是最好,下面介紹其他方法。

方法二:HashMap 法

雖然字符串總數比較多,但去重後不超過 300w,因此,可以考慮把所有字符串及出現次數保存在一個 HashMap 中,所佔用的空間爲 300w*(255+4)≈777M(其中,4表示整數佔用的4個字節)。由此可見,1G 的內存空間完全夠用。

思路如下

首先,遍歷字符串,若不在 map 中,直接存入 map,value 記爲 1;若在 map 中,則把對應的 value 加 1,這一步時間複雜度 O(N)

接着遍歷 map,構建一個 10 個元素的小頂堆,若遍歷到的字符串的出現次數大於堆頂字符串的出現次數,則進行替換,並將堆調整爲小頂堆。

遍歷結束後,堆中 10 個字符串就是出現次數最多的字符串。這一步時間複雜度 O(Nlog10)

方法三:前綴樹法

方法二使用了 HashMap 來統計次數,當這些字符串有大量相同前綴時,可以考慮使用前綴樹來統計字符串出現的次數,樹的結點保存字符串出現次數,0 表示沒有出現。

思路如下

在遍歷字符串時,在前綴樹中查找,如果找到,則把結點中保存的字符串次數加 1,否則爲這個字符串構建新結點,構建完成後把葉子結點中字符串的出現次數置爲 1。

最後依然使用小頂堆來對字符串的出現次數進行排序。

方法總結

前綴樹經常被用來統計字符串的出現次數。它的另外一個大的用途是字符串查找,判斷是否有重複的字符串等

https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-hotest-query-string.md


如何在大量的數據中判斷一個數是否存在?

題目描述

給定 40 億個不重複的沒排過序的 unsigned int 型整數,然後再給定一個數,如何快速判斷這個數是否在這 40 億個整數當中?

解答思路

方法一:分治法

依然可以用分治法解決,方法與前面類似,就不再次贅述了。

方法二:位圖法

40 億個不重複整數,我們用 40 億個 bit 來表示,初始位均爲 0,那麼總共需要內存:4,000,000,000b≈512M。

我們讀取這 40 億個整數,將對應的 bit 設置爲 1。接着讀取要查詢的數,查看相應位是否爲 1,如果爲 1 表示存在,如果爲 0 表示不存在。

方法總結

判斷數字是否存在、判斷數字是否重複的問題位圖法是一種非常高效的方法。

https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-a-number-if-exists.md


如何在大量的數據中找出不重複的整數?

題目描述

在 2.5 億個整數中找出不重複的整數。注意:內存不足以容納這 2.5 億個整數。

解答思路

方法一:分治法

與前面的題目方法類似,先將 2.5 億個數劃分到多個小文件,用 HashSet/HashMap 找出每個小文件中不重複的整數,再合併每個子結果,即爲最終結果。

方法二:位圖法

位圖,就是用一個或多個 bit 來標記某個元素對應的值,而鍵就是該元素。採用位作爲單位來存儲數據,可以大大節省存儲空間。

位圖通過使用位數組來表示某些元素是否存在。它可以用於快速查找,判重,排序等。不是很清楚?我先舉個小例子。

假設我們要對 [0,7] 中的 5 個元素 (6, 4, 2, 1, 5) 進行排序,可以採用位圖法。0~7 範圍總共有 8 個數,只需要 8bit,即 1 個字節。首先將每個位都置 0:

0 0 0 0 0 0 0 0

然後遍歷 5 個元素,首先遇到 6,那麼將下標爲 6 的位的 0 置爲 1;接着遇到 4,把下標爲 4 的位 的 0 置爲 1:

0 0 0 0 1 0 1 0

依次遍歷,結束後,位數組是這樣的:

0 1 1 0 1 1 1 0

每個爲 1 的位,它的下標都表示了一個數:

for i in range(8):
    if bits[i] == 1:
        print(i)

這樣我們其實就已經實現了排序。

對於整數相關的算法的求解,位圖法是一種非常實用的算法。假設 int 整數佔用 4B,即 32bit,那麼我們可以表示的整數的個數爲 232。

那麼對於這道題,我們用 2 個 bit 來表示各個數字的狀態:

  • 00 表示這個數字沒出現過;
  • 01 表示這個數字出現過一次(即爲題目所找的不重複整數);
  • 10 表示這個數字出現了多次。

那麼這 232 個整數,總共所需內存爲 232*2b=1GB。因此,當可用內存超過 1GB 時,可以採用位圖法。假設內存滿足位圖法需求,進行下面的操作:

遍歷 2.5 億個整數,查看位圖中對應的位,如果是 00,則變爲 01,如果是 01 則變爲 10,如果是 10 則保持不變。遍歷結束後,查看位圖,把對應位是 01 的整數輸出即可。

方法總結

判斷數字是否重複的問題,位圖法是一種非常高效的方法。

https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-no-repeat-number.md



如何找出某一天訪問百度網站最多的 IP?

題目描述

現有海量日誌數據保存在一個超大文件中,該文件無法直接讀入內存,要求從中提取某天訪問百度次數最多的那個 IP。

解答思路

這道題只關心某一天訪問百度最多的 IP,因此,可以首先對文件進行一次遍歷,把這一天訪問百度 IP 的相關信息記錄到一個單獨的大文件中。接下來採用的方法與上一題一樣,大致就是先對 IP 進行哈希映射,接着使用 HashMap 統計重複 IP 的次數,最後計算出重複次數最多的 IP。

注:這裏只需要找出出現次數最多的 IP,可以不必使用堆,直接用一個變量 max 即可。

方法總結

  1. 分而治之,進行哈希取餘;
  2. 使用 HashMap 統計頻數;
  3. 求解最大的 TopN 個,用小頂堆;求解最小的 TopN 個,用大頂堆

https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-top-1-ip.md


如何從大量數據中找出高頻詞?

題目描述

有一個 1GB 大小的文件,文件裏每一行是一個詞,每個詞的大小不超過 16B,內存大小限制是 1MB,要求返回頻數最高的 100 個詞(Top 100)。

解答思路

由於內存限制,我們依然無法直接將大文件的所有詞一次讀到內存中。因此,同樣可以採用分治策略,把一個大文件分解成多個小文件,保證每個文件的大小小於 1MB,進而直接將單個小文件讀取到內存中進行處理。

思路如下

首先遍歷大文件,對遍歷到的每個詞x,執行 hash(x) % 5000,將結果爲 i 的詞存放到文件 ai 中。遍歷結束後,我們可以得到 5000 個小文件。每個小文件的大小爲 200KB 左右。如果有的小文件大小仍然超過 1MB,則採用同樣的方式繼續進行分解。

接着統計每個小文件中出現頻數最高的 100 個詞。最簡單的方式是使用 HashMap 來實現。其中 key 爲詞,value 爲該詞出現的頻率。具體方法是:對於遍歷到的詞 x,如果在 map 中不存在,則執行 map.put(x, 1);若存在,則執行 map.put(x, map.get(x)+1),將該詞頻數加 1。

上面我們統計了每個小文件單詞出現的頻數。接下來,我們可以通過維護一個小頂堆來找出所有詞中出現頻數最高的 100 個。具體方法是:依次遍歷每個小文件,構建一個小頂堆,堆大小爲 100。如果遍歷到的詞的出現次數大於堆頂詞的出現次數,則用新詞替換堆頂的詞,然後重新調整爲小頂堆,遍歷結束後,小頂堆上的詞就是出現頻數最高的 100 個詞。

方法總結

  1. 分而治之,進行哈希取餘;
  2. 使用 HashMap 統計頻數;
  3. 求解最大的 TopN 個,用小頂堆;求解最小的 TopN 個,用大頂堆

https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-top-100-words.md


如何從大量的 URL 中找出相同的 URL?

題目描述

給定 a、b 兩個文件,各存放 50 億個 URL,每個 URL 各佔 64B,內存限制是 4G。請找出 a、b 兩個文件共同的 URL。

解答思路

每個 URL 佔 64B,那麼 50 億個 URL佔用的空間大小約爲 320GB。

5,000,000,000 * 64B ≈ 5GB * 64 = 320GB

由於內存大小隻有 4G,因此,我們不可能一次性把所有 URL 加載到內存中處理。對於這種類型的題目,一般採用分治策略,即:把一個文件中的 URL 按照某個特徵劃分爲多個小文件,使得每個小文件大小不超過 4G,這樣就可以把這個小文件讀到內存中進行處理了。

思路如下

首先遍歷文件 a,對遍歷到的 URL 求 hash(URL) % 1000,根據計算結果把遍歷到的 URL 存儲到 a0, a1, a2, ..., a999,這樣每個大小約爲 300MB。使用同樣的方法遍歷文件 b,把文件 b 中的 URL 分別存儲到文件 b0, b1, b2, ..., b999 中。這樣處理過後,所有可能相同的 URL 都在對應的小文件中,即 a0 對應 b0, ..., a999 對應 b999,不對應的小文件不可能有相同的 URL。那麼接下來,我們只需要求出這 1000 對小文件中相同的 URL 就好了。

接着遍歷 ai( i∈[0,999]),把 URL 存儲到一個 HashSet 集合中。然後遍歷 bi 中每個 URL,看在 HashSet 集合中是否存在,若存在,說明這就是共同的 URL,可以把這個 URL 保存到一個單獨的文件中。

方法總結

  1. 分而治之,進行哈希取餘;
  2. 對每個子文件進行 HashSet 統計。

https://github.com/doocs/advanced-java/blob/master/docs/big-data/find-common-urls.md


總結:

判斷數字是否存在、判斷數字是否重複的問題位圖法是一種非常高效的方法。

Top-N分治+hashmap+小頂堆

前綴樹經常被用來統計字符串的出現次數。它的另外一個大的用途是字符串查找,判斷是否有重複的字符串等

不同數據類型字節數

https://blog.csdn.net/sinat_34166518/article/details/80108497

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