- #include "stdafx.h"
- #include <iostream>
- using namespace std;
- char *g_bitmap = NULL;
- int g_size = 0;
- int g_base = 0;
- //功能:初始化bitmap
- //參數: size:bitmap的大小,即bit位的個數
- // start:起始值
- //返回值:0表示失敗,1表示成功
- int bitmap_init(int size, int start)
- {
- g_size = size/8+1;
- g_base = start;
- g_bitmap = new char[g_size];
- if(g_bitmap == NULL)
- {
- return 0;
- }
- memset(g_bitmap, 0x0, g_size);
- return 1;
- }
- //功能:將值index的對應位設為1
- //index:要設的值
- //返回值:0表示失敗,1表示成功
- int bitmap_set(int index)
- {
- int quo = (index-g_base)/8 ; //確定所在的字節
- int remainder = (index-g_base)%8; //字節內的偏移
- unsigned char x = (0x1<<remainder);
- if( quo > g_size)
- return 0;
- g_bitmap[quo] |= x; //所在字節內的特定位置為1
- return 1;
- }
- //功能:取bitmap第i位的值
- //i:待取位
- //返回值:-1表示失敗,否則返回對應位的值
- int bitmap_get(int i)
- {
- int quo = (i)/8 ;
- int remainder = (i)%8;
- unsigned char x = (0x1<<remainder);
- unsigned char res;
- if( quo > g_size)
- return -1;
- res = g_bitmap[quo] & x;
- return res > 0 ? 1 : 0;
- }
- //功能:返回index位對應的值
- int bitmap_data(int index)
- {
- return (index + g_base);
- }
- //釋放內存
- int bitmap_free()
- {
- delete [] g_bitmap;
- return 0;
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- int a[] = {5,8,7,6,3,1,10,78,56,34,23,12,43,54,65,76,87,98,89,100};
- int i;
- bitmap_init(100, 0);
- for(i=0; i<20; i++)
- {
- bitmap_set(a[i]);
- }
- for(i=0; i<=100; i++)
- {
- if(bitmap_get(i) > 0 )
- cout << bitmap_data(i)<< " ";
- }
- cout << endl;
- bitmap_free();
- return 0;
- }
【問題實例】
1)已知某個文件內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數。
8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。 (可以理解為從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的內存表示了所有的8位數的電話)
2)2.5億個整數中找出不重復的整數的個數,內存空間不足以容納這2.5億個整數。
將bit-map擴展一下,用2bit表示一個數即可,0表示未出現,1表示出現一次,2表示出現2次及以上,在遍歷這些數的時候,如果對應位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現這個 2bit-map,都是一樣的道理。
一、bitmap算法思想
32位機器上,一個整形,比如int a; 在內存中占32bit位,可以用對應的32bit位對應十進制的0-31個數,bitmap算法利用這種思想處理大量數據的排序與查詢.
優點:1.運算效率高,不許進行比較和移位;2.占用內存少,比如N=10000000;只需占用內存為N/8=1250000Byte=1.25M。
缺點:所有的數據不能重復。即不可對重復的數據進行排序和查找。
比如:
第一個4就是
00000000000000000000000000010000
而輸入2的時候
00000000000000000000000000010100
輸入3時候
00000000000000000000000000011100
輸入1的時候
00000000000000000000000000011110
思想比較簡單,關鍵是十進制和二進制bit位需要一個map圖,把十進制的數映射到bit位。下面詳細說明這個map映射表。
二、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位。
三、位移轉換
例如十進制0,對應在a[0]所占的bit為中的第一位:
00000000000000000000000000000001
0-31:對應在a[0]中
i =0 00000000000000000000000000000000
temp=0 00000000000000000000000000000000
answer=1 00000000000000000000000000000001
i =1 00000000000000000000000000000001
temp=1 00000000000000000000000000000001
answer=2 00000000000000000000000000000010
i =2 00000000000000000000000000000010
temp=2 00000000000000000000000000000010
answer=4 00000000000000000000000000000100
i =30 00000000000000000000000000011110
temp=30 00000000000000000000000000011110
answer=1073741824 01000000000000000000000000000000
i =31 00000000000000000000000000011111
temp=31 00000000000000000000000000011111
answer=-2147483648 10000000000000000000000000000000
32-63:對應在a[1]中
i =32 00000000000000000000000000100000
temp=0 00000000000000000000000000000000
answer=1 00000000000000000000000000000001
i =33 00000000000000000000000000100001
temp=1 00000000000000000000000000000001
answer=2 00000000000000000000000000000010
i =34 00000000000000000000000000100010
temp=2 00000000000000000000000000000010
answer=4 00000000000000000000000000000100
i =61 00000000000000000000000000111101
temp=29 00000000000000000000000000011101
answer=536870912 00100000000000000000000000000000
i =62 00000000000000000000000000111110
temp=30 00000000000000000000000000011110
answer=1073741824 01000000000000000000000000000000
i =63 00000000000000000000000000111111
temp=31 00000000000000000000000000011111
answer=-2147483648 10000000000000000000000000000000
淺析上面的對應表:
1.求十進制0-N對應在數組a中的下標:
十進制0-31,對應在a[0]中,先由十進制數n轉換為與32的余可轉化為對應在數組a中的下標。比如n=24,那么 n/32=0,則24對應在數組a中的下標為0。又比如n=60,那么n/32=1,則60對應在數組a中的下標為1,同理可以計算0-N在數組a中的下標。
2.求0-N對應0-31中的數:
十進制0-31就對應0-31,而32-63則對應也是0-31,即給定一個數n可以通過模32求得對應0-31中的數。
3.利用移位0-31使得對應32bit位為1.
四、編程實現
- #include <stdio.h>
- #define BITSPERWORD 32
- #define SHIFT 5
- #define MASK 0x1F
- #define N 10000000
- int a[1 + N/BITSPERWORD];//申請內存的大小
- //set 設置所在的bit位為1
- //clr 初始化所有的bit位為0
- //test 測試所在的bit為是否為1
- void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }
- void clr(int i) { a[i>>SHIFT] &= ~(1<<(i & MASK)); }
- int test(int i){ return a[i>>SHIFT] & (1<<(i & MASK)); }
- int main()
- { int i;
- for (i = 0; i < N; i++)
- clr(i);
- while (scanf("%d", &i) != EOF)
- set(i);
- for (i = 0; i < N; i++)
- if (test(i))
- printf("%d\n", i);
- return 0;
- }
解析本例中的void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }
1.i>>SHIFT:
其中SHIFT=5,即i右移5為,2^5=32,相當於i/32,即求出十進制i對應在數組a中的下標。比如i=20,通過i>>SHIFT=20>>5=0 可求得i=20的下標為0;
2.i & MASK:
其中MASK=0X1F,十六進制轉化為十進制為31,二進制為0001 1111,i&(0001 1111)相當於保留i的后5位。
比如i=23,二進制為:0001 0111,那么
0001 0111
& 0001 1111 = 0001 0111 十進制為:23
比如i=83,二進制為:0000 0000 0101 0011,那么
0000 0000 0101 0011
& 0000 0000 0001 0000 = 0000 0000 0001 0011 十進制為:19
i & MASK相當於i%32。
3.1<<(i & MASK)
相當於把1左移 (i & MASK)位。
比如(i & MASK)=20,那么i<<20就相當於:
0000 0000 0000 0000 0000 0000 0000 0001 >>20
=0000 0000 0000 1000 0000 0000 0000 0000
4.void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }等價於:
void set(int i)
{
a[i/32] |= (1<<(i%32));
}