求二进制中1的个数

     求二进制中1的个数。对于一个字节(8bit)的变量,求其二进制表示中"1"的个数,要求算法的执行效率尽可能的高。

     编程之美书中给出了如下几种解法:

解法一,每次除二,看是否为奇数,是的话就累计加一,最后这个结果就是二进制表示中1的个数。

解法二,同样用到一个循环,只是里面的操作用位移操作简化了。

   1:  int Count(int v)   
   2:  {   
   3:      int num = 0;
   4:      while (v) {   
   5:          num += v & 0x01;   
   6:          v >>= 1;   
   7:      }   
   8:      return num;   
   9:  } 

解法三,用到一个巧妙的与操作,v & (v -1 )每次能消去二进制表示中最后一位1,利用这个技巧可以减少一定的循环次数。

解法四,查表法,因为只有数据8bit,直接建一张表,包含各个数中1的个数,然后查表就行。复杂度O(1)。

   1:  int countTable[256] = { 0, 1, 1, 2, 1, ..., 7, 7, 8 };   
   2:      
   3:  int Count(int v) {   
   4:      return countTable[v];   
   5:  }

    对以上几种解法,各人比较喜欢解法三。在网上查了下,还有其他很精妙的方法:

解法五:

int BitCount(unsigned int n) 
{

    unsigned int tmp 
= n - ((n >>1&033333333333- ((n >>2&011111111111);
    return ((tmp + (tmp >>3)) &030707070707%63;
}

第一行代码的作用

先说明一点,以0开头的是8进制数,以0x开头的是十六进制数,上面代码中使用了三个8进制数。

将n的二进制表示写出来,然后每3bit分成一组,求出每一组中1的个数,再表示成二进制的形式。比如n = 50,其二进制表示为110010,分组后是110和010,这两组中1的个数本别是2和1。2对应010,3对应001,所以第一行代码结束后,tmp = 010001,具体是怎么实现的呢?由于每组3bit,所以这3bit对应的十进制数都能表示为2^2 * a + 2^1 * b + c的形式,也就是4a + 2b + c的形式,这里a,b,c的值为0或1,如果为0表示对应的二进制位上是0,如果为1表示对应的二进制位上是1,所以a + b + c的值也就是4a + 2b + c的二进制数中1的个数了。举个例子,十进制数6(0110)= 4 * 1 + 2 * 1 + 0,这里a = 1, b = 1, c = 0, a + b + c = 2,所以6的二进制表示中有两个1。现在的问题是,如何得到a + b + c呢?注意位运算中,右移一位相当于除2,就利用这个性质!

4a + 2b + c 右移一位等于2a + b

4a + 2b + c 右移量位等于a

然后做减法

4a + 2b + c –(2a + b) – a = a + b + c,这就是第一行代码所作的事,明白了吧。

第二行代码的作用

在第一行的基础上,将tmp中相邻的两组中1的个数累加,由于累加到过程中有些组被重复加了一次,所以要舍弃这些多加的部分,这就是&030707070707的作用,又由于最终结果可能大于63,所以要取模。

需要注意的是,经过第一行代码后,从右侧起,每相邻的3bit只有四种可能,即000, 001, 010, 011,为啥呢?因为每3bit中1的个数最多为3。所以下面的加法中不存在进位的问题,因为3 + 3 = 6,不足8,不会产生进位。

tmp + (tmp >> 3)-这句就是是相邻组相加,注意会产生重复相加的部分,比如tmp = 659 = 001 010 010 011时,tmp >> 3 = 000 001 010 010,相加得

001 010 010 011

000 001 010 010

---------------------

001 011 100 101

011 + 101 = 3 + 5 = 8。注意,659只是个中间变量,这个结果不代表659这个数的二进制形式中有8个1。

注意我们想要的只是第二组和最后一组(绿色部分),而第一组和第三组(红色部分)属于重复相加的部分,要消除掉,这就是&030707070707所完成的任务(每隔三位删除三位),最后为什么还要%63呢?因为上面相当于每次计算相连的6bit中1的个数,最多是111111 = 77(八进制)= 63(十进制),所以最后要对63取模。

解法六:

对于解法五,网上的讲解刚开始有错误之处,导致愚钝的我想了好久才明白,解法五的可读性的确不高,其实解法五中的思路可以概括为:

第一行代码:将待求数字每三个为一组,分别求出每组中1的个数。

第二行代码:对第一行的结果进行一次相邻归并,把每组三个变成每组6个。

第二行代码:取模操作求出数字中真正1的个数。


如果把其中的归并操作去掉,其实就是:

int BitCount2(unsigned int n) 
{
    unsigned int tmp = n - ((n >>1) &033333333333) - ((n >>2) &011111111111);
    return tmp %7;
}

看起来更加简洁,但是效率可能会比解法五略低,但是可读性更好。








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值