《编程珠玑》之位运算知识

来源:互联网 发布:linux shell exit 1 编辑:程序博客网 时间:2024/05/01 16:15

    《编程珠玑》第二章的问题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);elseprintf("%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个小时写这个博客,搞明白之后写代码果然顺畅,都怪自己基础都忘的差不多了,不然位运算这部分就是分分钟的事。不知道网上这些神人搞明白这个问题用了多久。。。

1 0
原创粉丝点击