1. 程式人生 > >點陣圖(bitmap)的理解及應用例項 布隆過濾

點陣圖(bitmap)的理解及應用例項 布隆過濾

點陣圖是記憶體中連續的二進位制位,用於大量整型數的查詢和去重。

比如,給定10bit的記憶體空間,要將{5,3,4,7}插入其中。
則,先將第5位置1,
再將3,4,7 依次置1。
這裡寫圖片描述
這樣,此時bitmap中儲存了哪些元素,就一目瞭然。
bitmap還可以用於去掉重複的整型值。

在一個使用者表中,一個使用者對應多種標籤。
這裡寫圖片描述
我們可以多個標籤對應一個使用者。

首先,建立使用者名稱和使用者ID的對映。
這裡寫圖片描述

然後,讓每一個標籤儲存包含此標籤的所有的ID,每一個標籤都是一個獨立的bitmap。
這裡寫圖片描述這裡寫圖片描述
等等…

這樣,實現使用者的查詢與去重,就是一目瞭然。
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

點陣圖

  • 優點:1.節省空間(相比HashSet或HashMap)
    2.運算快,交集或並集運算,使用位運算。(比如,要找“90後的程式設計師”,就用“90後”和”程式設計師”標籤的bitmap取交集)
  • 缺點:不支援非運算。(如果想要取非,可以定義一個全量使用者的bitmap,兩者相異或,就可以取非)

點陣圖的實現:

  • JDK中BitSet就是對bitmap演算法的實現
  • 谷歌的EWAHCompressedBitmap是一種更優化的實現

EWAHCompressedBitmap

把bitmap儲存在long陣列中

  • 初始long陣列長度為4(一個Word可表示64位)
    這裡寫圖片描述
  • 插入ID為1的使用者
    這裡寫圖片描述
    這裡Word0,儲存的特殊的資訊,下文會講。

    • Word被佔用,bitmap動態擴容。
      Word分為兩種:
      Literal Word(LW)儲存資料
      Running Length Word(RLW)儲存跨度資訊
      這裡寫圖片描述

      這裡的 Word0 和 Word4 就是 RLW,RLW 中,
  • 低32位表示當前Word橫跨了多少個Word,
  • 高32位表示當前RLW後方有多少個連續的LW

這樣,對於及其稀疏的bitmap,這種儲存方式就會節省大量空間。

一個新資料的插入,就要依靠每一個RLW作為路標,然後找到自己該插的位置。

這種結構,按照順序插會比較容易些,如果要插入的數已經被RLW跨過去了的話,就要將RLW拆開,會比較麻煩。官方也建議使用者按照順序,從小到大的插入。

*Though you can set the bits in any order (e.g., set(100), set(10), set(1),
* you will typically get better performance if you set the bits in increasing order (e.g., set(1), set(10), set(100)).
*
* Setting a bit that is larger than any of the current set bit
* is a constant time operation. Setting a bit that is smaller than an
* already set bit can require time proportional to the compressed
* size of the bitmap, as the bitmap may need to be rewritten.

問題及應用例項

1 使用點陣圖法判斷整形陣列是否存在重複

判斷集合中存在重複是常見程式設計任務之一,當集合中資料量比較大時我們通常希望少進行幾次掃描,這時雙重迴圈法就不可取了。
點陣圖法比較適合於這種情況,它的做法是按照集合中最大元素max建立一個長度為max+1的新陣列,然後再次掃描原陣列,遇到幾就給新陣列的第幾位置上1,如遇到 5就給新陣列的第六個元素置1,這樣下次再遇到5想置位時發現新陣列的第六個元素已經是1了,這說明這次的資料肯定和以前的資料存在著重複。這種給新陣列初始化時置零其後置一的做法類似於點陣圖的處理方法故稱點陣圖法。它的運算次數最壞的情況為2N。如果已知陣列的最大值即能事先給新陣列定長的話效率還能提高一倍。

2 在2.5億個整數中找出不重複的整數,注,記憶體不足以容納這2.5億個整數

解法一:將bit-map擴充套件一下,採用2-Bitmap(每個數分配2bit,00表示不存在,01表示出現一次,10表示多次,11無意義)進行,共需記憶體2^32 * 2 bit=1 GB記憶體,還可以接受。然後掃描這2.5億個整數,檢視Bitmap中相對應位,如果是00變01,01變10,10保持不變。所描完事後,檢視bitmap,把對應位是01的整數輸出即可。

或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現這個2bit-map,都是一樣的道理。

解法二:也可採用與第1題類似的方法,進行劃分小檔案的方法。然後在小檔案中找出不重複的整數,並排序。然後再進行歸併,注意去除重複的元素。

2.1 一個序列裡除了一個元素,其他元素都會重複出現3次,設計一個時間複雜度與空間複雜度最低的演算法,找出這個不重複的元素。
3 已知某個檔案內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數。

8位最多99 999 999,大概需要99m個bit,大概10幾m位元組的記憶體即可。 (可以理解為從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的記憶體表示了所有的8位數的電話)

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

解析:bitmap演算法就好辦多了。申請512M的記憶體,一個bit位代表一個unsigned int值,讀入40億個數,設定相應的bit位;讀入要查詢的數,檢視相應bit位是否為1,為1表示存在,為0表示不存在。
Note: unsigned int最大數為2^32 - 1,所以需要2^32 - 1個位,也就是(2^32 - 1) / 8 /10 ^ 9G = 0.5G記憶體。

逆向思維優化:usinged int只有接近43億(unsigned int最大值為232-1=4294967295,最大不超過43億),所以可以用某種方式存沒有出現過的3億個數(使用陣列{大小為3億中最大的數/8 bytes}儲存),如果出現在3億個數裡面,說明不在40億裡面。3億個數儲存空間一般小於40億個。(xx儲存4294967296需要512MB, 儲存294967296只需要35.16MBxx)

5 給定一個數組a,求所有和為SUM的兩個數。

如果陣列都是整數(負數也可以,將所有資料加上最小的負數x,SUM += 2x就可以了)。如a = [1,2,3,4,7,8],先求a的補陣列[8,7,6,5,2,1],開闢兩個陣列b1,b2(最大陣列長度為SUM/8/2{因為兩數滿足和為SUM,一個數 < SUM / 2,另一個數也就知道了},這樣每個b陣列最大記憶體為SUM/(8*2*1024*1024) = 128M),使用bitmap演算法和陣列a分別設定b1b2對應的位為1,b1b2相與就可以得到和為SUM的兩個數其中一個數了。

布隆過濾

以bitmap為基礎的排重演算法。
應用:URL的排重;垃圾郵箱地址的過濾

  1. 建立bitmap集合
  2. 把第一個URL按照三種不同的Hash演算法,生成三種不同的Hash值。
    這裡寫圖片描述
  3. 分別判斷5,17,9對應的位置是否為1,只要不同時為1,就認為該URL沒有重複,然後把5,17,9對應位置置1。

當被置1的位變多了,有可能沒有重複的URL,Hash出來的結果,對應的位數都是1,那麼就誤判了。為減小誤判的機率,可以讓bitmap的空間更大一些,單個URL所做的Hash更多(一般是8次)。
垃圾郵箱的過濾,則可以考慮加上白名單。

:-)