看到这个问题我就笑,当年笔试的时候遇到过这个题目,附加的要求是不用循环。我想也没想就用了32个移位累加做了,还好是32位的,卷面也还有些位置。。。用循环的版本如下,我的非循环版本就是去掉for loop然后手写累计32次。。。这里就不写出来了。

unsigned int countBits(unsigned int x)
{
   
const int mask = 1;
   unsigned 
int result = 0;
   
for(short si=0; si<32++si)
      result 
+= (x>>si)&mask;
   
return result;
}

前段时间看到一个网站上列出了所有的bitwise的函数,才知道如何不用循环来做这个算法。真是感叹自己的不动脑筋。。。

 代码如下:

Code

咋一看,比较难理解,其实是用的分而治之的算法思想。

我们先看如果不是32位整形,而是2位,如何来做这道题。

无非4种组合,写个switch cases就搞定了。。。有没有更好的方法呢,如何通过计算(移位,加,减)得到最后的值。

00 ==》0个1 ==》00

01 ==》1个1 ==》01

10 ==》1个1 ==》01

11 ==》2个1 ==》10

 f(n) = n-(n>>1)&0x01

将32位整形分成2位为一组,则有16组,分别计算出1的个数,其算法是f(n)=n-(n>>1)&0x55555555

这样2位一组的计算完毕,可以看到,结果只有0,1,2。

下面以4位一组,组合的可能如下,这里是做加法:

00 和 00 ==》0个1 ==》0000

00 和 01 ==》1个1 ==》0001

00 和 10 ==》1个1 ==》0010

01 和 00 ==》1个1 ==》0010

01 和 01 ==》2个1 ==》0010

01 和 10 ==》3个1 ==》0011

10 和 00 ==》2个1 ==》0010

10 和 01 ==》3个1 ==》0011

10 和 10 ==》4个1 ==》0100

f(AABB) = AA+BB = (AABB>>2)&0011+AABB&0011

即算法是f(n) = n&0x33333333+(n>>2)&0x33333333

运用同样的方法计算4位一组的结果。f(n)=n&0x0f0f0f0f+(n>>4)&0x0f0f0f0f

最后计算8位一组的结果。f(n) = n&0x00ff00ff+(n>>8)&0x00ff00ff.

最最后计算16位一组的结果。f(n) = n&0x0000ffff + (n>>16)&0x0000ffff,即得到最后的结果。但是上面的代码可不是这样写的。

与0x01010101相乘有一个有趣的结果,如果我们有4个字节A,B,C,D。

A,B,C,D->A+B+C+D,B+C+D,C+D,D

显然,高位字节是我们需要的结果,于是f(n)=(n*0x01010101)>>24

Brian Kernighan and Dennis Ritchie的“The C Programming Language”一书中给出了更好更快的一个算法,不过是用循环做的。

不像其它算法,这个方法的性能很大程度上依赖于输入,当少于4个1时,比其他算法优越得多,都是1时最慢。

countBitsLoop

主要的技巧就是x&=x-1:

• 如果 x = 0, 则根本不进 while-loop
• 如果最右一位是1, 则最右一位的x − 1是0. 所有其他位都是一样的x &= x − 1 → x = x − 1.
• 如果最右边的位都是0, 则x看起来是: ...1000. 那么x − 1为: ...0111. x &= x-1的结果是: ...0000.
  总之, x &= x − 1 将所有非点的位清零, 点代表的位不变. 
总的来说,x &= x − 1 将最右边值为1的位变成0.

<<2009-11-2完>>

 posted on 2009-11-02 15:06  Kratos  阅读(1318)  评论(0编辑  收藏  举报