《編程之美——微軟技術面試心得》讀書筆記
“求位元中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權威編程——讓人驚喜的變化
新一代介面編程體驗
橫評:一本書,兩個人