种群计数就是统计一个位串中1的个数。Java的Short,Integer和Long都有bitCount方法,就是种群计数的实现。这类问题不仅很基础,也很重要。如果将其作为面试题,很容易考察算法能力的高低。
-
最简单的实现
最容易想到的方法就是遍历每一位,测试该位是否是1。代码如下:
intcount = 0; for(inti = 0; i < Integer.SIZE; i++) { if((a & (1 << i)) != 0) { count++; } } returncount; |
假设位串的长度为n,则算法的时间复杂度为O(n)。对于统计整形这样的,因为其位数最多为64位,所以基本上可以算是常数。但如果要统计很多个整形,那么其实现将具有一个很大的常数。
-
一种改进
这种改进是基于这样一个事实:操作x^(x-1)能把最低1位置0。这样通过不断将最低1为置0,直到x等于0为止。具体实现如下:
intcount = 0; inta = x; while(a != 0) { a^= a – 1; count++; } |
改进之后,性能有很大提升。理论上,所有位都为1的情况很少,而位串中1的个数期望位位串长度的一半。所以其平均时间复杂度为第一种方法的一般。
-
查表法
考虑到给定位数(比如4位)中1的个数是可以预先给定了。一个整型有32位,只要查8次表就好了。代码如下:
Int[]table = new int[]{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; intcount = table[a&0x0f] + table[a&0xf0] + table[a&0xf00]+ table[a&0xf000] + table[a&0xf0000] + table[a&0xf00000]+ table[a&0xf000000] + + table[a&0xf0000000]; |
上面编译后得到的汇编指令很短,性能非常高,是常数级别的。
-
分而治之
统计32位的1个数,可以分解成两个16位的统计。这个方法在串行的机子上并没有实现性能上的提升。但如果并行计算,性能则能提升一倍。特别适合现在主流的多核计算机。具体的实现如下:
x= x & 0x55555555 + (x >> 1) & 0x55555555; //计算每2位1的个数 x= x & 0x33333333 + (x >> 2) & 0x33333333; //计算每4位 x= x & 0x0F0F0F0F + (x >> 4) & 0x0F0F0F0F; // 计算每8位 x= x & 0x00FF00FF + (x >> 8) & 0x00FF00FF; // 计算每16位 x= x & 0x0000FFFF + (x >> 16) & 0x0000FFFF;// 计算每32位 |
上面的实现,没有用到分支指令和内存引用,单纯的位操作,加法和赋值操作,因此性能应该是最高效的了。这也是Java中Integer类型的bitCount函数的实现。