1. 程式人生 > >BitMap演算法介紹

BitMap演算法介紹

問題引出

我們先來看個簡單的問題。

假如給你20億個非負數的int型整數,然後再給你一個非負數的int型整數 t ,讓你判斷t是否存在於這20億數中,你會怎麼做呢?

有人可能會用一個int陣列,然後把20億個數給存進去,然後再迴圈遍歷一下就可以了。

想一下,這樣的話,時間複雜度是O(n),所需要的記憶體空間

4byte * 20億,一共需要80億個位元組,

大概需要8GB的記憶體空間,顯然有些計算機的記憶體一次是載入不了這麼這麼多的資料的。

初步優化

按照上面的做法,時間複雜度是O(n),記憶體是8GB,實際上我們是可以把時間複雜度降低到O(1)的。

例如我們可以這樣來存資料,把一個int非負整數n作為陣列下標,如果n存在,則對應的值為1,如果不存在,對應的值為0。例如陣列arr[n] = 1,表示n存在,arr[n] = 0表示n不存在。

那麼,我們就可以把20億個數作為下標來存,之後直接判斷arr[t]的值,如果arr[t] = 1,則代表存在,如果arr[t] = 0,則代表不存在。這樣,我們就可以把時間複雜度降低到O(1)。不過空間複雜度我們並沒有降低。還稍微大了點。

由於int非負整數一共有 2^31 個,所以陣列的大小需要 2^31 這麼大。

這裡可能有人說也可以用HashSet來存啊,時間複雜度也是近似O(1)。不過這裡需要說明的是,HashSet裡面存的必須是物件,也就是說需要把int包裝成Integer,顯然一個物件的話是更花銷記憶體的,需要物件頭啊什麼的…..

再次優化

大家想一個問題,對於一個數,實際上我們只需要兩種狀態,就是這個數存在和不存在這兩種可能。上面我們用1代表存在,用0代表不存在。

也就是說,我們是可以不用int型的陣列來儲存的,一個int型佔用4個位元組,即32個二進位制位,一共可以表示40億多個狀態。用int型的來存兩個狀態,多浪費。

所以我們可以考慮用boolean型的來存的,boolean貌似就佔用一個位元組(java中的boolena貌似是佔用一個位元組)。而一個boolean有true和false兩種狀態,所以也是成立的。這樣子的話佔用的記憶體就是2GB的記憶體了。

這樣,就可以降低到之前的四分之1記憶體了。

最終優化:bitmap

大家再想一個問題,雖然boolean是表示兩種狀態,但是boolean實際上佔用了8bit啊,按道理8bit是可以表示128種狀態的。而被我們拿來表示兩個狀態,是否也有點浪費了呢?

我們都知道,一個二進位制位,有0和1兩種狀態,所以說,其實我們是可以用一個二進位制位來代表一個int型的數是否存在的。例如對於1,3,5,7這四個數,如果存在的話,則可以這樣表示:

1代表這個數存在,0代表不存在。例如表中01010101代表1,3,5,7存在,0,2,4,6不存在。

那如果8,10,14也存在怎麼存呢?如圖,8,10,14我們可以存在第二個位元組裡

以此類推。這樣子,我們又可以把記憶體降低到之前的8分之一了。

這種採用一個二進位制位來儲存資料的方法,我們也叫做bitmap演算法。

可能有人會問,假如我要新增一個數n,我知道它要存在第n個位那裡,把第n個二進位制改為1,可是我要怎麼操作呢?

這個對於bitmap演算法是如何儲存的,如何進行增刪操作的,我會在之後的文章裡講,這篇就大概介紹下bitmap演算法。

Java中有自帶的bitmap實現,今天我們就用Java中自帶的bitmap來做道題練練手。我們換道類似題目吧,不知道你一眼是否就能想到用bitmap演算法來做。

題目描述:

現在有五十億個int型別的正整數,要從中找出重複的數並返回。

判斷50億個數有哪些是重複和剛才上面那個判斷是否存在,其實是一樣的。我們採用bitmap演算法來做。不過這裡50億個數,別人肯定是以檔案流的形式給你的。這樣我們為了方便,我們就假設這些數是以存在int型陣列的形式給我們的。

public class Test {

   //為了方便,假設資料是以陣列的形式給我們的

   public static Set<Integer> test(int[] arr) {

       int j = 0;

       //用來把重複的數返回,存在Set裡,這樣避免返回重複的數。

       Set<Integer> output = new HashSet<>();

       BitSet bitSet = new BitSet(Integer.MAX_VALUE);

       int i = 0;

       while (i < arr.length) {

           int value = arr[i];

           //判斷該數是否存在bitSet裡

           if (bitSet.get(value)) {

               output.add(value);

           } else {

               bitSet.set(value, true);

           }

           i++;

       }

       return output;

   }

   //測試

   public static void main(String[] args) {

       int[] t = {1,2,3,4,5,6,7,8,3,4};

       Set<Integer> t2 = test(t);

       System.out.println(t2);

   }

}


程式碼可以左右滑動

列印結果:

[3, 4]

當然,bitmap演算法的應用不僅僅是節省記憶體,它還有很多其他的優點。之後有機會就拿一些其他的應用來寫篇文章。

本次講解到此結束。如果喜歡,可以分享給更多的小夥伴哦。