数据结构--位图 BitMap
文章目錄
- 1. 位圖
- 2. 位圖代碼
- 3. 布隆過濾器 Bloom Filter
- 4. 總結
1. 位圖
我們有1千萬個整數,整數的范圍在1到1億之間。如何快速查找某個整數是否在這1千萬個整數中呢?
- 當然,這個問題可以用散列表來解決。可以使用一種特殊的散列表,那就是位圖。
- 申請一個大小為1億、布爾類型(true或者false)的數組。將這1千萬個整數作為數組下標,將對應的數組值設置成true。比如,整數5對應下標為5的數組值設置為true,也就是array[5]=true。
- 查詢某個整數K是否在這1千萬個整數中的時候,只需將array[K]取出來,看是否等于true。如果等于true,那說明1千萬整數中包含這個整數K;相反,就表示不包含這個整數K。
- 不過,很多語言中提供的布爾類型,大小是1個字節的,并不能節省太多內存空間。實際上,表示true和false,只需要**一個二進制位(bit)**就可以了。
- 我們可以借助編程語言中提供的數據類型,比如int、long、char等類型,通過位運算,用其中的某個位表示某個數字。
2. 位圖代碼
#include <iostream> #include <cstring> using namespace std; class BitMap {char *bytes; //char是1字節,8位int nbits; public:BitMap(int n){nbits = n;bytes = new char [nbits/8 + 1];memset(bytes, 0, (nbits/8+1)*sizeof(char));}~BitMap(){delete [] bytes;}void set(int k){if(k > nbits)return;int byteIndex = k/8;int bitIndex = k%8;bytes[byteIndex] |= (1<<bitIndex);}bool get(int k){if(k > nbits)return false;int byteIndex = k/8;int bitIndex = k%8;return (bytes[byteIndex] & (1 << bitIndex)) != 0;}void print(){for(int i = 15; i >= 0; --i)cout << get(i) << " ";} }; int main() {BitMap bm(8);bm.set(8);cout << bm.get(8) << endl;bm.print();return 0; }比如上面例子,如果用散列表存儲這1千萬的數據,數據是32位的整型數,也就是需要4個字節的存儲空間,那總共至少需要40MB的存儲空間。如果通過位圖的話,數字范圍在1到1億之間,只需要1億個二進制位,1億/8/1024/1024 = 12, 也就是12MB左右的存儲空間就夠了。
不過,這里我們有個假設,就是數字范圍不是很大。如果數字的范圍很大,數字范圍不是1到1億,而是1到10億,那位圖的大小就是10億個二進制位,也就是120MB的大小,消耗的內存空間,不降反增!
怎么辦?請布隆過濾器登場!
3. 布隆過濾器 Bloom Filter
- 布隆過濾器就是為了解決剛剛這個問題,對位圖這種數據結構的一種改進。
還是剛剛那個例子,數據個數是1千萬,數據的范圍是1到10億。
-
布隆過濾器的做法是,我們仍然使用一個1億個二進制大小的位圖,然后通過哈希函數,對數字進行處理,讓它落在這1到1億范圍內。比如我們把哈希函數設計成f(x) = x%n。其中,x表示數字,n表示位圖的大小(1億),也就是,對數字跟位圖的大小進行取模求余。
-
哈希函數會存在沖突的問題,為了降低沖突概率,可以設計一個復雜點、隨機點的哈希函數。除此之外,還有其他方法嗎?
-
我們來看布隆過濾器的處理方法。既然一個哈希函數可能會存在沖突,那用多個哈希函數一起定位一個數據,是否能降低沖突的概率呢?
-
使用 K 個哈希函數,對同一個數字進行求哈希值,那會得到K個不同的哈希值,我們分別記作X1,X2,X3,……Xk 。我們把這 K 個數字作為位圖中的下標,將對應的BitMap[X1],BitMap[X2],BitMap[X3],……BitMap[Xk]都設置成true,也就是說,我們用 K 個二進制位,來表示一個數字的存在。
-
當我們要查詢某個數字是否存在的時候,我們用同樣的 K 個哈希函數,對這個數字求哈希值,分別得到Y1,Y2,Y3,……Yk 。看這 K 個哈希值,對應位圖中的數值是否都為true,都是true,這個數字存在,任意一個不為true,說明這個數字不存在。
對于兩個不同的數字,經過 K 個哈希函數處理之后,K 個哈希值都相同的概率就非常低了。盡管采用 K 個哈希函數之后,兩個數字哈希沖突的概率降低了,但是,這種處理方式又帶來了新的問題,那就是容易誤判。看下面例子。
-
布隆過濾器的誤判有一個特點,那就是,它只會對存在的情況有誤判。
-
如果某個數字經過布隆過濾器判斷不存在,那說明這個數字真的不存在,不會誤判
-
如果某個數字經過布隆過濾器判斷存在,有可能誤判,有可能并不存在。不過,只要我們調整哈希函數的個數、位圖大小跟要存儲數字的個數之間的比例,那就可以將這種誤判的概率降到非常低。
-
盡管布隆過濾器會存在誤判,但是,這并不影響它發揮大作用。很多場景對誤判有一定的容忍度。
4. 總結
布隆過濾器非常適合這種不需要100%準確的、允許存在小概率誤判的大規模判重場景。比如統計一個大型網站的每天的UV數,也就是每天有多少用戶訪問了網站,就可以使用布隆過濾器,對重復訪問的用戶,進行去重。
布隆過濾器的誤判率,主要跟哈希函數的個數、位圖的大小有關。往布隆過濾器中不停地加入數據之后,位圖中不是true的位置就越來越少了,誤判率就越來越高了。所以,對于無法事先知道要判重的數據個數的情況,我們需要支持自動擴容的功能。
當布隆過濾器中,數據個數與位圖大小的比例超過某個閾值的時候,我們就重新申請一個新的位圖。后面來的新數據,會被放置到新的位圖中。但是,如果我們要判斷某個數據是否在布隆過濾器中已經存在,我們就需要查看多個位圖,相應的執行效率就降低了一些。
位圖、布隆過濾器應用如此廣泛,很多編程語言都已經實現了。比如 Java 中的 BitSet 類就是一個位圖,Redis 也提供了 BitMap 位圖類,Google 的 Guava 工具包提供了BloomFilter 布隆過濾器的實現。
課后思考
1.假設我們有1億個整數,數據范圍是從1到10億,如何快速并且省內存地給這1億個數據從小到大排序?
傳統做法:1億個整數,存儲需要400M空間
位圖算法:數字范圍是1到10億,用位圖存儲125M就夠了,然后將1億個數字依次添加到位圖中,再將位圖按下標從小到大輸出值為1的下標,排序就完成了,時間復雜度為O(n)
總結
以上是生活随笔為你收集整理的数据结构--位图 BitMap的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: es6 类的私有属性_JavaScrip
- 下一篇: 图Graph--最小生成树