快速统计二进制中1的个数(分析篇)

    xiaoxiao2026-05-02  12

    今天做了一道题,发现n&=(n-1)这个式子很好奇,然后试着算了一遍发现它竟然能够快速统计二进制1的个数,特此拿来分享一下。

    首先,分析一下该式子,先可以简化为

    n=n&(n-1);

    我们先做一个实例,

    n12345678十进制12345678二进制00010010001101000101011001111000

    我们先试着求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)这个比较有代表性

    n12345678十进制12345678二进制00010010001101000101011001111000 同样进行&操作,

         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++; }

    转载请注明原文地址: https://ju.6miu.com/read-1309290.html
    最新回复(0)