思路: 先判断整数二进制表示中最右一位是否为1,再把输入的整数右移一位,再判断最右一位是否为1。直到整个数变为零。
将整数和1做位与运算看结果是否为0即可判断该整数最右一位是否为1。与运算结果为1时,表示该整数最右一位是1。
1、普通算法:
int NumberOf1( int n)
{
int count =0 ; // 计数器
while (n)
{
if(n & 1) // 当前位是1
++count ; // 计数器加1
n >>=1 ; // 移位
}
return count ;
}
上面的程序会被问两个问题:
1、整数右移一位和把整数除以2在数学上是等价的,上面的代码中可以把右移运算换成除以2吗?
答案是否定的!!!因为除法的效率比移位运算要低的多。
2、若是个负数右移会出项什么情况?(如0X80000000)
解析:负数移位后仍然是负数,移位后最高位会设为1。0X80000000右移一位是0XC0000000并不是0X40000000。如果,一直将一个负数做右移运算,那么这个数 字最终将会变成0xFFFFFFFF而陷入死循环。
为避免死循环,我们可以考虑不右移输入数字。
先将n和1做与运算,判断最低位是不是为1。接着把1左移一位得到2,再把n做与运算,就能判断n的次低位是不是1……如此反复左移,每次都能判断n的其中一位是否为1.
int BitCount1( int n)
{
int count =0 ; // 计数器
unsigned int flag=1;
while(flag)
{
if(n & flag)
count++;
flag = flag << 1;
}
return count;
}
2、快速算法(s)
将一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0。那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
int BitCount2( int n)
{
unsigned int count =0 ;
for (count =0; n; ++count)
{
n &= (n -1); // 清除最低位的1
}
return count ;
}
为什么n &= (n – 1)能清除最右边的1呢?因为从二进制的角度讲,n相当于在n - 1的最低位加上1。举个例子,8(1000)= 7(0111)+ 1(0001),所以8 & 7 = (1000)&(0111)= 0(0000),清除了8最右边的1(其实就是最高位的1,因为8的二进制中只有一个1)。再比如7(0111)= 6(0110)+ 1(0001),所以7 & 6 = (0111)&(0110)= 6(0110),清除了7的二进制表示中最右边的1(也就是最低位的1)。
3、平行算法
【精辟】
int BitCount4(unsigned int n)
{
n = (n &0x55555555) + ((n >>1) &0x55555555) ;
n = (n &0x33333333) + ((n >>2) &0x33333333) ;
n = (n &0x0f0f0f0f) + ((n >>4) &0x0f0f0f0f) ;
n = (n &0x00ff00ff) + ((n >>8) &0x00ff00ff) ;
n = (n &0x0000ffff) + ((n >>16) &0x0000ffff) ;
return n ;
}
接下来开始分析改算法是如何实现二进制表示中1的个数统计的,为了方便理解,我们把代码改成如下的形式:
int func(unsigned int i)
{
unsigned int temp = i;
temp = (temp & 0x55555555) + ((temp>> 1) & 0x55555555); //temp相邻位相加 (32位二进制位01010101010101010101010101010101)
temp = (temp & 0x33333333) + ((temp >> 2) & 0x33333333); //temp相邻(以2为单位)相加(32位二进制位00110011001100110011001100110011)
temp = (temp & 0x0f0f0f0f) + ((temp>> 4) & 0x0f0f0f0f); //temp相邻(以4为单位)相加(32位二进制位00110011001100110011001100110011)
temp = (temp & 0xff00ff) + ((temp>> 8) & 0xff00ff); //temp相邻(以8为单位)相加(32位二进制00001111000011110000111100001111)
temp = (temp & 0xffff) + ((temp>> 16) & 0xffff) ; //temp相邻(以16为单位)相加(32位二进制11111111111111111111111111111111)
return temp;
}
temp相邻位相加:相加原理若相邻的两个数为00则结果为00, 相邻的两个数为01或10则结果为01,相邻两个数为11则结果为10(十进制是2),也就是先小范围统计每两位中1的个数,后面的步骤在累计有多少个1.
0x11530828的二进制表示如下:
0001 0001 1001 0011 0000 1000 0010 1000;
0 1 0 1 1 1 0 2 0 0 1 0 0 1 1 0;
1 1 2 2 0 1 1 1;
2 4 1 2
6 3
9