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

    《编程珠玑》第二章的问题A,给40亿个不重复的unsigned int的整数,没有排过序,然后再给一个数,如果快速判断这个数是否在那40亿个数当中。不考虑内存的情况下,如何解决。

问题先放这里,我们先复习下位运算基础知识。

    1.位(bit),即比特,1字节(Byte)=8比特(bit),就是说1个字节有8位,在32机中int占4个字节,在我电脑上VS2010中用sizeof(int)试验过,4个字节就是32位,从0~31.如果是int数组,我们借鉴一张图:

《编程珠玑》之位运算知识_第1张图片

即:

a[0]的比特位为0——31
a[1]的比特位为32——63
a[2]的比特位为64——95
a[3]的比特位为96——127

......

下面开始位运算,研究数组的比特位编号

#include 

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<

结果是输出了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 

using namespace std;

static int a=0;

void SetBit(int bit)
{
 a|=(1<


输出结果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 
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<


运行效果如下:

《编程珠玑》之位运算知识_第2张图片


当然C++中还有一种简单的方法实现,利用bitset
#include 
#include 

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 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;
}
 
    
 
    
 
   

运行效果如下:

《编程珠玑》之位运算知识_第3张图片

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

你可能感兴趣的:(行为日记)