本文主要收集了一些很nice的方法,用来计算一个数,其二进制形式中1的位数。
简介
李明老师的C语言中级课程中,讲解了这一算法,无奈记性不好。。为了以后能时常看到,现记录下来。
简单相与法
比如一个二进制数 0b 1111 1111 = 0xFF,只需要:
- 1、与 0b 0000 0001 = 0x01 相与,如果为真,则个数加1;
 - 2、与 0b 0000 0010 = 0x02 相与,如果为真,则个数加1;
 - …
 - n、如果这个数是16位的,必须相与到 0xFFFF 才能结束
 
核心代码如下:
1  | for(i = 0; i < 16; i++) {  | 
与相邻数相与法
注意这种情况:一个数与这个数减1相与,会把这个数的最后面的一位1变为0.如下情况:
1  | 0b 1000 & 0b 0111 = 0b 0000  | 
这就引出下面的一种算法,其核心代码如下:
1  | while(num != 0) {  | 
每三位计算一次
这一个算法实在CSAPP课上老师讲解的一个算法,现在给它添加上去。原理就是对每3个比特计算一下1的个数,可以同步进行,计算一个32位的数字,最多执行11次。具体做法:
假设这3为比特位为 abc,那么这3位比特位含1的个数为 a + b + c。而 abc 换成十进制为 4a + 2b + c,注意怎么从 4a + 2b + c 变到 a + b + c,因为后者才是这3位数含有的1的个数。这个是通过下面一个式子实现的。假设一个3比特位的数 abc.
1  | (abc >> 1) & 011 = 0ab = 2a + b (十进制)  | 
那么就有如下的代码,可以计算一个32位数中1的个数,如下所示:
1  | int bitcount(unsigned int n) {  | 
最后那个 return 语句是为了把这个数字每3位中的1的个数加起来,
与特定值相与法
上一个算法看似很好,但如果遇到 0xFF 和 0xFFFF 之类全1的数,性能就下降了。对此想到一种新的算法.
1  | 0b 1111 1111  | 
把上述俩个结果相加得:
1  | 0b 0101 0101 + 0b 0101 0101 = 0b 1010 1010  | 
这只是计算出每两位所含的1,即10(为2)个。同理计算每4位的,再计算每8位的等等…
1  | 
上述三个宏用来测试八位数中的二进制中1的个数,其测试代码如下:
1  | num = num & M1 + ( ( num >> 1 ) & M1 );  | 
最后的num地值,即为1的个数。假设为32位的数,那么需要5个宏定义,分别如下:
1  | #define M1 0x55555555  | 
最后的num地值,即为1的个数。这种算法时间复杂度固定,为 O(1)。