MIT HAKMEM算法分析

原文   http://blog.csdn.net/msquare/article/details/4536388

简化版 复习用

问题需求:计算32位整型数中的'1'的个数

思路分析:

1.整型数 i 的数值,实际上就是各位乘以权重——也就是一个以2为底的多项式:

i = A0*2^0+A1*2^1+A2*2^2+...

因此,要求1的位数,实际上只要将各位消权:

i = A0+A1+A2+...

所得的系数和就是'1'的个数。

2.对任何自然数n的N次幂,用n-1取模得数为1,证明

若 n^(k-1) % (n-1) = 1 成立

则 n^k % (n-1) = ((n-1)*n^(k-1) + n^(k-1)) % (n-1) = 0 + n^(k-1) % (n-1)  = 1 也成立

又有 n^(1-1) % (n-1) = 1

故对任意非负整数N, n^N %(n-1)=1

3.因此,对一个系数为{Ai}的以n为底的多项式P(N), P(N)%(n-1) = (sum({Ai})) % (n-1) ;

如果能保证sum({Ai}) < (n-1),则 P(N)%(n-1) = (sum({Ai}))  ,也就是说,此时只要用n-1对多项式取模,就可以完成消权,得到系数和。

于是,问题转化为,将以2为底的多项式转化为以n为底的多项式,其中n要足够大,使得n-1 > sum({Ai})恒成立。

32位整型数中Ai=0或1,sum({Ai})<=32。n-1 > 32 ,n需要大于33。

因此取n=2^6=64>33作为新多项式的底。

4.将32位二进制数的每6位作为一个单位,看作以64为底的多项式:

i = t0*64^0 + t1*64^1 + t2*64^2 + t3*64^3 + ...

各项的系数ti就是每6位2进制数的值。

这样,只要通过运算,将各个单位中的6位数变为这6位中含有的'1'的个数,再用63取模,就可以得到所求的总的'1'的个数。

5.取其中任意一项的6位数ti进行考虑,最简单的方法显然是对每次对1位进行mask然后相加,即

(ti>>5)&(000001) + (ti&>>4)(000001) + (ti>>3)&(000001) + (ti>>2)&(000001) + (ti>>1)&(000001) + ti&(000001)

其中000001位2进制数

由于ti最多含有6个1,因此上式最大值为000110,绝不会产生溢出,所以上式中的操作完全可以直接对整型数 i 进行套用,操作过程中,t0~t6将并行地完成上式的运算。

注意:不能将&运算提取出来先+后&,想想为什么。

因此,bit count的实现代码如下:

[cpp]  view plain copy
  1. int bitcount(unsigned int n)  
  2. {  
  3.     unsigned int tmp;  
  4.   
  5.     tmp = (n &010101010101)  
  6.      +((n>>1)&010101010101)  
  7.      +((n>>2)&010101010101)  
  8.      +((n>>3)&010101010101)  
  9.      +((n>>4)&010101010101)  
  10.      +((n>>5)&010101010101);  
  11.   
  12.     return (tmp%63);  
  13. }  

但MIT HAKMEM最终的算法要比上面的代码更加简单一些。

为什么说上面的式子中不能先把(ti>>k)都先提取出来相加,然后再进行&运算呢?

因为用&(000001)进行MASK后,产生的有效位只有1位,只要6位数中的'1'个数超过1位,那么在"先加"的过程中,得数就会从最低位中向上溢出。

但是我们注意到,6位数中最多只有6个'1',也就是000110,只需要3位有效位。上面的式子实际上是以1位为单位提取出'1'的个数再相加求和求出6位中'1'的总个数的,所以用的是&(000001)。如果以3位为单位算出'1'的个数再进行相加的话,那么就完全可以先加后MASK。算法如下:

tmp = (ti>>2)&(001001) + (ti>>1)&(001001) + ti&(001001)

(tmp + tmp>>3)&(000111)

C代码:

[cpp]  view plain copy
  1. int bitcount(unsigned int n)  
  2. {  
  3.     unsigned int tmp;  
  4.   
  5.     tmp = (n &011111111111)  
  6.      +((n>>1)&011111111111)  
  7.      +((n>>2)&011111111111);  
  8.        
  9.     tmp = (tmp + (tmp>>3)) &030707070707;  
  10.   
  11.     return (tmp%63);  
  12. }  


注:代码中是使用8进制数进行MASK的,11位8进制数为33位2进制数,多出一位,因此第一位八进制数会把最高位舍去(7->3)以免超出int长度。

从第一个版本到第二个实际上是一个“提取公因式”的过程。用1组+, >>, &运算代替了3组。并且已经提取了"最大公因式"。然而这仍然不是最终的MIT HAKMEM算法,不过已经非常接近了,看看代码吧。

MIT HAKMEM算法:

[cpp]  view plain copy
  1. int bitcount(unsigned int n)  
  2. {  
  3.     unsigned int tmp;  
  4.   
  5.     tmp = n  
  6.         - ((n >> 1) & 033333333333)  
  7.         - ((n >> 2) & 011111111111);  
  8.   
  9.     tmp = (tmp + (tmp >> 3)) & 030707070707  
  10.   
  11.     return (tmp%63);  
  12. }  

又减少了一组+, >>, &运算。被优化的是3位2进制数“组”内的计算。再回到多项式,一个3位2进制数是4a+2b+c,我们想要求的是a
+b+c,n>>1的结果是2a+b,n>>2的结果是a。

于是: (4a+2b+c) - (2a+b) - (a) = a + b + c

中间的MASK是为了屏蔽"组间""串扰",即屏蔽掉从左边组的低位移动过来的数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值