计算一个int型数值中bit-1的个数

之前面试遇到过这样的问题:写一个方法,统计出一个int型数值中比特值为1的比特个数,后来群里也讨论过,在这里总结一下。
直观的看一下这个问题的解法。
假设一个int型数值为80。
首先,将这个数值转化成二进制形式。

80 = 00000000 00000000 00000000 01010000

然后数一数为1的bit有几个,很明显是2个,那么结果就是2。

但是写程序要怎么做呢?
首先看一个小技巧,x & (x-1)会把x中最右边为1的bit变成0,利用这个小技巧我们可以写出下面的程序:

public static int bitCount(int n){
int count = 0;
while(n != 0){
n = n & (n-1);
count++;
}
return count;
}

这个过程相当于从右往左数1的个数,假设n为-1,就得数32次,相当于一个线性时间的操作,有没有更好的呢?

在Hackers Delight里面介绍了很好的技巧。
这种技巧是一种分治的思想,假设我们要求32位数值的bit-1个数,我们只要计算出前16位和后16位的bit-1个数,然后把他们加起来就行了,那么对于16位的bit-1个数的求法,我们采取同样的方式...直到我们计算2位bit-1个数的时候,我们只要把两个1位bit的值加起来就好了,这相当于是子问题的基本情况,递归的出口。
下面的图展示了这个过程:
[img]http://dl2.iteye.com/upload/attachment/0098/9046/2bdfa96e-4044-36f5-aea9-f28fb7bf34f0.jpg[/img]
注:图片来之Hackers Delight。

按照这个思路,我们便可以写出如下代码:

public static int bitCount1(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;
}

相对于上面线性时间的求法来说,这个"分治"的求法只用了log(32)=5步便完成了运算,非常巧妙。

但是。这个还能优化,首先想一下结果最大也就是32了,所以可以减少一些"与"操作,在最后让n和0x0000003f相与就行了。从上图可以看出,当我们在进行8位到16位步骤的时候,8位的高4位对于我们来说已经没用了,所以从这里开始,我们可以不管高位,减少"与"操作,这样能减少一些指令。
代码如下:

public static int bitCount1(int n){
n = (n & 0x55555555) + ((n >>> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f);
n = n + (n >>> 8);
n = n + (n >>> 16);
return n & 0x0000003f;
}

其实n可以看成是(n & 0x55555555)加上(((n >>> 1) & 0x55555555) << 1),也就是

n = (n & 0x55555555) + (((n >>> 1) & 0x55555555) * 2)
(n & 0x55555555) = n - (((n >>> 1) & 0x55555555) * 2)

将这个代入上面的程序,就得到如下代码:

public static int bitCount2(int n){
n = n - ((n >>> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f);
n = n + (n >>> 8);
n = n + (n >>> 16);
return n & 0x0000003f;
}

这样一来,代码的第一行又减少了一个指令。
OK!

这段代码也被Java采用作为Integer类中bitCount方法的实现。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值