求二進位制中1的個數。對於乙個位元組(8bit)的變數,求其二進位制表示中"1"的個數,要求演算法的執行效率盡可能的高。
先來看看樣章上給出的幾個演算法:
解法一 ,每次除二,看是否為奇數,是的話就累計加一,最後這個結果就是二進位制表示中1的個數。
解法二 ,同樣用到乙個迴圈,只是裡面的操作用位移操作簡化了。
1: int count(int v)
2:
8: return num;
9: }
解法三 ,用到乙個巧妙的與操作,v & (v -1 )每次能消去二進位制表示中最後一位1,利用這個技巧可以減少一定的迴圈次數。
解法四 ,查表法,因為只有資料8bit,直接建一張表,包含各個數中1的個數,然後查表就行。複雜度o(1)。
1: int counttable[256] = ;
2:
3: int count(int v)
好了,這就是樣章上給出的四種方案,下面談談我的看法。
首先是對演算法的衡量上,複雜度真的是唯一的標準嗎?尤其對於這種資料規模給定,而且很小的情況下,複雜度其實是個比較次要的因素。
查表法的複雜度為o(1),我用解法一,迴圈八次固定,複雜度也是o(1)。至於資料規模變大,變成32位整型,那查表法自然也不合適了。
其次,我覺得既然是這樣乙個很小的操作,衡量的尺度也必然要小,cpu時鐘週期可以作為乙個參考。
再 看解法四,查表法看似一次位址計算就能解決,但實際上這裡用到乙個訪存操作,而且第一次訪存的時候很有可能那個陣列不在cache裡,這樣乙個 cache miss導致的後果可能就是耗去幾十甚至上百個cycle(因為要訪問記憶體)。所以對於這種「小操作」,這個演算法的效能其實是很差的。
這裡我再推薦幾個解決這個問題的演算法,以32位無符號整型為例。
1: int count(unsigned x)
這裡用的是二分法,兩兩一組相加,之後四個四個一組相加,接著八個八個,最後就得到各位之和了。
還有乙個更巧妙的hakmem演算法
1: int count(unsigned x)
首先是將二進位制各位三個一組,求出每組中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的個數就行了。
int count( int a, int b)
return num;
};
程式設計之美 求二進位制數中1的個數
題目 對於乙個位元組的無符號整型變數,求其二進位制表示中 1 的個數,要求演算法的執行效率盡可能高。題目很簡單,一般人都可以用最直接的方法求解出來,通過求餘和模除運算。對二進位制操作過程中,除以乙個2,原來的數就會少乙個0,如果除過程中余1,則表示當前位置有乙個1,計數值加1,很簡單。但從執行效率來...
《程式設計之美》 求二進位制數中1的個數
問題 求乙個位元組 8bit 的無符號整型變數二進位制表示中 1 的個數。要求執行效率盡可能高。分析與解法 解法一 每次除2取餘,若為奇數則累加,最終累加結果為 1 的個數。如,10100010除以2,商為1010001,餘數為0 1010001除以2,商為101000,餘數為1。時間複雜度o lo...
程式設計之美 求二進位制數中1的個數
文中講了五種方法 1.迴圈利用除法和模運算。該方法最容易想到。可謂沒有技術含量的。2.迴圈利用位移和按位與。用位操作代替算術運算,提高效率。3.只考慮數字中的1。有經驗規律知 對於2的方冪n與n 1取與為0.因而,可以利用該規律想法怎麼把原數字變為0,經過的步數就是1的個數。如果沒有相應的經驗或常識...