本文主要收集了一些很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)。