1. Bit Map算法簡介
來自於《編程珠璣》。所謂的Bit-map就是用一個bit位來標記某個元素對應的Value, 而Key即是該元素。由於采用了Bit為單位來存儲數據,因此在存儲空間方面,可以大大節省。
2、 Bit Map的基本思想
我們先來看一個具體的例子,假設我們要對0-7內的5個元素(4,7,2,5,3)排序(這里假設這些元素沒有重復)。那么我們就可以采用Bit-map的方法來達到排序的目的。要表示8個數,我們就只需要8個Bit(1Bytes),首先我們開辟1Byte的空間,將這些空間的所有Bit位都置為0,如下圖:
然后遍歷這5個元素,首先第一個元素是4,那么就把4對應的位置為1(可以這樣操作 p+(i/8)|(0x01<<(i%8)) 當然了這里的操作涉及到Big-ending和Little-ending的情況,這里默認為Big-ending),因為是從零開始的,所以要把第五位置為一(如下圖):
然后再處理第二個元素7,將第八位置為1,,接着再處理第三個元素,一直到最后處理完所有的元素,將相應的位置為1,這時候的內存的Bit位的狀態如下:
然后我們現在遍歷一遍Bit區域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達到了排序的目的。
優點:
1.運算效率高,不許進行比較和移位;
2.占用內存少,比如N=10000000;只需占用內存為N/8=1250000Byte=1.25M。
缺點:
所有的數據不能重復。即不可對重復的數據進行排序和查找。
算法思想比較簡單,但關鍵是如何確定十進制的數映射到二進制bit位的map圖。
3、 Map映射表
假設需要排序或者查找的總數N=10000000,那么我們需要申請內存空間的大小為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位。
如題:
給你一個文件,里面包含40億個整數,寫一個算法找出該文件中不包含的一個整數, 假設你有1GB內存可用。
如果你只有10MB的內存呢?
一個位代表一個數據,那40一個數據大概要40*10^8*bit = 0.5GB,滿足內存要求。
首先我們用int來表示:int bmap[1+N/32]; //N是總數,N=40億,一個int32bit
然后我們插入一個整數val,要先計算val位於數組bmap中的索引:index = val/32;
比如整數33,index=33/32=1,第33位於數組中的index=1
比如整數67,index=67/32=2,位於數組中index=2
然后在計算在這個index中的位置,因為數組中的每個元素有32位
33,index=1,在1中的位置為33%32=1
67,index=2,在2中的位置為67%32=3
然后就是標識這個位置為1:
bmap[val/32] |= (1<<(val%32));
33: bmap[1] != (1<<1);//xxxxxx1x,紅絲位置被置為1
67: bmap[2] != (1<<3);//xxxx1xxx
void setVal(int val) { bmap[val/32] |= (1<<(val%32)); //bmap[val>>5] != (val&0x1F);//這個更快? }
怎樣檢測整數是否存在?
比如我們檢測33,同樣我們需要計算index,以及在index元素中的位置
33: index = 1, 在bmap[1]中的位置為 1,只需要檢測這個位置是否為1
bmp[1] &(1<<1),這樣是1返回true,否側返回false
67:bmp[2]&(1<<3)
127:bmp[3]&(1<<31)
bool testVal(int val) { return bmap[val/32] & (1<<(val%32)); //return bmap[val>>5] & (val&0x1F); }
現在我們來看如果內存要求是10MB呢?
這當然不能用bitmap來直接計算。因為從40億數據找出一個不存在的數據,我們可以將這么多的數據分成許
多塊, 比如每一個塊的大小是1000,那么第一塊保存的就是0到999的數,第2塊保存的就是1000 到1999的數……
實際上我們並不保存這些數,而是給每一個塊設置一個計數器。 這樣每讀入一個數,我們就在它所在的塊對應的計數器加1。
處理結束之后, 我們找到一個塊,它的計數器值小於塊大小(1000), 說明了這一段里面一定有數字是文件中所不包含的。然后我們單獨處理
這個塊即可。接下來我們就可以用Bit Map算法了。我們再遍歷一遍數據, 把落在這個塊的數對應的位置1(我們要先把這個數
歸約到0到blocksize之間)。 最后我們找到這個塊中第一個為0的位,其對應的數就是一個沒有出現在該文件中的數。)
4、 Bit-Map的應用
1)可進行數據的快速查找,判重,刪除,一般來說數據范圍是int的10倍以下。
2)去重數據而達到壓縮數據
5、 具體實現(JAVA)
【問題實例】
1)已知某個文件內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數。
8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。
位圖法需要的空間很少(依賴於數據分布,但是我們也可以通過一些放啊發對數據進行處理,使得數據變得密集),在數據比較密集的時候效率非常高。例如:8位整數可以表示的最大十進制數值為99999999,如果每個數組對應於一個bit位,那么把所有的八進制整數存儲起來只需要:99Mbit = 12.375MB.
實際上,Java jdk1.0已經提供了bitmap的實現BitSet類,不過其中的某些方法是jdk1.4之后才有的。
分別使用自己實現的BitMap和jdk的BitSet類:
1 //去除重復並排序 2 import java.util.Arrays; 3 import java.util.BitSet; 4 import java.util.Random; 5 6 /** 7 * @author 8 * @date Time: 9 * @des: 10 */ 11 public class BitMap { 12 int ARRNUM = 800; 13 int LEN_INT = 32; 14 int mmax = 9999; 15 int mmin = 1000; 16 int N = mmax - mmin + 1; 17 18 public static void main(String args[]) { 19 new BitMap().findDuplicate(); 20 new BitMap().findDup_jdk(); 21 } 22 23 public void findDup_jdk() { 24 System.out.println("*******調用JDK中的庫方法--開始********"); 25 BitSet bitArray = new BitSet(N); 26 int[] array = getArray(ARRNUM); 27 for (int i = 0; i < ARRNUM; i++) { 28 bitArray.set(array[i] - mmin); 29 } 30 int count = 0; 31 for (int j = 0; j < bitArray.length(); j++) { 32 if (bitArray.get(j)) { 33 System.out.print(j + mmin + " "); 34 count++; 35 } 36 } 37 System.out.println(); 38 System.out.println("排序后的數組大小為:" + count ); 39 System.out.println("*******調用JDK中的庫方法--結束********"); 40 } 41 //下面是自己實現的方法: 42 public void findDuplicate() { 43 int[] array = getArray(ARRNUM); 44 int[] bitArray = setBit(array); 45 printBitArray(bitArray); 46 } 47 48 public void printBitArray(int[] bitArray) { 49 int count = 0; 50 for (int i = 0; i < N; i++) { 51 if (getBit(bitArray, i) != 0) { 52 count++; 53 System.out.print(i + mmin + "\t"); 54 } 55 } 56 System.out.println(); 57 System.out.println("去重排序后的數組大小為:" + count); 58 } 59 60 public int getBit(int[] bitArray, int k) {// 1右移 k % 32位 與上 數組下標為 k/32 位置的值 61 return bitArray[k / LEN_INT] & (1 << (k % LEN_INT)); 62 } 63 64 public int[] setBit(int[] array) {// 首先取得數組位置下標 i/32, 然后 或上 65 // 在該位置int類型數值的bit位:i % 32 66 int m = array.length; 67 int bit_arr_len = N / LEN_INT + 1; 68 int[] bitArray = new int[bit_arr_len]; 69 for (int i = 0; i < m; i++) { 70 int num = array[i] - mmin; 71 bitArray[num / LEN_INT] |= (1 << (num % LEN_INT)); 72 } 73 return bitArray; 74 } 75 76 public int[] getArray(int ARRNUM) { 77 78 @SuppressWarnings("unused") 79 int array1[] = { 1000, 1002, 1032, 1033, 6543, 9999, 1033, 1000 }; 80 81 int array[] = new int[ARRNUM]; 82 System.out.println("數組大小:" + ARRNUM); 83 Random r = new Random(); 84 for (int i = 0; i < ARRNUM; i++) { 85 array[i] = r.nextInt(N) + mmin; 86 } 87 88 System.out.println(Arrays.toString(array)); 89 return array; 90 } 91 }
2)2.5億個整數中找出不重復的整數的個數,內存空間不足以容納這2.5億個整數。
將bit-map擴展一下,用2bit表示一個數即可,0表示未出現,1表示出現一次,2表示出現2次及以上,在遍歷這些數的時候,如果對應位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現這個2bit-map,都是一樣的道理。
給你一個文件,里面包含40億個整數,寫一個算法找出該文件中不包含的一個整數, 假設你有1GB內存可用。
如果你只有10MB的內存呢?
這個是剛剛說到的那個題,具體java實現,跟1類似,有時間的話我來補充完整。