轉載關於計算二進制數中1的個數

求二進制數中1的個數(摘)


寫一個函數,返回數字中二進制位爲'1'的個數。
比如36,化爲二進制得到100100,其中有2個'1'。

方法1:分別判斷各個位
int bit_count(unsigned int n)
{
int count;
for(count = 0; n; n >>= 1)
{
count += n & 1;
}
return count;
}

方法2:循環中直接計算1的數量
如何只數'1'的個數?如果一個數字至少包含一個'1'位,那麼這個數字減1將從最低位開始依次向高位借位,直到遇到第一個不爲'0'的位。依次借位使得經過的位由原來的'0'變爲'1',而第一個遇到的那個'1'位則被借位變爲'0'。
36 d = 100100 b
36-1 d = 100011 b
如果最低位本來就是'1',那麼沒有發生借位。
現在把這2個數字做按位與:n & n-1的結果是什麼?
2個數字在原先最低爲'1'的位以下(包括這個位)的部分都不同,所以結果是保留了其他的'1'位。
36 & 36-1 d = 100000 b
這個結果剛好去掉了最低的一個'1'位
int bit_count(unsigned int n)
{
int count;
for(count = 0; n; n &= n - 1)
{
count++;
}
return count;
}

由於直接跳過'0'位,這個方法比上面的要略微快一些。



方法3:並行計算的- -這個更快
#define POW(c) (1<<(c))
#define MASK(c) (((unsigned long)-1) / (POW(POW(c)) + 1))
#define ROUND(n, c) (((n) & MASK(c)) + ((n) >> POW(c) & MASK(c)))

int bit_count(unsigned int n)
{
n = ROUND(n, 0);
n = ROUND(n, 1);
n = ROUND(n, 2);
n = ROUND(n, 3);
n = ROUND(n, 4);
return n;
}
一下子看不明白,先把宏展開來:
POW是計算2的冪
MASK很奇怪,一個全1的無符號數字除以2的冪的冪加1?
好在打印出來還能看得懂:
MASK(0) = 55555555 h = 01010101010101010101010101010101 b
MASK(1) = 33333333 h = 00110011001100110011001100110011 b
MASK(2) = 0f0f0f0f h = 00001111000011110000111100001111 b
MASK(3) = 00ff00ff h = 00000000111111110000000011111111 b
MASK(4) = 0000ffff h = 00000000000000001111111111111111 b
這些mask分別把32位數字劃分爲幾個部分。每個部分的前一半和後一半分別是全'0'和全'1'。
MASK(0)分爲16個部分,MASK(1)分爲8個部分,...
ROUND中對n的處理:(n & MASK) + (n >> POW & MASK)
POW的值剛好是MASK中連續'0'(或者連續'1')的長度。也就是說ROUND把由MASK分開的n的各個部分中的高POW位和低POW位相加。
爲了便於說明,取一個簡單的部分:MASK(1)的0011
假設n的值爲1001,那麼ROUND後的結果就是10 + 01 = 11 b,把這個結果賦值給n,這時n的含義由原來的二進制位串變爲'1'位的數量。特別的,當ROUND(n, 0)時,把n當作一個32個部分各自'1'位的數量。('0'表示沒有'1',而'1'則表示有1個'1')
計算完n = ROUND(n, 0)後,n是一個16個部分各自'1'位數量的'數組',這個'數組'的每個元素只有2個二進制位。最大值爲2,足夠由2個二進制位來表示。
接下來,計算完n=ROUND(n,1)後,n是一個8個部分各自'1'位數量的'數組',這個'數組'的每個元素只有4個二進制位。最大值爲4,足夠由4個二進制位來表示。(實際只需要3個二進制位)
...
最後一步,計算n=ROUND(n,4)後,n是一個1個部分各自'1'位數量的'數組',這個'數組'的每個元素有32個二進制位。最大值爲32,足夠由32個二進制位來表示。(實際只需要6個二進制位)
這個代表32位內'1'位數量的32位二進制數也就是我們要求的結果。


這個方法的好處是隻需要5步(log n (n=32)),並且沒有循環(或分支),流水線不會被打破。

方法四 靜態查表法 效率最高,代價是空間換時間

int countTable[256] =

{

0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8

};

int Count(int v)

{
return countTable[v];
}


方法五 用沒位運算的循環實現,比較好理解,效率最低
int Count(int v)
{
int num=0;
while(v)
{
if(v%2==1)
num++;
v=v/2;
}
return num;
}



方法六 位加法 這個跟方法三一樣比較複雜

舉例說明,考慮2位整數 n=11,裏邊有2個1,先提取裏邊的偶數位10,奇數位01,把偶數位右移1位,然後與奇數位相加,因爲每對奇偶位相加的和不會超過“兩位”,所以結果中每兩位保存着數n中1的個數;相應的如果n是四位整數 n=0111,先以“一位”爲單位做奇偶位提取,然後偶數位移位(右移1位),相加;再以“兩位”爲單位做奇偶提取,偶數位移位(這時就需要移2位),相加,因爲此時沒對奇偶位的和不會超過“四位”,所以結果中保存着n中1的個數,依次類推可以得出更多位n的算法。整個思想類似分治法。

例如:32位無符號數的1的個數可以這樣數:

int count_bits(unsigned long n)
{
//0xAAAAAAAA,0x55555555分別是以“1位”爲單位提取奇偶位
n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);

//0xCCCCCCCC,0x33333333分別是以“2位”爲單位提取奇偶位
n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);

//0xF0F0F0F0,0x0F0F0F0F分別是以“4位”爲單位提取奇偶位
n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);

//0xFF00FF00,0x00FF00FF分別是以“8位”爲單位提取奇偶位
n = ((n & 0xFF00FF00) >> 8) + (n & 0x00FF00FF);

//0xFFFF0000,0x0000FFFF分別是以“16位”爲單位提取奇偶位
n = ((n & 0xFFFF0000) >> 16) + (n & 0x0000FFFF);

return n;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章