转载关于计算二进制数中1的个数

求二进制数中1的个数(摘)


写一个函数,返回数字中二进制位为'1'的个数。
比如36,化为二进制得到100100,其中有2个'1'。

方法1:分别判断各个位
int bit_count(unsigned int n)
{
int count;
for(count = 0; n; n >>= 1)
{
count += n & 1;
}
return count;
}

方法2:循环中直接计算1的数量
如何只数'1'的个数?如果一个数字至少包含一个'1'位,那么这个数字减1将从最低位开始依次向高位借位,直到遇到第一个不为'0'的位。依次借位使得经过的位由原来的'0'变为'1',而第一个遇到的那个'1'位则被借位变为'0'。
36 d = 100100 b
36-1 d = 100011 b
如果最低位本来就是'1',那么没有发生借位。
现在把这2个数字做按位与:n & n-1的结果是什么?
2个数字在原先最低为'1'的位以下(包括这个位)的部分都不同,所以结果是保留了其他的'1'位。
36 & 36-1 d = 100000 b
这个结果刚好去掉了最低的一个'1'位
int bit_count(unsigned int n)
{
int count;
for(count = 0; n; n &= n - 1)
{
count++;
}
return count;
}

由于直接跳过'0'位,这个方法比上面的要略微快一些。



方法3:并行计算的- -这个更快
#define POW(c) (1<<(c))
#define MASK(c) (((unsigned long)-1) / (POW(POW(c)) + 1))
#define ROUND(n, c) (((n) & MASK(c)) + ((n) >> POW(c) & MASK(c)))

int bit_count(unsigned int n)
{
n = ROUND(n, 0);
n = ROUND(n, 1);
n = ROUND(n, 2);
n = ROUND(n, 3);
n = ROUND(n, 4);
return n;
}
一下子看不明白,先把宏展开来:
POW是计算2的幂
MASK很奇怪,一个全1的无符号数字除以2的幂的幂加1?
好在打印出来还能看得懂:
MASK(0) = 55555555 h = 01010101010101010101010101010101 b
MASK(1) = 33333333 h = 00110011001100110011001100110011 b
MASK(2) = 0f0f0f0f h = 00001111000011110000111100001111 b
MASK(3) = 00ff00ff h = 00000000111111110000000011111111 b
MASK(4) = 0000ffff h = 00000000000000001111111111111111 b
这些mask分别把32位数字划分为几个部分。每个部分的前一半和后一半分别是全'0'和全'1'。
MASK(0)分为16个部分,MASK(1)分为8个部分,...
ROUND中对n的处理:(n & MASK) + (n >> POW & MASK)
POW的值刚好是MASK中连续'0'(或者连续'1')的长度。也就是说ROUND把由MASK分开的n的各个部分中的高POW位和低POW位相加。
为了便于说明,取一个简单的部分:MASK(1)的0011
假设n的值为1001,那么ROUND后的结果就是10 + 01 = 11 b,把这个结果赋值给n,这时n的含义由原来的二进制位串变为'1'位的数量。特别的,当ROUND(n, 0)时,把n当作一个32个部分各自'1'位的数量。('0'表示没有'1',而'1'则表示有1个'1')
计算完n = ROUND(n, 0)后,n是一个16个部分各自'1'位数量的'数组',这个'数组'的每个元素只有2个二进制位。最大值为2,足够由2个二进制位来表示。
接下来,计算完n=ROUND(n,1)后,n是一个8个部分各自'1'位数量的'数组',这个'数组'的每个元素只有4个二进制位。最大值为4,足够由4个二进制位来表示。(实际只需要3个二进制位)
...
最后一步,计算n=ROUND(n,4)后,n是一个1个部分各自'1'位数量的'数组',这个'数组'的每个元素有32个二进制位。最大值为32,足够由32个二进制位来表示。(实际只需要6个二进制位)
这个代表32位内'1'位数量的32位二进制数也就是我们要求的结果。


这个方法的好处是只需要5步(log n (n=32)),并且没有循环(或分支),流水线不会被打破。

方法四 静态查表法 效率最高,代价是空间换时间

int countTable[256] =

{

0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8

};

int Count(int v)

{
return countTable[v];
}


方法五 用没位运算的循环实现,比较好理解,效率最低
int Count(int v)
{
int num=0;
while(v)
{
if(v%2==1)
num++;
v=v/2;
}
return num;
}



方法六 位加法 这个跟方法三一样比较复杂

举例说明,考虑2位整数 n=11,里边有2个1,先提取里边的偶数位10,奇数位01,把偶数位右移1位,然后与奇数位相加,因为每对奇偶位相加的和不会超过“两位”,所以结果中每两位保存着数n中1的个数;相应的如果n是四位整数 n=0111,先以“一位”为单位做奇偶位提取,然后偶数位移位(右移1位),相加;再以“两位”为单位做奇偶提取,偶数位移位(这时就需要移2位),相加,因为此时没对奇偶位的和不会超过“四位”,所以结果中保存着n中1的个数,依次类推可以得出更多位n的算法。整个思想类似分治法。

例如:32位无符号数的1的个数可以这样数:

int count_bits(unsigned long n)
{
//0xAAAAAAAA,0x55555555分别是以“1位”为单位提取奇偶位
n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);

//0xCCCCCCCC,0x33333333分别是以“2位”为单位提取奇偶位
n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);

//0xF0F0F0F0,0x0F0F0F0F分别是以“4位”为单位提取奇偶位
n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);

//0xFF00FF00,0x00FF00FF分别是以“8位”为单位提取奇偶位
n = ((n & 0xFF00FF00) >> 8) + (n & 0x00FF00FF);

//0xFFFF0000,0x0000FFFF分别是以“16位”为单位提取奇偶位
n = ((n & 0xFFFF0000) >> 16) + (n & 0x0000FFFF);

return n;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值