求二進制數中1的個數


問題描述:

任意給定一個32位無符號整數n,求n的二進制表示中1的個數,比如n = 5(0101)時,返回2,n = 15(1111)時,返回4。

若干解決方案:

普通法:

使用移位操作,判末位是否為1;移位的次數為32。

int BitCount(unsigned int n)
{
    unsigned int c =0 ; // 計數器
    while (n >0)
    {
        if((n &1) ==1) // 當前位是1
            ++c ; // 計數器加1
        n >>=1 ; // 移位
    }
    return c ;
}

快速法:

這個方法我最喜歡,也常用。迭代n=n&(n-1),消除最右邊的1,計數。

int BitCount2(unsigned int n)
{
    unsigned int c =0 ;
    for (c =0; n; ++c)
    {
        n &= (n -1) ; // 清除最低位的1
    }
    return c ;
}

動態表8bit:

使用查表法。制作包含8bit所有整數對應1的個數的表,然后匹配32位n,匹配4次。

int BitCount3(unsigned int n) 
{ 
    // 建表
    unsigned char BitsSetTable256[256] = {0} ; 

    // 初始化表 
    for (int i =0; i <256; i++) 
    { 
        BitsSetTable256[i] = (i &1) + BitsSetTable256[i /2]; 
    } 

    unsigned int c =0 ; 

    // 查表
    unsigned char* p = (unsigned char*) &n ; 

    c = BitsSetTable256[p[0]] + 
        BitsSetTable256[p[1]] + 
        BitsSetTable256[p[2]] + 
        BitsSetTable256[p[3]]; 

    return c ; 
}

靜態表4bit:

所謂靜態表,就是把動態表的制作結果直接放到代碼中去。在數據量小的情況下,一般都會這么做。

int BitCount4(unsigned int n)
{
    unsigned int table[16] = 
    {
        0, 1, 1, 2, 
        1, 2, 2, 3, 
        1, 2, 2, 3, 
        2, 3, 3, 4
    } ;

    unsigned int count =0 ;
    while (n)
    {
        count += table[n &0xf] ;
        n >>=4 ;
    }
    return count ;
}

靜態表8bit:

int BitCount7(unsigned int n)
{ 
    unsigned int table[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, 
    }; 

    return table[n &0xff] +
        table[(n >>8) &0xff] +
        table[(n >>16) &0xff] +
        table[(n >>24) &0xff] ;
}

平行算法:

說實在的,這個我腦子一時轉不過來,不過代碼看起來很優雅。

int BitCount4(unsigned 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 ; 
}

網友的解釋:先將n寫成二進制形式,然后相鄰位相加,重復這個過程,直到只剩下一位。

完美法:

先聲明,這是原作者起的名字,我不贊同。代碼里有取模運算,而取模這種操作,我能不用的時候堅決不用,性能很差。我也沒看懂,下面會貼上網友的注釋。(可能我真的是老了,工作上安逸久了,是壞事。)

int BitCount5(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和3。2對應010,3對應011,所以第一行代碼結束后,tmp = 010011,具體是怎么實現的呢?由於每組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取模。

位標志法:

我沒測,不過,不看好位域的效率。

struct _byte 
{ 
    unsigned a:1; 
    unsigned b:1; 
    unsigned c:1; 
    unsigned d:1; 
    unsigned e:1; 
    unsigned f:1; 
    unsigned g:1; 
    unsigned h:1; 
}; 

long get_bit_count( unsigned char b ) 
{
    struct _byte *by = (struct _byte*)&b; 
    return (by->a+by->b+by->c+by->d+by->e+by->f+by->g+by->h); 
}

指令法:

使用微軟提供的指令,首先要確保你的CPU支持SSE4指令,用Everest和CPU-Z可以查看是否支持。

unsigned int n =127 ;
unsigned int bitCount = _mm_popcnt_u32(n) ;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM