【轉】算法-求二進制數中1的個數

寫在前面的話 CSAPP課程的lab1有一道這樣的題目,而後老師上課講過一個,就是轉帖的那個完美解法。真心好贊。這幾天在複習考試的時候又把這個帖子翻出來看了~好有魔性。
這裏是做者原來的博客 算法-求二進制數中1的個數感謝感謝!html

問題描述


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

這也是一道比較經典的題目了,相信很多人面試的時候可能遇到過這道題吧,下面介紹了幾種方法來實現這道題,相信不少人可能見過下面的算法,但我相信不多有人見到本文中全部的算法。若是您上頭上有更好的算法,或者本文沒有提到的算法,請不要吝惜您的代碼,分享的時候,也是學習和交流的時候。算法

普通法

我老是習慣叫普通法,由於我實在找不到一個合適的名字來描述它,其實就是最簡單的方法,有點程序基礎的人都能想獲得,那就是移位+計數,很簡單,很少說了,直接上代碼,這種方法的運算次數與輸入n最高位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 ;
}

一個更精簡的版本以下指針

int BitCount1(unsigned int n)
{
    unsigned int c =0 ; // 計數器
    for (c =0; n; n >>=1) // 循環移位
        c += n &1 ; // 若是當前位是1,則計數器加1
    return c ;
}

快速法

這種方法速度比較快,其運算次數與輸入n的大小無關,只與n中1的個數有關。若是n的二進制表示中有k個1,那麼這個方法只須要循環k次便可。其原理是不斷清除n的二進制表示中最右邊的1,同時累加計數器,直至n爲0,代碼以下code

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

爲何n &= (n – 1)能清除最右邊的1呢?由於從二進制的角度講,n至關於在n - 1的最低位加上1。舉個例子,8(1000)= 7(0111)+ 1(0001),因此8 & 7 = (1000)&(0111)= 0(0000),清除了8最右邊的1(其實就是最高位的1,由於8的二進制中只有一個1)。再好比7(0111)= 6(0110)+ 1(0001),因此7 & 6 = (0111)&(0110)= 6(0110),清除了7的二進制表示中最右邊的1(也就是最低位的1)。
(這個方法開始看了很久~結果只總結出一個道理~不要只是看~看不懂就動動筆啊。)htm

查表法

動態建表

因爲表示在程序運行時動態建立的,因此速度上確定會慢一些,把這個版本放在這裏,有兩個緣由blog

  1. 介紹填表的方法,由於這個方法的確很巧妙。get

  2. 類型轉換,這裏不能使用傳統的強制轉換,而是先取地址再轉換成對應的指針類型。也是經常使用的類型轉換方法。博客

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

先說一下填表的原理,根據奇偶性來分析,對於任意一個正整數n

1.若是它是偶數,那麼n的二進制中1的個數與n/2中1的個數是相同的,好比4和2的二進制中都有一個1,6和3的二進制中都有兩個1。爲啥?由於n是由n/2左移一位而來,而移位並不會增長1的個數。

2.若是n是奇數,那麼n的二進制中1的個數是n/2中1的個數+1,好比7的二進制中有三個1,7/2 = 3的二進制中有兩個1。爲啥?由於當n是奇數時,n至關於n/2左移一位再加1。

再說一下查表的原理

對於任意一個32位無符號整數,將其分割爲4部分,每部分8bit,對於這四個部分分別求出1的個數,再累加起來便可。而8bit對應2^8 = 256種01組合方式,這也是爲何表的大小爲256的緣由。
注意類型轉換的時候,先取到n的地址,而後轉換爲unsigned char*,這樣一個unsigned int(4 bytes)對應四個unsigned char(1 bytes),分別取出來計算便可。舉個例子吧,以87654321(十六進制)爲例,先寫成二進制形式-8bit一組,共四組,以不一樣顏色區分(這裏好像無法顯示顏色,不過不影響理解。),這四組中1的個數分別爲4,4,3,2,因此一共是13個1,以下面所示。

10000111 01100101 01000011 00100001 = 4 + 4 + 3 + 2 = 13

(看到這個方法的時候,我居然想到了動態規劃的建表。應該不是一種意思,不過這個思路好巧的說。怎麼仍是以爲有點像動態規劃。)


今天就看到了這裏~明天補上。

相關文章
相關標籤/搜索