位计数(二进制中1的个数)--读Hacker's Delight

计算一个字中1位的数目有时被称为“种群计数”

 

以一个32位的int为例

 

最朴素的方法:检查最后一位,计数,然后无符号右移。

 

这样的算法复杂度为O(logi),最坏的时候要做32次循环。

 

O(m)算法,m为二进制中1的个数

 

x-1操作将x的二进制表示中的最右边的1改成0,而该位右边所有的0都改成1,x&(x-1)就将x二进制中最右边的1去掉,于是

 

复杂度为O(m),在种群稀疏的字计数中效率尤其明显。如果较为密集,可以改为计数0的个数,然后从32中减去

计数0的个数与计数1的个数操作是对称的:x+1将最右边的0改成1,该0右边的所有1都改成0,x|(x+1)就将x二进制中最右边的0去掉,循环检测条件改成检测该数是否为-1即可。

 

分治算法

 

上述的O(m)算法已经足够巧妙了,然而还存在一般情况下更好的算法。

首先计算二进制位中相邻两个位的和,并将结果存放在这两位中。然后计算相邻的两个两位的和,放在这四位中,以此类推。。。例如

 

1 0 1 1 1 1  0 0 0 1 1 0 0 0  1 1 0 1 1 1 1 1  0 1 1 1 1 1 1 1  1 1 

0 1|1 0|1 0|0 0|0 1|0 1|0 0|1 0|0 1|1 0|1 0|0 1|1 0|1 0|1 0|1 0

0 0 1 1|0 0 1 0| 0 0 1 0|0 0 1 0|0 0 1 1|0 0 1 1 |0 1 0 0|0 1 0 0

0 0 0 0 0 1 0 1| 0 0 0 0 0 1 0 0|0 0 0 0 0 1 1 0|0 0 0 0 1 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 | 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1

 

最后的结果就是1个个数,上例中为23

 

每一步都可以用一个mask与移位之后加法来实现

 

以下是VC6.0下反编译的结果 

每一步需要7个汇编指令,一共需要35个汇编指令。

 

以下是bit_count2的反编译结果 

每一个循环需要10个汇编指令,可见在位数超过4的时候,效率就已经不如bit_count3。

 

优化是没有止境的

 

细细分析bit_count3,还有很多值得优化的地方。

 

首先对于一个二进制数各位数字的和有公式

 

pop(x)=x-(x>>1)-(x>>2)-...-(x>>31)

 

证明:对于一般的32位字,unsigned int的二进制表示的第i位可以表示为

 

                              bi=(x>>i)-((x>>i+1)<<1)

 

从0到31累加即可得到结论。

 

二进制中1的个数也就是二进制中的各位数字之和,一般情况下用上述公式进行计算的效率是不如bit_count3的,但是,在二进制数只有2位的时候,可以直接用x-(x>>1)来计算,可以利用这个结论来优化bit_count3,bit_count3的第一步就是计算相邻两位的各位数字之和。

 

i=(i&0x55555555)+((i>>1)&0x55555555);

就可以改写为

 

i=i-((i>>1)&0x55555555);

 

另外,对于不可能对相邻位产生进位的加法,不需要进行与运算。

2个2位的时候最多为10+10,有进位,故第二步不能省

2个4位的时候最多为100+100,无进位,第三步可以省,但是由于第四步要进行8位相加,故仍然需要用0x0f0f0f0f进行掩码

2个8位的时候最多为1000+1000,可以省,而且剩余位不会对结果产生影响,不需要掩码

16位的时候同理

位数不会超过32,所以最后结果需要对x进行0x0000003F的掩码

 

这样就产生了bit_count4

反汇编的结果如下

需要31条汇编指令。

 

如果我们对每4位进行各位之和的计算

即前两步为

 i-=((i>>1)&0x77777777)+((i>>2)&0x33333333)+((i>>3)&0x11111111);

没什么效果。。。若直接用汇编编写,效率应该还有待提高。。。

 

真的没有止境

 

如果对空间的要求不是那么苛刻的话,为了将时间效率发挥到极致,可以采用查表法。

例如8位的查表法,代码如下

 

效率更高请采用16位查表法。。。。需要65536个字节的空间来存储。。。。

 

位图中的种群计数

 

在一个数组的种群计数中,我们可以用上述方法依次求出每个元素的计数,然后累加。

但是,注意到,4位的计数最多可以到15,而按照上述算法每一个数的4位最多只有4,那么我们运用上述分治算法的前两步,然后3个元素相加,求得和之后,再对和进行后面步数的处理,而8位的情况,每个8位最多是24,8位可以统计255/24=10个字,以此类推,这样的算法效率会很高,但是过多的循环控制语句会大大降低算法节省的效率。

由此,只利用一个中间层次,计算出4个8位部分的和之后,每个8位部分的和最多为8,最多可以将255/8=31个8位和相加而不溢出。以下代码来自Hacker's Delight。。。

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值