理解BitMap算法的原理及應用

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

 

 

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