MIT AI Memo 239 - HakMem 算法解析

该算法用于计算整数中1的个数。

首先,来看一个整数的性质

m = x n k n + x n − 1 k n − 1 + . . . + x 1 k 1 + x 0 m = x_nk^n+x_{n-1}k^{n-1}+...+x_1k^1+x_0 m=xnkn+xn1kn1+...+x1k1+x0

m % ( k − 1 ) = ( x n + x n − 1 + . . . + x 1 + x 0 ) % ( k − 1 ) m \% (k-1) = (x_n+x_{n-1}+...+x_1+x_0)\%(k-1) m%(k1)=(xn+xn1+...+x1+x0)%(k1)
证明很简单,用二项式展开看一下就明白了
根据该性质,可以得到HakMem算法的第一个版本

uint32 count_one_num(uint32 val)
{
	uint32 tmp;
	tmp = (val & 010101010101)
		+ ((val >> 1) & 010101010101)
		+ ((val >> 2) & 010101010101)
		+ ((val >> 3) & 010101010101)
		+ ((val >> 4) & 010101010101)
		+ ((val >> 5) & 010101010101);
	return tmp % 63;
}

int main()
{
	uint32 input = 0x2345;
	printf("count result %d in 0x%x", count_one_num(input), input);
	getchar();
}

代码的思路是将uint32分段,6bit一段,每一段计算其中1的个数,由于每一段有6bit,最后的tmp就相当于k=64是的m,所以对63取余就得到每段1的个数的和。

上面的代码计算每6bit的1的个数需要移位6次,这个可以通过下面代码进行优化

uint32 count_one_num(uint32 val)
{
	uint32 tmp;
	tmp = val - ((val >> 1) & 033333333333) - ((val >> 2) & 011111111111);
	tmp = (tmp + (tmp >> 3)) & 030707070707;
	return tmp % 63;
}

int main()
{
	uint32 input = 0x2345;
	printf("count result %d in 0x%x", count_one_num(input), input);
	getchar();
}

先计算3bit中1的个数,然后采用再移位求和,取余。
其中,计算3bit的1的个数,采用了一个小技巧,举个例子来说,假设3bit是abc,(a,b,c是0/1值),计算abc中1的个数采用的是abc-ab-a,展开来看就是(4a+2b+c)-(2a+b)-a,显然其值为a+b+c,即3bit中1的个数

除此之外,还有一个比较巧妙的方法,代码如下

uint32 count_one_num(uint32 val)
{
	uint32 cnt = 0, tmp = val;
	while(tmp) {
        cnt++;
         tmp = tmp & (tmp - 1);
    }
	return cnt;
}

int main()
{
	uint32 input = 0x2345;
	printf("count result %d in 0x%x", count_one_num(input), input);
	getchar();
}

用软件对比三个方案的计算时间,方案三耗时是前两个的两倍左右,第一个略优于第二个。

你可能感兴趣的:(数学)