算法---BitMap

問題:

假設有3億個整數(範圍0-2億),如何判斷某一個樹是否存在。侷限條件一臺機器,內存500m

 

常規的思路:我們可以將數據存到一個集合中,然後判斷某個數是否存在;或者用一個等長的數組來表示,每個數對應的索引位置,存在就標記爲1,不存在0。當然如果設備條件允許,上面的這方案是可行的。

但是現在我們內存只有500mInt類型4個字節。

單單是這3億個數都已經:300000000*4/1024/1024 差不多1144m了。顯然已經遠超過內存限制了。

顯然在這種條件下面,我們想要將這些書完整的存儲下來已經是不現實的。只有另尋他經。

上面的第二種方案中提供一個很好思路,用標記來標註。我們只有尋求內存佔用更小的數據類型來標記了,1int=4byte,1byte = 8bit

如果我們用bit來打標記。就不可以很好的縮小內存佔用了嘛。1int=4byte,如果用bit那麼內存佔用就會縮小32倍,1144/32大概36m就可以了。

也就是說其實我們的一個int位置,可以表示32個數據存在與否

Int 1的二進制完整表示:0000 0000 0000 0000 0000 0000 0000 0001,它是有32位,只不過平時前面的0都是沒有顯示的

我們聲明一個int類型的標記數組:flag[2/32 +1],數組長度2/32 +1,就可以了,爲什麼是2/32,不是3億呢,因爲這3億個數的範圍就是0-2億,所以我們的標記數組最大只需要對應2億即可。有的數,就將其標記爲1。就是說數組的沒一個元素其實包含了32個數存在與否。

Flag[0] --->0-31

Flag[1] --->32-63

Flag[2] --->64-95

Flag[3] --->96-127

.......

我們怎麼來操作這個int類型裏面的32位呢,這就要用到我們的位運算:

<<:左移

>>:右移

&:同位上的兩個數都是1則位1,否則爲0

|:同爲上的兩個數只要有一個爲1 則爲1,否則爲0

 

我們怎麼找到某個數的標記在數組的索引及其值的多少位呢?

假設3這個數

Index = 3 / 32 = 0

Location = 3 % 32 = 3

3的標記應該在flag[0] 的第三個位置,將其置爲1

flag[0]  |=  1 << Location (位或是不會影響其他位置1的標記)

原本的flag[0]  0000 0000 0000 0000 0000 0000 0000 0011    原本已經有了01

     |    0000 0000 0000 0000 0000 0000 0000 1000    1 << Location 之後的

得到:           0000 0000 0000 0000 0000 0000 0000 1011    現在有0,1,3

這樣就可以所有的數據標記給保存下來了。

那麼判斷的時候怎麼判斷呢。

同理先找到某個數精確位置

還是3

Index = 3 / 32 = 0

Location = 3 % 32 = 3

現在我們的flag[0] -> 0000 0000 0000 0000 0000 0000 0000 1011

Exist = flag[0] & (1 << Location) != 0  &:相同位上的兩個數都是1則位1,否則爲0

  0000 0000 0000 0000 0000 0000 0000 1011

&  0000 0000 0000 0000 0000 0000 0000 1000

    0000 0000 0000 0000 0000 0000 0000 1000

&運算之後結果不爲0說明該數存在。

 

大致的思路就是這個樣子,下面用代碼實現上述邏輯

 

package com.nijunyang.algorithm.math;

/**
 * Description:
 * Created by nijunyang on 2020/5/5 22:33
 */
public class BitMap {
    /**
     * 範圍中最大的那個數
     */
    private int max;

    private int[] flag;

    public BitMap(int max) {
        this.max = max;
        flag = new int[max >> 5 + 1]; //除以32也可以表示爲>>5
    }

    /**
     * 添加數據標記
     * @param val
     */
    public void put(int val) {        //往bitmap裏面添加數字

        int index = val / 32;        // 計算數組索引位置
        int location = val % 32;    // 計算在32位int中的位置
        flag[index] |= 1 << location;   //標記位改成1
    }

    /**
     * 判斷是否存在
     * @param val
     * @return
     */
    public boolean exist(int val) {
        int index = val / 32;
        int location = val % 32;

        int result = flag[index] & (1 << location);
        return result != 0;
    }

    /**
     * 移除標記
     * @param val
     * @return
     */
    public void remove(int val) {
        int index = val / 32;
        int location = val % 32;

        System.out.println(Integer.toBinaryString(flag[index]));
        flag[index] = flag[index] &~ (1 << location); //~取反1變0 0變1
        System.out.println(Integer.toBinaryString(flag[index]));
    }

    public static void main(String[] args) {
        BitMap bitMap = new BitMap(200_000_000);
        bitMap.put(128);
        bitMap.put(129);
        System.out.println(bitMap.exist(127));
        System.out.println(bitMap.exist(128));
        bitMap.remove(128);
        System.out.println(bitMap.exist(128));

    }
}

 

 

雖然說用bitMap的這種思想可以解決上面的這個問題,但是它還是有缺點的,我們上面用的數字,如果是其他類型可能就需要hash之後得到hashcode再進行這個操作,對於hash衝突是無法解決的。因爲標記只有01。數據量少的時候相對於普通的hash集合操作並沒有優勢。它對於那種數據量很大,且數據相對密集的,因爲數組的長度是和最大的數據值有關,而不是和集合容量有關。

布隆過濾器就使用bitMap的思想。不過它同時會使用集中hash算法來計算hashcode,來儘量解決hash衝突。Redis緩存設計與性能優化 中有簡單的介紹。

JDK中也有一個BitSet類。

 

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