首先来看一下问题描述:
Write the function int bitCount(DWORD input) that takes a short as input and returns an int. The function returns the number of bits set in the input variable. For instance:
bitCount(7) --> 3
bitCount(2543) --> 9
bitCount(11111) –> 9
《编程之美》中也存在这道题的描述,不过input类型变为BYTE即unsigned char型(扩展中为DWORD)。需要注意的是,input的数据类型对此题影响很大。这里,我们以32位无符号整数即DWORD(unsigned int)为主,同时会简单介绍一下BYTE的O(1)算法。
算法一:
判断input的最低位是否为1(与1做’&’运算),若是则计数器++;否则,input<<1即右移一位,循环继续。程序写起来也比较简单:
{
int nRet = 0;
while(input != 0)
{
// 表示最低位为1
if(input & 1)
nRet++;
input = input >> 1; // 右移1位
}
return nRet;
}
算法的时间复杂度为O(n),n为二进制的位数。
算法二:
循环时只与位为1的进行判断,利用判断一个数是否为2的n次幂的算法来做。
为了简化描述,我们以8位2进制数为例来解析,32位类似。如:01000000,如何去判断这个二进制数里面有且仅有一个1呢?很自然的想到可以通过判断这个数是否为2的n次幂来实现。假设我们希望这个数和某个数的运算结果为0,则代表该数是2的n次幂,那么00111111便符合要求。即01000000 & 00111111 = 0,而00111111 = 01000000 – 1;连起来即01000000 & 00111111 = 01000000 & (01000000 – 1) = 0;
那么我们就有了数学上的描述,设数为x,那么其是否为2的n次幂就可以通过一个x & ( x – 1 ) == 0来判断(是则返回TRUE,否则FALSE)。
很快写出程序:
{
int nRet = 0;
while(input)
{
input &= (input - 1);
nRet++;
}
return nRet;
}
注意,while循环内第一句为’&=’运算。
算法的时间复杂度为O(M),M为二进制数中为1的位数
算法三:
对于input为BYTE类型的数,可以采用查表法,空间换时间,达到O(1)的时间复杂度,对于input为DWORD来说,这种方法没有意义。
算法四:
通过位运算技巧可得到一个效率极高的算法,同时可通过简单修改适用于各种数位,先给出完整代码:
* 下面这个函数,是针对32位的,至于其它位数稍加修改就可以了。
*
* return number of bits set
*/
#include <stdio.h>
#include <stdlib.h>
unsigned int bitcount32(unsigned int x)
{
x = (x & 0x55555555UL) + ((x >> 1) & 0x55555555UL); // 0-2 in 2 bits
x = (x & 0x33333333UL) + ((x >> 2) & 0x33333333UL); // 0-4 in 4 bits
#if 1
// Version 1
x = (x & 0x0f0f0f0fUL) + ((x >> 4) & 0x0f0f0f0fUL); // 0-8 in 8 bits
x = (x & 0x00ff00ffUL) + ((x >> 8) & 0x00ff00ffUL); // 0-16 in 16 bits
x = (x & 0x0000ffffUL) + ((x >> 16) & 0x0000ffffUL); // 0-31 in 32 bits
return x;
#else
// Version 2
x = (x + (x >> 4)) & 0x0f0f0f0fUL; // 0-8 in 4 bits
x += x >> 8; // 0-16 in 8 bits
x += x >> 16; // 0-32 in 8 bits
return x & 0xff;
#endif
}
int main()
{
short nVal;
int nCount;
printf("Input a value:");
scanf("%d", &nVal);
nCount = bitcount32(nVal);
printf("BitCount:%d\n", nCount);
system("pause");
return 0;
}
下面对代码做下解释,解释大部分引用自http://blog.csdn.net/jiyucn/archive/2006/06/19/812845.aspx 感谢原作者:)
对于二进制数的理解,二进制数每一位只能为0和1,因此在这个地方,我们可以理解为每一位都可以表示该位中包含1的数目。
看bitcount第一行代码:x = (x & 0x55555555UL) + ((x >> 1) & 0x55555555UL);
5的二进制是 0101,当x & 0x55555555UL得出的结果是保留了0, 2, 4, 6 ... , 30 位的值,即 0, 2, 4, 6 ,... , 30 位中包含1的数目;那么自然可以理解(x >> 1) & 0x55555555UL得出的结果是保留了1, 3, 5, 7, ... , 31 位的值,即 1, 3, 5, 7, ..., 31 位中包含1的数目。把两者相加起来:
xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
+0101 0101 0101 0101 0101 0101 0101 0101
------------------------------------------------------------------------
0x0x 0x0x 0x0x 0x0x 0x0x 0x0x 0x0x 0x0x (这里的x即为x中 0, 2, 4, ... , 30 位的值)
0xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
+0101 0101 0101 0101 0101 0101 0101 0101
------------------------------------------------------------------------
0y0y 0y0y 0y0y 0y0y 0y0y 0y0y 0y0y 0y0y (这里的y即为x中 1, 3, 5 , ... , 31 位的值)
0x0x 0x0x 0x0x 0x0x 0x0x 0x0x 0x0x 0x0x
+0y0y 0y0y 0y0y 0y0y 0y0y 0y0y 0y0y 0y0y
------------------------------------------------------------------------
zz zz zz zz zz zz zz zz
可以把32位的X数每两位看成一个最小单元,x+y的值z即为这个两位最小单元中,1的个数。可以看出,巧妙地把两个加数种0的空间用来保存进位。那么到现在为止,我们就得到了数X中,每两位为最小单位,每两位中的数值表示着这两位中1的个数。
依此类推,接下来我们用同样的方法计算以4位为最小单位,4位中的数值表示该4位中1的个数,即:x = (x & 0x33333333UL) + ((x >> 2) & 0x33333333UL);
然后是 8位, 16位,当计算到以32位为最小单位,32位中的数值表示该32位中1的个数的时候,答案就揭晓了。
算法五:
HAKMEM算法,来自http://www.blogjava.net/zellux/archive/2008/04/15/192955.html,这里就不贴代码了,zellux解释的很清楚:)
转自:http://www.cnblogs.com/EricYang/archive/2009/09/07/1561838.html