看到一篇博客,发现n&=(n-1)竟然能够快速统计二进制1的个数,经过博主同意特此拿来分享一下。
首先,分析一下该式子,先可以简化为
n=n&(n-1);
我们先做一个实例,
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
十进制 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
二进制 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 |
我们先试着求7中二进制1的个数k
0111
& 0110
-------------
0110(6)
然后对其结果继续进行上述操作
0110
& 0101
-------------
0100(4)
继续上述操作
0100
& 0011
-------------
0000
结束操作。
至此,我们可以发现,每次&操作后,0111(7)的二进制数中最右边的1都被消除1个,
第一次&操作后,结果为0110
第二次&操作后,结果为0100
第三次&操作后,结果为0000
而k恰好与&操作的次数是相同的。那么我们可以猜测这两者必然存在直接关联关系。
不过,当二进制数中1的位置不连续,中间有若干个0这个公式也能“跳过”0,直接统计1的个数吗?
下面我们在举个实例37(0010 0101)这个比较有代表性
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
十进制 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
二进制 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 |
100101
& 100100(36)
----------------------
100100(36)
继续重复上述操作
100100
& 100011
--------------------
100000(32)
继续重复上述操作
100000
& 011111
--------------------
000000
结束。
到这里,我们会得出一个结论,
1的位置无非就三种情况,头和中间、尾部
a.在尾部的情况
当末尾是1的时候(奇数),与前一个数(偶数)进行&操作后,结果必为0,末尾的1消除
b.在头部的情况
当1只在头部的时候,其余位上都为0.类似8(1000),4(0100),与前一个数(全1,7(0111),3(0011))进行&操作时,结果必为0.
c.在中间的情况
无非也是上述两种情况的递归。保证末尾位为0,因为之前已经处理过尾部的情况了。
比如,尾部是*0010结尾,*0100结尾,*1010结尾,*11010结尾。他们对应的前一个数分别是:*0001,*0011,*1001,*11001。(*代表左边还有若干个0和1)
0010
& 0001
-------------
0000
结束。
0100
& 0011
-------------
0000
结束。
1010
& 1001
--------------
1000(消除最右边的1,下一步进行第二种情况的处理)
结束。
11010
& 11001
---------------
11000(消除最右边的1,下一步进行第一种情况的处理)
结束。
--------------------------------分割线-------------------------------------
到这里,我们就可以看出,每次在最右的1设置一个flag的话,
当它(i)与它前一位(i-1)进行&操作时,对flag左边的1是没有影响的,每次得到的结果,就会将flag位置及右边所有的数置为0.
例如:11010&11001 ==11000(24)
那么,结束条件是什么呢?
那就是当&操作后的结果为0,循环结束。
好啦,分析就到这里。下面附上源代码供看官们欣赏哈~
-
for (
int count =
0; n; ++count)
-
{
-
n &= (n
-1) ;
//每次消除最右边的1,当n为0结束
-
}
另一种写法:
-
count=
0
-
while(k){
-
k=k&(k
-1);
-
count++;
-
}