BitMap簡介:
什麼是 BitMap 算法
所謂 BitMap 就是用一個 bit 位來標記某個元素對應的 value,而 key 即是這個元素。由於採用bit爲單位來存儲數據,因此在可以大大的節省存儲空間。
算法思想
32位機器上,一個整形,比如 int a; 在內存中佔32bit,可以用對應的32個bit位來表示十進制的0-31個數,bitmap算法利用這種思想處理大量數據的排序與查詢。
優點:
- 效率高,不許進行比較和移位
- 佔用內存少,比如N=10000000;只需佔用內存爲N/8 = 1250000Bytes = 1.2M,如果採用int數組存儲,則需要38M多
缺點:
- 無法對存在重複的數據進行排序和查找
示例:
申請一個int型的內存空間,則有4Byte,32bit。輸入 4, 2, 1, 3時:
輸入4:
輸入2:
輸入1:
輸入3:
思想比較簡單,關鍵是十進制和二進制bit位需要一個 map 映射表,把10進制映射到bit位上。
map映射表
假設需要排序或者查找的總數N=10000000,那麼我們需要申請的內存空間爲 int a[N/32 + 1].其中a[0]在內存中佔32位,依此類推:
bitmap表爲:
a[0] ------> 0 - 31
a[1] ------> 32 - 63
a[2] ------> 64 - 95
a[3] ------> 96 - 127
......
下面介紹用位移將十進制數轉換爲對應的bit位
位移轉換
(1) 求十進制數 0-N 對應的在數組 a 中的下標
index_loc = N / 32即可,index_loc即爲n對應的數組下標。例如n = 76, 則loc = 76 / 32 = 2,因此76在a[2]中。
(2)求十進制數0-N對應的bit位
bit_loc = N % 32即可,例如 n = 76, bit_loc = 76 % 32 = 12
(3)利用移位0-31使得對應的32bit位爲1
代碼示例(c語言)
#include <stdio.h>
#include <stdlib.h>
#define SHIFT 5
#define MASK 0x1F
/**
* 設置所在的bit位爲1
*
* T = O(1)
*
*/
void set(int n, int *arr)
{
int index_loc, bit_loc;
index_loc = n >> SHIFT; // 等價於n / 32
bit_loc = n & MASK; // 等價於n % 32 。 h%2^n = h & (2^n -1)
arr[index_loc] |= 1 << bit_loc;
}
/**
* 初始化arr[index_loc]所有bit位爲0
*
* T = O(1)
*
*/
void clr(int n, int *arr)
{
int index_loc;
index_loc = n >> SHIFT;
arr[index_loc] &= 0;
}
/**
* 測試n所在的bit位是否爲1
*
* T = O(1)
*
*/
int test(int n, int *arr)
{
int i, flag;
i = 1 << (n & MASK);
flag = arr[n >> SHIFT] & i;
return flag;
}
int main(void)
{
int i, num, space, *arr;
while (scanf("%d", &num) != EOF) {
// 確定大小&&動態申請數組
space = num / 32 + 1;
arr = (int *)malloc(sizeof(int) * space);
// 初始化bit位爲0
for (i = 0; i <= num; i ++)
clr(i, arr);
// 設置num的比特位爲1
set(num, arr);
// 測試
if (test(num, arr)) {
printf("成功!\n");
} else {
printf("失敗!\n");
}
}
return 0;
}
BitMap的應用:
前言
位圖:一種常用的數據結構,代表了有限域中的稠集(dense set),每一個元素至少出現一次,沒有其他的數據和元素相關聯。在索引,數據壓縮,海量數據處理等方面有廣泛應用。
BitMap 的思想的和原理是很多算法的基礎,比如 Bloom Filter、Counting Bloom Filter。
BitMap的原理
BitMap 的基本原理就是用一個 bit 位來存放某種狀態,適用於大規模數據,但數據狀態又不是很多的情況。通常是用來判斷某個數據存不存在的。
舉個例子在Java裏面一個int類型佔4個字節,也就是4*8=32bit,大多數時候我們一個int類型僅僅表示一個整數,但其實如果映射成位存儲的話,一個int類型是可以存儲32個bit狀態的。
也就是說使用1G的內存,換算成bit=1024 * 1024 * 1024 * 8約等於86億個bit,注意換算的方式GB=>MB=>KB=>Byte=>Bit。如果存儲int類型,能存儲多少個?我們算下1024 * 1024 * 1024 / 4 約等於2億6千萬個int類型。
從上面的計算的結果來看,在面對海量數據的時候,如果能夠採用bit存儲,那麼在存儲空間方面可以大大節省。
在Java裏面,其實已經有對應實現的數據結構類java.util.BitSet了,BitSet的底層使用的是long類型的數組來存儲元素。
也就是說,
假設我想排序或者查找的總數N=10000,那麼,我申請的數組大小如下:
如果是int類型:int temp[]=new int[1+N/32],也就是312+1=313
如果是long類型:long temp[]=new long[1+N/64],也就是156+1=157
這裏注意Java裏面的整數除法是向下取整的,所以數組長度還需要加上1.
這裏以int爲例,生成的bitmap表如下:
其實申請一個int一維數組,那麼可以當作爲列爲32位的二維數組。先通過對32進行相除,得到數組下標,然後將十進制轉成二進制之後,進行移位計算,用來代表狀態。
下面,我們來看一個排序場景,定義一個元素不重複的數組。
輸出:
第一行的64,是代表當前的bit數,因爲是long類型,而數組裏面的最大值沒有超過63,所以其實只用一個long類型就能處理上面的排序。
看到這裏,如果熟悉排序算法裏面計數排序,那麼我們就能發現原理非常類似,不同的是使用bitmap排序佔用的存儲空間更小,但缺點是不支持重複數字。
來看一下關於BitMap算法一些處理大數據問題的場景:
(1)給定40億個不重複的 int的整數,沒排過序的,然後再給一個數,如何快速判斷這個數是否在那40億個數當中。
解法:遍歷40億數字,映射到BitMap中,然後對於給出的數,直接判斷指定的位上存在不存在即可。
(2)使用位圖法判斷整形數組是否存在重複
解法:遍歷一遍,存在之後設置成1,每次放之前先判斷是否存在,如果存在,就代表該元素重複。
(3)使用位圖法進行元素不重複的整形數組排序
解法:遍歷一遍,設置狀態1,然後再次遍歷,對狀態等於1的進行輸出,參考計數排序的原理。
(4)在2.5億個整數中找出不重複的整數,注,內存不足以容納這2.5億個整數
解法1:採用2-Bitmap(每個數分配2bit,00表示不存在,01表示出現一次,10表示多次,11無意義)。
解法2:採用兩個BitMap,即第一個Bitmap存儲的是整數是否出現,接着,在之後的遍歷先判斷第一個BitMap裏面是否出現過,如果出現就設置第二個BitMap對應的位置也爲1,最後遍歷BitMap,僅僅在一個BitMap中出現過的元素,就是不重複的整數。
解法3:分治+Hash取模,拆分成多個小文件,然後一個個文件讀取,直到內存裝的下,然後採用Hash+Count的方式判斷即可。
該類問題的變形問題,如已知某個文件內包含一些電話號碼,每個號碼爲8位數字,統計不同號碼的個數。8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。 (可以理解爲從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==12MBytes,這樣,就用了小小的12M左右的內存表示了所有的8位數的電話)
BitMap的一些缺點:
(1)數據碰撞。比如將字符串映射到 BitMap 的時候會有碰撞的問題,那就可以考慮用 Bloom Filter 來解決,Bloom Filter 使用多個 Hash 函數來減少衝突的概率。
(2)數據稀疏。又比如要存入(10,8887983,93452134)這三個數據,我們需要建立一個 99999999 長度的 BitMap ,但是實際上只存了3個數據,這時候就有很大的空間浪費,碰到這種問題的話,可以通過引入 Roaring BitMap 來解決。
總結
本文主要介紹了BitMap算法的基本原理和應用案例,其本質上是採用了bit位來表示元素狀態,從而在特定場景下能夠極大的節省存儲空間,非常適合對海量數據的查找,判重,刪除等問題的處理。
海量數據處理面試題集錦
https://blog.csdn.net/v_july_v/article/details/6685962