Bitcount

昨天又看到了一种bitcount(数一个整数中1的个数)的算法,想自己总结一下各种不同的算法。上网找了一下,发现zdd已经总结过了,这里想加入点自己的理解。

对于没有具体说明的算法,可以参考zdd算法-求二进制数中1的个数

 


 

1. 普通法:就是简单地循环迭代。(Iterated count)

 


 

2. 查表法:以空间换取时间效率。可以是4bit-表,8-bit表或者16-bit表,表越大越快,但是所占的内存空间也就更大。对于32位的整数,也可以使用11-bit的表,把整数分成10-bit, 11-bit, 11-bit三段。

 


 

3. 快速法:运算次数与输入n的大小无关,只与n中1的个数有关。

 1 /***********parse***********/
 2 
 3 int bitcount (unsigned int n)  {
 4    int count = 0 ;
 5    while (n)  {
 6       count++ ;
 7       n &= (n - 1) ;
 8    }
 9    return count ;
10 }

其中的关键就是n &= (n-1), 它把n整数的最右边的1变成了0。所以循环次数只是n中1的个数。上述适用于整数n是parse的。如果整数n是dense的,也就是说有比较多的1,那么可以先把n取反再算。

 1 /***********dense***********/
 2 
 3 int bitcount (unsigned int n)   {
 4    int count = 8 * sizeof(int) ;
 5    n ^= (unsigned int) - 1 ;
 6    while (n)  {
 7       count-- ;
 8       n &= (n - 1) ;
 9    }
10    return count ;
11 }

 


 

4. 平行法:其实我更愿意把这种方法叫做“二分法”,基本的思想类似“二分法”。

 1 #define TO(c)     (0x1u << (c))
 2 #define MASK(c) \
 3   (((unsigned int)(-1)) / (TO(TO(c)) + 1u))
 4 #define COUNT(x,c) \
 5   ((x) & MASK(c)) + (((x) >> (TO(c))) & MASK(c))
 6 
 7 int bitcount (unsigned int n)  {
 8    n = COUNT(n, 0) ;
 9    n = COUNT(n, 1) ;
10    n = COUNT(n, 2) ;
11    n = COUNT(n, 3) ;
12    n = COUNT(n, 4) ;
13    return n ;
14 }

开始n的二进制看成以1-bit为1组,那么0表示该组1的个数为0,1表示该组1的个数为1。然后把相邻组的1的个数相加,变成2-bit为一组。每2-bit存储的是2-bit为一组的1的个数。接着再以4-bit为一组,依次类推。

如果以二分法来看,很容易理解。假设整数n为32位,那么1的个数是高16位为一组1的个数加上低16位为一组1的个数,然后把16-bit再二分,依次类推。

 


 

5. MIT HAKMEM:  很好很强大,太geek了。

1 int BitCount5(unsigned int n) 
2 {
3     unsigned int tmp = n - ((n >> 1) & 033333333333) - ((n >> 2) & 011111111111);
4     return ((tmp + (tmp >> 3)) & 030707070707) % 63;
5 }

先看第4行

return ((tmp + (tmp >> 3)) & 030707070707) % 63;

说明是以3-bit为一组相加,得到的是6-bit为一组,结果用63取模。

为什么以63取模,其实是有根据的。

对于多项式f(n) = A0 * n+ A1 * n1 + A2 * n2 + .... = Σ(Ai) (mod n-1)。

于是如果Ai代表了各组的1的位数,那么用n-1取模可以直接算出Σ(Ai)。不过要求Σ(Ai) < n-1, n如果是32位的,那么32 < n-1, 于是取n = 64。

即6-bit为一组。然后我们可以用上面的二分法,6-bit为一组可以由3-bit为一组合并得到,于是有了

(tmp + (tmp >> 3) & 030707070707)。

(ps: 这里还有一个小的trick, 如果按照上面的二分方式,应该是tmp & 030707070707 + (tmp>>3) & 030707070707, 由于6-bit为一组最大1的个数是6,即(110)2,不可能发生溢出,用三位足够保存,所以可以把&操作提取出来)。

 

上述分析都是基于tmp中是以3-bit为一组,每组中的值是整数n相应3位中1的个数。那么如何得出这个数值呢?很自然地想到就是上述二分法中的办法

tmp = n & 0x01010101 + (n>>1) & 0x01010101 + (n>>2) & 0x01010101;

不过HAKMEM中少了一次&操作,很厉害。

对于3位(abc)2 = 4a+2b+c,

(abc)2>>1 = (0ab)=2a+b,

(abc)2>>2 = (00a)=a

于是第3行就变成a+b+c。

 

大家可以仿照该方法考虑下64bit整数如何做类似的处理。对于并行法,利用上面得出的结论n>33, 还可以化简为如下(不过速度不一定更快,取决于mod操作)

 1 #define TO(c)     (0x1u << (c))
 2 #define MASK(c) \
 3   (((unsigned int)(-1)) / (TO(TO(c)) + 1u))
 4 #define COUNT(x,c) \
 5   ((x) & MASK(c)) + (((x) >> (TO(c))) & MASK(c))
 6 
 7 int bitcount (unsigned int n)  {
 8    n = COUNT(n, 0) ;
 9    n = COUNT(n, 1) ;
10    n = COUNT(n, 2) ;13    return n%255 ;
14 }

 


 

Reference:

1. MIT HAKMEM算法分析

2. 算法-求二进制数中1的个数

3. Fast Bit Counting

 

转载于:https://www.cnblogs.com/lukelouhao/archive/2012/06/12/2546267.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值