《編程珠璣》之位運算知識

    《編程珠璣》第二章的問題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;
}

結果是輸出了4個16,也就是說,一開始的比特位是這樣編排的00000000 00000000 00000000 00000001,向左移4位之後成了這樣00000000 00000000 00000000 00010000,這裏要注意的是,向左移動4位不是把原來0的位置變成1,可以想象成向左推4次,當然越界的0或1不能留,缺了的要補0(保持8位)。

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個小時寫這個博客,搞明白之後寫代碼果然順暢,都怪自己基礎都忘的差不多了,不然位運算這部分就是分分鐘的事。不知道網上這些神人搞明白這個問題用了多久。。。

發佈了23 篇原創文章 · 獲贊 9 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章