求二進位制中的1/0的個數
設x為二進位制數1111 1111 1111 1010,則求x中0的個數操作:
int countofZero(int x)
{
int N = 0;
while (x + 1)
{
N++;
x |= (x + 1);
}
return N;
}
求x中1的個數操作:
int countOfOne(x)
{
int N = 0;
while(x)
{
N++;
x &= (x - 1);
}
return N;
}
昨天刷題的時候刷到MIT HAKMEM演算法,用於求整型數中的'1'的個數,閱讀了幾篇文章,在這裡整理一下:
演算法如下:
int bitcount(unsigned int n)
{
unsigned int tmp;
tmp = n
- ((n >> 1) & 033333333333)
- ((n >> 2) & 011111111111);
tmp = (tmp + (tmp >> 3)) & 030707070707;
return (tmp%63);
}
分析:
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。
3. 因此,對一個係數為{Ai}的以n為底的多項式P(N), P(N)%(n-1) = (sum({Ai})) % (n-1) 。
4. 將32位二進位制數的每6位作為一個單位,看作以64(2^6)為底的多項式:
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)
int bitcount(unsigned int n)
{
unsigned int tmp;
tmp = (n &010101010101)
+((n>>1)&010101010101)
+((n>>2)&010101010101)
+((n>>3)&010101010101)
+((n>>4)&010101010101)
+((n>>5)&010101010101);
return (tmp%63);
}
第一步優化:
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)
int bitcount(unsigned int n)
{
unsigned int tmp;
tmp = (n &011111111111)
+((n>>1)&011111111111)
+((n>>2)&011111111111);
tmp = (tmp + (tmp>>3)) &030707070707;
return (tmp%63);
}
注:程式碼中是使用8進位制數進行MASK的,11位8進位制數為33位2進位制數,多出一位,因此第一位八進位制數會把最高位捨去(7->3)以免超出int長度。
從第一個版本到第二個實際上是一個“提取公因式”的過程。用1組+, >>, &運算代替了3組。並且已經提取了"最大公因式"。
第二步優化:
又減少了一組+, >>, &運算。被優化的是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是為了遮蔽"組間""串擾",即遮蔽掉從左邊組的低位移動過來的數。
以上,這個演算法分析我還沒有看得太懂,先標記一下,下次再看~~~