寫在前面的話 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
介紹填表的方法,由於這個方法的確很巧妙。get
類型轉換,這裏不能使用傳統的強制轉換,而是先取地址再轉換成對應的指針類型。也是經常使用的類型轉換方法。博客
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
(看到這個方法的時候,我居然想到了動態規劃的建表。應該不是一種意思,不過這個思路好巧的說。怎麼仍是以爲有點像動態規劃。)
今天就看到了這裏~明天補上。