今天遇到一個面試題:
如何統計一個二進制整數num中1的個數.這裏參考了
https://blog.csdn.net/peiyao456/article/details/51724099
的第4種思路,非常巧妙,這裏寫一下心得筆記
我們以8位整數爲例,
首先輸入num可以看成一個二進制序列
num= a1 b1 a2 b2 a3 b3 a4 b4,
可以認爲他們自動分成8組,每組長度爲1,即
num = {a1},{b1},{a2},{b2},{a3},{b3},{a4},{b4},
每組中的值正好就是本組中1的個數.
然後我們把每2組合併成新的一組(現在每組的長度爲2),可以得到
num= {a1,b1}{a2,b2}{a3,b3}{a4,b4}
我們的目的是希望計算新的每一組的和,那麼具體步驟是
首先構造一個單組掩碼g_mask,長度等於每組的長度,左半部分都爲0,右半部分都爲1,
也就是g_mask=01,(這個掩碼的作用就是每一組的值和它進行與運算之後,只保留第2個元素,左半部分爲0!)
然後把每組的g_mask合併起來得到一個總的掩碼mask,即
mask=01010101=0x55
然後我們計算num&mask,這一步的目的是把每組左半部分的值爲0,
即
num&mask = {0,b1}{0,b2}{0,b3}{0,b4}
然後我們要得到每一組的左半部分,並且要和右半部分對齊,以便相加,實現這一步的技巧爲
首先計算 num>>s,其中s爲組長的一半,即
num>>s = {a1,a1}{b1,a2}{b2,a3}{b3,a4}
然後再和mask進行與運算,以只保留右半部分,即
(num>>s)&mask = {0,a1}{0,a2}{0,a3}{0,a4}
然後我們計算
num&mask+(num>>s)&mask,這就相當於計算
{0,b1}{0,b2}{0,b3}{0,b4}
+{0,a1}{0,a2}{0,a3}{0,a4}
={a1+b1},{a2+b2},{a3+b3},{a4+b4}
然後對新的數據繼續進行兩兩合併,
每次重新計算
n*=2; //兩兩合併就意味着每次分組長度擴大一倍,同時分組數目減少一半
s=n/2; //s始終爲當前分組長度的一半,這樣才能保證每組右移s位之後,左元素和右元素對齊,以便相加
g_mask=1<<s-1; g_mask左邊是s個0,右邊是s個1
mask=n個g_mask連接起來,總長度爲整數位長L
right = num>>mask; //每組只保留右半部分,左半部分爲0
left = (num>>s)&mask; //把每組的左半部分移動到右部
num = left+right; //計算每組的和,現在每組中的值都等於原始數據中對應那一組中的1的個數
直到最後只剩一組爲止,循環結束.最後得到的結果即爲所求
下面是一個具體的計算例子,
輸入數據:
a爲輸入的二進制整數
a=1111 1110 0001 0010 1001 1010 1011 1100
L=整數位數=32
一共18個1,預期輸出18.
計算步驟(n和s每次都乘2)
原始分組
num = {1} {1} {1} {1} {1} {1} {1} {0} {0} {0} {0} {1} {0} {0} {1} {0} {1} {0} {0} {1} {1} {0} {1} {0} {1} {0} {1} {1} {1} {1} {0} {0}
step1
將num兩兩合併得到
{1,1} {1,1} {1,1} {1,0} {0,0} {0,1} {0,0} {1,0} {1,0} {0,1} {1,0} {1,0} {1,0} {1,1} {1,1} {0,0}
現在分組長度n=2,移位長度爲s=n/2=1,分組數爲16,單組mask爲01,
總mask爲 16個二進制01即0x5555 5555
right=num&mask的結果爲
{0,1} {0,1} {0,1} {0,0} {0,0} {0,1} {0,0} {0,0} {0,0} {0,1} {0,0} {0,0} {0,0} {0,1} {0,1} {0,0}
num>>s的結果爲
{0,1} {1,1} {1,1} {1,1} {0,0} {0,0} {1,0} {0,1} {0,1} {0,0} {1,1} {0,1} {0,1} {0,1} {1,1} {1,0}
left=(num>>s) &mask的結果爲
{0,1} {0,1} {0,1} {0,1} {0,0} {0,0} {0,0} {0,1} {0,1} {0,0} {0,1} {0,1} {0,1} {0,1} {0,1} {0,0}
left+right的結果爲
b={1,0} {1,0} {1,0} {0,1} {0,0} {0,1} {0,0} {0,1} {0,1} {0,1} {0,1} {0,1} {0,1}{1,0} {1,0} {0,0}
step2
繼續將b兩兩合併
得到
b={1,0,1,0} {1,0,0,1} {0,0,0,1} {0,0,0,1} {0,1,0,1} {0,1,0,1} {0,1,1,0} {1,0,0,0}
現在分組長度n=4,移位長度爲s=n/2=2,分組數爲8,單組mask爲0011,
總mask爲 8個二進制0011即0x33333333
然後計算 c= b&mask+(b>>s) &mask,得到
c=0100 0011 0001 0001 0010 0010 0011 0010
step3,
現在分組長度n=8,移位長度爲s=n/2=4,分組數爲4,單組mask爲00001111,
總mask爲 0x0f0f0f0f
計算d= c&mask+(c>>s)&mask
得到
d=0000 0111 0000 0010 0000 0100 0000 0101
step4.
現在分組長度n=16,移位長度爲s=n/2=8,分組數爲2,單組mask爲0x00ff
總mask爲 0x00ff00ff
計算e= d&mask+(d>>s)&mask
得到
e=0000 0000 0000 1001 0000 0000 0000 1001
step 5
現在分組長度n=32,移位長度爲s=n/2=16,分組數爲1,單組mask爲0x0000ffff
總mask爲 0x0000ffff
計算f= e&mask+(e>>s)&mask
得到
f=0000 0000 0000 0000 0000 0000 0001 0010
即10進制的18,現在分組數目爲1,算法結束,f即爲所求結果
下面是上面計算過程的C程序,供讀者參考,並自行修改讓其適應64位的情況
#include <stdio.h>
void print_bin(int value) {
const int L = sizeof(int) * 8; //得到整數的位數
unsigned int mask =1<<(L-1);
for (int i = 0;i < L;++i, mask >>= 1) {
if (i % 4 == 0 && i != 0)
printf(" ");
printf("%d", value&mask ? 1 : 0);
}
printf("\n");
}
void print_bin(int value,int offset) {
const int L = sizeof(int) * 8; //得到整數的位數
unsigned int mask = 1 << (L - 1);
printf("{");
for (int i = 0;i < L;++i, mask >>= 1) {
if (i%offset==0 && i)
{
printf("} {");
}
if (i % offset )
printf(",");
printf("%d", value&mask ? 1 : 0);
}
printf("}");
printf("\n");
}
int count_ones(unsigned int num)
{
unsigned int m_2 = 0x55555555;
unsigned int m_4 = 0x33333333;
unsigned int m_8 = 0x0f0f0f0f;
unsigned int m_16 = 0x00ff00ff;
unsigned int m_32 = 0x0000ffff;
unsigned int b = (m_2&num) + ((num >> 1)&m_2);
printf("b=");
print_bin(b);
unsigned int c = (m_4&b) + ((b >> 2)&m_4);
printf("c=");
print_bin(c);
unsigned int d = (m_8&c) + ((c >> 4)&m_8);
printf("d=");
print_bin(d);
unsigned int e = (m_16&d) + ((d >> 8)&m_16);
printf("e=");
print_bin(e);
printf("f=");
unsigned int f = (m_32& e) + ((e>>16)&m_32);
print_bin(f);
return f;
}
int main()
{
int a = 0xfe129abc;
printf("a=");
print_bin(a);
int n = count_ones(a);
printf("0x%x has %d ones \n", a,n);
return 0;
}
程序運行結果爲
a=1111 1110 0001 0010 1001 1010 1011 1100
b=1010 1001 0001 0001 0101 0101 0110 1000
c=0100 0011 0001 0001 0010 0010 0011 0010
d=0000 0111 0000 0010 0000 0100 0000 0101
e=0000 0000 0000 1001 0000 0000 0000 1001
f=0000 0000 0000 0000 0000 0000 0001 0010
0xfe129abc has 18 ones
顯然本算法的複雜度爲log(2,L),L爲機器的整數位長.