【一】「求二進位制數中1的個數」的幾種方法
**[求二進位制中1的個數。對於乙個位元組(8bit)的變數,求其二進位制表示中"1"的個數,要求演算法的執行效率盡可能的高。
先來看看樣章上給出的幾個演算法:
解法一,每次除二,看是否為奇數,是的話就累計加一,最後這個結果就是二進位制表示中1的個數。
解法二,同樣用到乙個迴圈,只是裡面的操作用位移操作簡化了。
int count(int v)
return num;
}
解法三,用到乙個巧妙的與操作,v & (v -1 )每次能消去二進位制表示中最後一位1,利用這個技巧可以減少一定的迴圈次數。
解法四,查表法,因為只有資料8bit,直接建一張表,包含各個數中1的個數,然後查表就行。複雜度o(1)。
int counttable[256] = ;
int count(int v)
好了,這就是樣章上給出的四種方案,下面談談我的看法。
首先是對演算法的衡量上,複雜度真的是唯一的標準嗎?尤其對於這種資料規模給定,而且很小的情況下,複雜度其實是個比較次要的因素。
查表法的複雜度為o(1),我用解法一,迴圈八次固定,複雜度也是o(1)。至於資料規模變大,變成32位整型,那查表法自然也不合適了。
其次,我覺得既然是這樣乙個很小的操作,衡量的尺度也必然要小,cpu時鐘週期可以作為乙個參考。
再看解法四,查表法看似一次位址計算就能解決,但實際上這裡用到乙個訪存操作,而且第一次訪存的時候很有可能那個陣列不在cache裡,這樣乙個cache miss導致的後果可能就是耗去幾十甚至上百個cycle(因為要訪問記憶體)。所以對於這種「小操作」,這個演算法的效能其實是很差的。
這裡我再推薦幾個解決這個問題的演算法,以32位無符號整型為例。
這裡用的是二分法,兩兩一組相加,之後四個四個一組相加,接著八個八個,最後就得到各位之和了。
還有乙個更巧妙的hakmem演算法
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的個數就行了。
【二】mit hakmem演算法分析
**[今天學習了一種很有趣的bitcount演算法——mit hakmem演算法。
本文中^表示乘方
問題需求:計算32位整型數中的'1'的個數
思路分析:
1.整型數 i 的數值,實際上就是各位乘以權重——也就是乙個以2為底的多項式:
i = a0*2^0+a1*2^1+a2*2^2+...
因此,要求1的位數,實際上只要將各位消權:
i = a0+a1+a2+...
所得的係數和就是'1'的個數。
2.對任何自然數n的n次冪,用n-1取模得數為1,證明如下:
若 n^(k-1) % (n-1) = 1 成立
則 n^k % (n-1) = ((n-1)*n^(k-1) + n^(k-1)) % (n-1) = 0 + n^(k-1) % (n-1) = 1 也成立
又有 n^(1-1) % (n-1) = 1
故對任意非負整數n, n^n %(n-1)=1
3.因此,對乙個係數為的以n為底的多項式p(n), p(n)%(n-1) = (sum()) % (n-1) ;
如果能保證sum() < (n-1),則 p(n)%(n-1) = (sum()) ,也就是說,此時只要用n-1對多項式取模,就可以完成消權,得到係數和。
於是,問題轉化為,將以2為底的多項式轉化為以n為底的多項式,其中n要足夠大,使得n-1 > sum()恆成立。
32位整型數中ai=0或1,sum()<=32。n-1 > 32 ,n需要大於33。
因此取n=2^6=64>33作為新多項式的底。
4.將32位二進位制數的每6位作為乙個單位,看作以64為底的多項式:
i = t0*64^0 + t1*64^1 + t2*64^2 + t3*64^3 + ...
各項的係數ti就是每6位2進製數的值。
這樣,只要通過運算,將各個單位中的6位數變為這6位中含有的'1'的個數,再用63取模,就可以得到所求的總的'1'的個數。
5.取其中任意一項的6位數ti進行考慮,最簡單的方法顯然是對每次對1位進行mask然後相加,即
(ti>>5)&(000001) + (ti&>>4)(000001) + (ti>>3)&(000001) + (ti>>2)&(000001) + (ti>>1)&(000001) + ti&(000001)
其中000001位2進製數
由於ti最多含有6個1,因此上式最大值為000110,絕不會產生溢位,所以上式中的操作完全可以直接對整型數 i 進行套用,操作過程中,t0~t6將並行地完成上式的運算。
注意:不能將&運算提取出來先+後&,想想為什麼。
因此,bit count的實現**如下:
int bitcount(unsigned int n)
但mit hakmem最終的演算法要比上面的**更加簡單一些。
為什麼說上面的式子中不能先把(ti>>k)都先提取出來相加,然後再進行&運算呢?
因為用&(000001)進行mask後,產生的有效位只有1位,只要6位數中的'1'個數超過1位,那麼在"先加"的過程中,得數就會從最低位中向上溢位。
但是我們注意到,6位數中最多只有6個'1',也就是000110,只需要3位有效位。上面的式子實際上是以1位為單位提取出'1'的個數再相加求和求出6位中'1'的總個數的,所以用的是&(000001)。如果以3位為單位算出'1'的個數再進行相加的話,那麼就完全可以先加後mask。演算法如下:
tmp = (ti>>2)&(001001) + (ti>>1)&(001001) + ti&(001001)
(tmp + tmp>>3)&(000111)
c**:
int bitcount(unsigned int n)
注:**中是使用8進製數進行mask的,11位8進製數為33位2進製數,多出一位,因此第一位八進位制數會把最高位捨去(7->3)以免超出int長度。
從第乙個版本到第二個實際上是乙個「提取公因式」的過程。用1組+, >>, &運算代替了3組。並且已經提取了"最大公因式"。然而這仍然不是最終的mit hakmem演算法,不過已經非常接近了,看看**吧。
mit hakmem演算法:
int bitcount(unsigned int n)
又減少了一組+, >>, &運算。被優化的是3位2進製數「組」內的計算。再回到多項式,乙個3位2進製數是4a+2b+c,我們想要求的是a
+b+c,n>>1的結果是2a+b,n>>2的結果是a。
於是: (4a+2b+c) - (2a+b) - (a) = a + b + c
中間的mask是為了遮蔽"組間""串擾",即遮蔽掉從左邊組的低位移動過來的數。
求二進位制數中1的個數
解法一 可以舉乙個八位的二進位制例子來進行分析。對於二進位制操作,我們知道,除以乙個2,原來的數字將會減少乙個0。如果除的過程中有餘,那麼就表示當前位置有乙個1。以10 100 010為例 第一次除以2時,商為1 010 001,余為0。第二次除以2時,商為101 000,余為1。因此,可以考慮利用...
求二進位制數中1的個數
對於乙個位元組 8bit 的變數,求其二進位制表示中 1 的個數,要求演算法的執行效率盡可能地高。解法一 可以舉乙個八位的二進位制例子來進行分析。對於二進位制操作,我們知道,除以乙個2,原來的數字將會減少乙個0。如果除的過程中有餘,那麼就表示當前位置有乙個1。int count int v int ...
求二進位制數中1的個數
對於乙個位元組 8bit 的無符號整型變數,求其二進位制表示中 1 的個數。c codes as below using system class program static void main string args program program new program for int i 0...