「求二進位制數中1的個數」

2021-06-08 00:45:10 字數 3952 閱讀 3406

【一】「求二進位制數中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...