《編程珠璣》第二章的問題A,給40億個不重複的unsigned int的整數,沒有排過序,然後再給一個數,如果快速判斷這個數是否在那40億個數當中。不考慮內存的情況下,如何解決。
問題先放這裏,我們先複習下位運算基礎知識。
1.位(bit),即比特,1字節(Byte)=8比特(bit),就是說1個字節有8位,在32機中int佔4個字節,在我電腦上VS2010中用sizeof(int)試驗過,4個字節就是32位,從0~31.如果是int數組,我們借鑑一張圖:
即:
a[0]的比特位爲0——31
a[1]的比特位爲32——63
a[2]的比特位爲64——95
a[3]的比特位爲96——127
......
下面開始位運算,研究數組的比特位編號
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
int a[4];
for(int i=0;i<4;i++)
{
a[i]=1;
}
for(i=0;i<4;i++)
{
a[i]=a[i]<<4;
cout<<a[i]<<endl;
}
return 0;
}
2.位運算與取模運算、去餘運算
兩個主要的運算:
字節位置=數據/32;(採用位運算即右移5位)
位位置=數據%32;(採用位運算即跟0X1F進行與操作)。0X1F=31=0001 1111
一個例子,33%32,33&31,(33位於a[1]第二個,即第1位,第0位是32)經過計算,兩者答案一樣。32=0010 0000,31=0001 1111,兩者差1位,而33=0010 0001與31進行與運算(&)時,爲0000 0001。我們又發現如果0000 0000左移5位是0010 0000=32,那如果右移呢?33右移5位,0000 0001,十進制爲1;127右移5位,0000 0011,十進制爲3,剛好是數組的第三字節;127&31=0001 1111,即31,表示字節最後的位置。下面是左移5位的代碼,初始值爲0
#include <iostream>
using namespace std;
static int a=0;
void SetBit(int bit)
{
a|=(1<<bit);
}
void ClearBit(int bit)
{
a&=~(1<<bit);
}
int main(int argc, char* argv[])
{
SetBit(5);
cout<<a<<endl;
ClearBit(5);
cout<<a<<endl;
return 0;
}
輸出結果32和0。從上面的位運算的取模取餘運算,我們可以想到存儲一個整數,可以用位圖的方法,例如,可以用如下字符串表示集合{1,2,4,5,8}:
0 1 1 1 0 1 0 0 1 0 0
代表集合中數值的位都置爲1,其他所有的位置爲0。那我們回到問題當中來,要從40億不重複的無序整數找不存在的整數,可以採用位圖的方法,很巧妙吧?我們通常的想法是帶有思維慣性的,一看沒排序,首先想到了要進行排序,40億個整數排序的效率可想而知,然後再找缺失值,各種慢。。。而位圖的方法大大增加了效率,貌似沒有排序,但是位圖完成之後,本身就是個有序的,再找缺失值,只要輸出位圖不是1的就行,很方便,如果內存有限,我們可以分多趟排序,把集合分成幾個部分,每次解決一個部分,這裏先不談。
如給定表示文件中整數集合的位圖數據結構,上面爲題可以分三個階段來解決(摘自《編程珠璣》)
第一階段:將所有的位都置爲0,從而將集合初始化爲空。
第二階段:通過讀入文件中的每個整數來建立集合,將每個對應的位置都置爲1。
第三階段:檢驗每一位,如果該爲爲1,就輸出對應的整數,有此產生有序的輸出文件。
40億個整數,需要40億位,4*10^9/(8*1024*1024)=476.84,所以申請512內存來處理。
3.下面我們模擬一下上面的問題的解決過程,我們簡化一下問題,假如有近100個不重複的正整數,範圍在[1-100]之間,但是現在缺失了一些數,要確定缺失的數據。
因爲是100個數,所以需要100位,100位=12.5字節,一個int類型是4字節,所以需要4個int類型,因此可以申請一個int數組,數組的大小爲4,下面是程序源碼。
#include <iostream>
using namespace std;
static int a[4];
void SetBit(int n) //將邏輯位置爲n的二進制位置爲1
{
a[n>>5] |= (1<<((n&0x1F)-1)); //n>>SHIFT右移5位相當於除以32求算字節位置,n&0x1F相當於對32取餘即求位位置。
}
void ClearBit(int n)//將邏輯位置爲n的二進制位置0
{
a[n>>5] &= ~(1<<((n&0x1F)-1)); //將邏輯位置爲n的二進制位置0,原理同set操作
}
int TestBit(int n)
{
return a[n>>5] & (1<<((n&0x1F)-1)); //測試邏輯位置爲n的二進制位是否爲1,如果爲1,則返回非零值,注意,不是返回1,是返回非零值;如果爲0,則返回0
}
int main(int argc, char* argv[])
{
int b[100]={11,21,31,41,51,61,71,81,91,
2,12,22,32,42,52,62,72,82,92,
3,13,23,33,43,53,63,73,83,93,
4,14,24,34,44,54,64,74,84,94,
5,15,25,35,45,55,65,75,85,95,
6,16,36,56,66,76,86,96,
7,17,27,37,47,57,67,77,87,97,
8,18,28,38,48,58,68,78,88,98,
9,19,29,39,49,59,69,79,89,99,
10,100};
for(int i=0;i<100;i++)//根據數組裏面的數據,將相應的比特位置爲1
{
SetBit(b[i]);
}
for(int i=1;i<=100;i++)
{
if(TestBit(i)==0)
{
cout<<i<<" is not exist"<<endl;
}
else
{
cout<<i<<" is exist"<<endl;
}
}
return 0;
}
運行效果如下:
當然C++中還有一種簡單的方法實現,利用bitset
#include <iostream>
#include<bitset>
using namespace std;
int main(int argc, char *argv[])
{
int b[100]={11,21,31,41,51,61,71,81,91,
2,12,22,32,42,52,62,72,82,92,
3,13,23,33,43,53,63,73,83,93,
4,14,24,34,44,54,64,74,84,94,
5,15,25,35,45,55,65,75,85,95,
6,16,36,56,66,76,86,96,
7,17,27,37,47,57,67,77,87,97,
8,18,28,38,48,58,68,78,88,98,
9,19,29,39,49,59,69,79,89,99,
10,100};
const int max = 100;
int i;
bitset<max+1> bit; //初始默認所有二進制位爲0
for(i=0;i<100;i++) //根據數組裏面的數據,將相應的比特位置爲1
{
bit.set(b[i],1); //將b[i]邏輯位置1
}
for(i=1;i<100;i++)
{
if(bit[i]==1)
printf("%d 存在\n",i);
else
printf("%d is not exist\n",i);
}
return 0;
}
運行效果如下:
還有一個問題是《編程珠璣》提到的,就是43億32位幀數有序文件,找出一個出現至少兩次的整數。只要求找到一個即可,我們可以用位圖(bitmap)來做,前面的問題是不重複的文件,有多少個文件我們用多少位,但是這個是有重複文件,那就要用兩位來表示,00表示沒有,01表示有一個,10表示兩個,11表示兩個以上,也就是說要申請的內存是數據文件個數的兩倍,如果內存有限制,仍然可以用多趟算法解決,二分搜索還沒理解精髓,就不談了。。日後再說。。
總結:
1、bitmap可以用於海量數據的排序
這種情況可能有一些要求,比如數據儘量不重複。如果數據不重複的話,而且空間沒有要求,那麼bitmap是很高效的。當然如果數據有重複,但能知道重複至多不超過多少,也是可以的。例如數據至多重複10次,那麼可以用4位(最多是1111)來表示一個數據,這樣還是比4個字節(一般整數是4個字節)的空間減少了很多。
2、bitmap用於查找缺少的數據、重複的數據
這個問題在上面的描述中都有體現了,40億數據中不包含那個32位的整數;43億數據中哪個數是至少重複兩次的?
參考資料:
http://blog.163.com/xb_stone_yinyang/blog/static/2118160372013625112558579/
http://www.cnblogs.com/biyeymyhjob/archive/2012/08/14/2636933.html
自己花了一個下午把這個問題搞明白,又花了1個小時寫這個博客,搞明白之後寫代碼果然順暢,都怪自己基礎都忘的差不多了,不然位運算這部分就是分分鐘的事。不知道網上這些神人搞明白這個問題用了多久。。。