《編程之美》讀書筆記——“求位元中1的個數”

來源:互聯網
上載者:User

   《編程之美——微軟技術面試心得》讀書筆記

“求位元中1的個數”

by ZelluX 

 

由電子工業出版社博文視點和w3china.org社區聯合舉辦了“看樣章, 寫書評, 贏取《編程之美——微軟技術面試心得》的活動”,詳情請見:http://bbs.w3china.org/dispbbs.asp?boardID=42&ID=61162

大家踴躍參加,活動非常熱烈。下面文章來自讀者ZelluX:

 求二進位中1的個數。對於一個位元組(8bit)的變數,求其二進位表示中"1"的個數,要求演算法的執行效率儘可能的高。

先來看看樣章上給出的幾個演算法:

解法一,每次除二,看是否為奇數,是的話就累計加一,最後這個結果就是二進位表示中1的個數。

解法二,同樣用到一個迴圈,只是裡面的操作用位移操作簡化了。

   1:  int Count(int v)   
   2:  {   
   3:      int num = 0;
   4:      while (v) {   
   5:          num += v & 0x01;   
   6:          v >>= 1;   
   7:      }   
   8:      return num;   
   9:  } 

解法三,用到一個巧妙的與操作,v & (v -1 )每次能消去二進位表示中最後一位1,利用這個技巧可以減少一定的迴圈次數。

解法四,查表法,因為只有資料8bit,直接建一張表,包含各個數中1的個數,然後查表就行。複雜度O(1)。

   1:  int countTable[256] = { 0, 1, 1, 2, 1, ..., 7, 7, 8 };   
   2:      
   3:  int Count(int v) {   
   4:      return countTable[v];   
   5:  }
   
好了,這就是樣章上給出的四種方案,下面談談我的看法。

首先是對演算法的衡量上,複雜度真的是唯一的標準嗎?尤其對於這種資料規模給定,而且很小的情況下,複雜度其實是個比較次要的因素。

查表法的複雜度為O(1),我用解法一,迴圈八次固定,複雜度也是O(1)。至於資料規模變大,變成32位整型,那查表法自然也不合適了。

其次,我覺得既然是這樣一個很小的操作,衡量的尺度也必然要小,CPU刻度可以作為一個參考。

解法一裡有若干次整數加法,若干次整數除法(一般的編譯器都能把它最佳化成位移),還有幾個迴圈分支判斷,幾個奇偶性判斷(這個比較耗時間,根據CSAPP上的資料,一般一個branch penalty得耗掉14個左右的cycle),加起來大概幾十個cycle吧。

再看解法四,查表法看似一次地址計算就能解決,但實際上這裡用到一個訪存操作,而且第一次訪存的時候很有可能那個數組不在cache裡,這樣一個cache miss導致的後果可能就是耗去幾十甚至上百個cycle(因為要訪問記憶體)。所以對於這種“小操作”,這個演算法的效能其實是很差的。

這裡我再推薦幾個解決這個問題的演算法,以32位無符號整型為例。

   1:  int Count(unsigned x) {   
   2:     x = x - ((x >> 1) & 0x55555555);    
   3:     x = (x & 0x33333333) + ((x >> 2) & 0x33333333);    
   4:     x = (x + (x >> 4)) & 0x0F0F0F0F;    
   5:     x = x + (x >> 8);    
   6:     x = x + (x >> 16);    
   7:     return x & 0x0000003F;    
   8:  } 
   
這裡用的是二分法,兩兩一組相加,之後四個四個一組相加,接著八個八個,最後就得到各位之和了。

還有一個更巧妙的HAKMEM演算法

   1:  int Count(unsigned x) {
   2:     unsigned n;    
   3:      
   4:     n = (x >> 1) & 033333333333;    
   5:     x = x - n;   
   6:     n = (n >> 1) & 033333333333;   
   7:     x = x - n;    
   8:     x = (x + (x >> 3)) & 030707070707;   
   9:     x = modu(x, 63);  
   10:     return x;   
   11:  } 
   
首先是將二進位各位三個一組,求出每組中1的個數,然後相鄰兩組歸併,得到六個一組的1的個數,最後很巧妙的用除63取餘得到了結果。

因為2^6 = 64,也就是說 x_0 + x_1 * 64 + x_2 * 64 * 64 = x_0 + x_1 + x_2 (mod 63),這裡的等號表示同餘。

這個程式只需要十條左右指令,而且不訪存,速度很快。

由此可見,衡量一個演算法實際效果不單要看複雜度,還要結合其他情況具體分析。

關於後面的兩道擴充問題,問題一是問32位整型如何處理,這個上面已經講了。

問題二是給定兩個整數A和B,問A和B有多少位是不同的。

這個問題其實就是數1問題多了一個步驟,只要先算出A和B的異或結果,然後求這個值中1的個數就行了。

總體看來這本書還是很不錯的,比較喜歡裡面針對一個問題提出不同演算法並不斷改進的風格。這裡提出一點個人的理解,望大家指正 ;-)

 

推薦話題:

《編程之美》讀書筆記(二): 一摞烙餅的排序問題 

《編程之美》讀書筆記(一):中國象棋將帥問題

資料庫開發人員的《孫子兵法》

PHP5權威編程——讓人驚喜的變化

新一代介面編程體驗

橫評:一本書,兩個人

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.