1. 程式人生 > >[算法系列之十八]海量資料處理之BitMap

[算法系列之十八]海量資料處理之BitMap

一:簡介

所謂的BitMap就是用一個bit位來標記某個元素對應的Value, 而Key即是該元素。由於採用了bit為單位來儲存資料,因此在儲存空間方面,可以大大節省。

二:基本思想

我們用一個具體的例子來講解,假設我們要對0-7內的5個元素(4,7,2,5,3)排序(這裡假設這些元素沒有重複)。那麼我們就可以採用BitMap的方法來達到排序的目的。要表示8個數,我們就只需要8個bit(1Bytes)。 
(1)首先我們開闢1位元組(8bit)的空間,將這些空間的所有bit位都置為0,如下圖:

這裡寫圖片描述

(2)然後遍歷這5個元素,首先第1個元素是4,那麼就把4對應的位置為1,因為是從零開始的,所以要把第5個位置為1(如下圖):

這裡寫圖片描述

然後再處理第2個元素7,將第8個位置為1,,接著再處理第3個元素,一直到處理完所有元素,將相應的位置為1,這時候的記憶體的bit位的狀態如下:

這裡寫圖片描述

(3)然後我們現在遍歷一遍bit區域,將該位是1的位的編號輸出(2,3,4,5,7),這樣就達到了排序的目的。

演算法思想比較簡單,但關鍵是如何確定十進位制的數對映到二進位制bit位的map圖。

三:Map對映

假設需要排序或者查詢的總數N=10000000。 
BitMap中1bit代表一個數字 
1個int = 4Bytes = 4*8bit = 32 bit,那麼N個數需要N/32 int空間。所以我們需要申請記憶體空間的大小為int a[1 + N/32],其中:a[0]在記憶體中佔32為可以對應十進位制數0-31,依次類推:

BitMap表為:

    a[0]  --------->  0-31 
    a[1]  --------->  32-63 
    a[2]  --------->  64-95 
    a[3]  --------->  96-127 
    .......... 

那麼十進位制數如何轉換為對應的bit位,下面介紹用位移將十進位制數轉換為對應的bit位。

申請一個int一維陣列,那麼可以當作為列為32位的二維陣列。

a[0]這裡寫圖片描述 
a[1]這裡寫圖片描述 
a[2]這裡寫圖片描述 
a[3]這裡寫圖片描述

a[i] ……………………………….

a[n]這裡寫圖片描述

例如: 
十進位制1 在a[0]中,位置如下圖: 
這裡寫圖片描述

 
十進位制31 在a[0]中,位置如下圖: 
這裡寫圖片描述 
十進位制32 在a[1]中,位置如下圖: 
這裡寫圖片描述 
十進位制33 在a[1]中,位置如下圖: 
這裡寫圖片描述

通過上圖分析得出通過以下幾步將十進位制數如何轉換為對應的bit位:

(1)求十進位制數在對應陣列a中的下標

十進位制數0-31,對應在陣列a[0]中,32-63對應在陣列a[1]中,64-95對應在陣列a[2]中……… 
分析得出:對於一個十進位制數n,對應在陣列a[n/32]中 
例如n=11,那麼 n/32=0,則11對應在陣列a中的下標為0,n=32,那麼n/32=1,則32對應在陣列a中的下標為1,n = 106,那麼n/32 = 3,則106對應陣列a中的下標為3。

(2)求十進位制數在對應陣列a[i]中的下標

例如十進位制數1在a[0]的下標為1,十進位制數31在a[0]中下標為31,十進位制數32在a[1]中下標為0。 
在十進位制0-31就對應0-31,而32-63則對應也是0-31,即給定一個數n可以通過模32求得在對應陣列a[i]中的下標。 
分析得出:對於一個十進位制數n,對應在陣列a[n/32][n%32]中

(3)移位

對於一個十進位制數n,對應在陣列a[n/32][n%32]中,但陣列a畢竟不是一個二維陣列,我們通過移位操作實現置1。 
a[n/32] |= 1 << n % 32 
移位操作: 
a[n>>5] |= 1 << (n & 0x1F)

n & 0x1F 保留n的後五位 相當於 n % 32 求十進位制數在陣列a[i]中的下標

/*--------------------------------
*   日期:2015-02-07
*   作者:SJF0115
*   題目: BitMap
*   部落格:
------------------------------------*/
#include <iostream>
#include <vector>
using namespace std;

#define N 1000000000

//申請記憶體的大小
int a[1 + N/32];

// 設定所在的bit位為1
void BitMap(int n){
    // row = n / 32 求十進位制數在陣列a中的下標
    int row = n >> 5;
    // n & 0x1F 保留n的後五位
    // 相當於 n % 32 求十進位制數在陣列a[i]中的下標
    a[row] |= 1 << (n & 0x1F);
}
// 判斷所在的bit為是否為1
bool Exits(int n){
    int row = n >> 5;
    return a[row] & ( 1 << (n & 0x1F));
}

void Show(int row){
    cout<<"BitMap點陣圖展示:"<<endl;
    for(int i = 0;i < row;++i){
        vector<int> vec;
        int tmp = a[i];
        for(int i = 0;i < 32;++i){
            vec.push_back(tmp & 1);
            tmp >>= 1;
        }//for
        cout<<"a["<<i<<"]"<<"->";
        for(int i = vec.size()-1;i >= 0;--i){
            cout<<vec[i]<<" ";
        }//for
        cout<<endl;
    }//for
}

int main(){
    int num[] = {1,5,30,32,64,56,159,120,21,17,35,45};
    for(int i = 0;i < 12;++i){
        BitMap(num[i]);
    }//for
    int row = 5;
    Show(5);
    /*if(Exits(n)){
        cout<<"該數字已經存在"<<endl;
    }//if
    else{
        cout<<"該數字不存在"<<endl;
    }//else*/
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

這裡寫圖片描述

應用範圍

可以運用在快速查詢、去重、排序、壓縮資料等。

c++版BitMap

在C++中提供了bitset這種集合,專門用來進行位操作,因此實現起來比較容易。 
具體參考:STL bitset用法總結

擴充套件

Bloom filter可以看做是對BitMap的擴充套件 
布隆過濾器具體參考:[算法系列之十]大資料量處理利器:布隆過濾器

具體應用

(1)已知某個檔案內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數。

思路 
電話號碼每個為8位數字,最小電話號碼為00 000 000,最大99 999 999,一共100 000 000個電話號碼, 
100 000 000bit = 12 500 000Bytes = 12MBytes 故需要12Mbytes的記憶體即可儲存所有的電話號碼。 
可以理解為從00 000 000 - 99 999 999的數字,每個數字對應一個bit位,所以只需要12MBytes,這樣,就用了小小的12M左右的記憶體表示了所有的8位數的電話。

程式碼

    /*-------------------------------------
    *   日期:2015-03-27
    *   作者:SJF0115
    *   題目: 統計電話號碼個數
    *   來源:海量資料
    *   部落格:
    ------------------------------------*/
    #include <iostream>
    #include <vector>
    #include <time.h>
    using namespace std;
    // 給定的電話號碼個數
    #define NUM 1000
    // 電話號碼的最小值
    #define MIN 10000000
    // 電話號碼的最大值
    #define MAX 99999999
    // 電話號碼總共個數
    #define N (MAX - MIN + 1)

    // 置1
    void SetBitMap(int bitMap[],int num){
        num -= MIN;
        //bitMap[num/32] |= 1 << num % 32;
        bitMap[num >> 5] |= (1 << (num % 32));
    }//void

    // 置0
    void ClearBitMap(int bitMap[],int num){
        bitMap[num >> 5] &= ~(1 << (num % 32));
    }//void

    // 獲取
    bool GetBitMap(int bitMap[],int num){
        return bitMap[num >> 5] & (1 << (num % 32));
    }//void
    // 統計電話號碼個數
    int PhoneCount(int phone[],int n,int bitMap[]){
        for(int i = 0;i < n;++i){
            SetBitMap(bitMap,phone[i]);
        }//for
        // 統計個數
        int count = 0;
        for(int i = 0;i < N;++i){
            if(GetBitMap(bitMap,i)){
                ++count;
            }//if
        }//for
        return count;
    }

    int main(){
        // 隨機生成NUM個電話號碼
        int phone[NUM];
        // 點陣圖大小
        int* bitMap = new int[N/32+1];
        // 清空
        for(int i = 0;i < N;++i){
            ClearBitMap(bitMap,i);
        }//for
        // 隨機生成100個電話號碼
        srand((unsigned)time(nullptr));
        for(int i = 0;i < NUM;++i){
            phone[i] = rand() % N + MIN;
            cout<<phone[i]<<endl;
        }//for
        cout<<"電話號碼個數->"<<PhoneCount(phone,NUM,bitMap)<<endl;
        return 0;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

待完善…….