设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'的个数,阅读了几篇文章,在这里整理一下:
(参考(侵删):https://blog.csdn.net/msquare/article/details/4536388)
算法如下:
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是为了屏蔽"组间""串扰",即屏蔽掉从左边组的低位移动过来的数。
以上,这个算法分析我还没有看得太懂,先标记一下,下次再看~~~