詳解BitMap算法


所謂的BitMap就是用一個bit位來標記某個元素所對應的value,而key即是該元素,由於BitMap使用了bit位來存儲數據,因此可以大大節省存儲空間。
 

1. 基本思想

首先用一個簡單的例子來詳細介紹BitMap算法的原理。假設我們要對0-7內的5個元素(4,7,2,5,3)進行排序(這里假設元素沒有重復)。我們可以使用BitMap算法達到排序目的。要表示8個數,我們需要8個byte。

  1. 首先我們開辟一個字節(8byte)的空間,將這些空間的所有的byte位都設置為0

  2. 然后便利這5個元素,第一個元素是4,因為下邊從0開始,因此我們把第五個字節的值設置為1

  3. 然后再處理剩下的四個元素,最終8個字節的狀態如下圖

  1. 現在我們遍歷一次bytes區域,把值為1的byte的位置輸出(2,3,4,5,7),這樣便達到了排序的目的

從上面的例子可以看出,BitMap算法的思想還是比較簡單的,關鍵的問題是如何確定10進制數到二進制的轉換

MAP映射:

假設需要排序或則查找的數的總數N=100000000,BitMap中1bit代表一個數字,1個int = 4Bytes = 4*8bit = 32 bit,那么N個數需要N/32 int空間。所以我們需要申請內存空間的大小為int a[1 + N/32],其中:a[0]在內存中占32為可以對應十進制數0-31,依次類推:

  a[0]-----------------------------> 0-31

  a[1]------------------------------> 32-63

  a[2]-------------------------------> 64-95

  a[3]--------------------------------> 96-127

  ......................................................

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

  1.求十進制數在對應數組a中的下標

  十進制數0-31,對應在數組a[0]中,32-63對應在數組a[1]中,64-95對應在數組a[2]中………,使用數學歸納分析得出結論:對於一個十進制數n,其在數組a中的下標為:a[n/32]

  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]中的下標。

  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]中的下標

 

2.代碼實現

public class BitMap {

    private static final int N = 10000000;

    private int[] a = new int[N/32 + 1];

    /**
     * 設置所在的bit位為1
     * @param n
     */
    public void addValue(int n){
        //row = n / 32 求十進制數在數組a中的下標
        int row = n >> 5;
        //相當於 n % 32 求十進制數在數組a[i]中的下標
        a[row] |= 1 << (n & 0x1F);
    }

    // 判斷所在的bit為是否為0 
    public boolean exits(int n){
        int row = n >> 5;
        return (a[row] & ( 1 << (n & 0x1F))) != 0;
    }

    public void display(int row){
        System.out.println("BitMap位圖展示");
        for(int i=0;i<row;i++){
            List<Integer> list = new ArrayList<Integer>();
            int temp = a[i];
            for(int j=0;j<32;j++){
                list.add(temp & 1);
                temp >>= 1;
            }
            System.out.println("a["+i+"]" + list);
        }
    }

    public static void main(String[] args){
        //int num[] = {1,5,30,32,64,56,159,120,21,17,35,45};
        int num[] = {4,7}
        BitMap map = new BitMap();
        for(int i=0;i<num.length;i++){
            map.addValue(num[i]);
        }

        int temp = 4;
        if(map.exits(temp)){
            System.out.println("value:[" + temp + "] has already exists");
        }
        map.display(3);
    }
}

運行結果:

value:[4] has already exists
BitMap位圖展示
a[0][0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
a[1][0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
a[2][0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

解析代碼:

如果將 0,1,2 ,3 這些10進制位的數字 分別 通過 & 0X1F (相當於取模32) 通過移位 << N

其實就是 將其 變為 :

​ 0 ---> 0001 即2的0次方 1

​ 1 ---> 0010 即2的1次方 2

​ 2 ---> 0100 即2的2次方 4

再然后 | 運算 由於 都是通過直接左移 得出(不會有重合的值 )

此時的 | 運算 可以理解為相加 那么 如果 a[row] |= 1 << (n & 0x1F) 得出結果為 7

那么肯定是由 0111 構成,那么 不管是 0001 (1) 、 0010 (2)、0100(4) 與 0111 & 運算

其結果肯定有相同位置 同1 也就是一定不為0

應用范圍: 可以運用在快速查找、去重、排序、壓縮數據等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM