http://www.cnblogs.com/graphics/archive/2010/06/21/1752421.html
问题描述
任意给定一个32位无符号整数n,求n的二进制表示中1的个数,比如n = 5(0101)时,返回2,n = 15(1111)时,返回4
这也是一道比较经典的题目了,相信不少人面试的时候可能遇到过这道题吧,下面介绍了几种方法来实现这道题,相信很多人可能见过下面的算法,但我相信很少有人见到本文中所有的算法。如果您上头上有更好的算法,或者本文没有提到的算法,请不要吝惜您的代码,分享的时候,也是学习和交流的时候。
普通法
我总是习惯叫普通法,因为我实在找不到一个合适的名字来描述它,其实就是最简单的方法,有点程序基础的人都能想得到,那就是移位+计数,很简单,不多说了,直接上代码,这种方法的运算次数与输入n最高位1的位置有关,最多循环32次。
int BitCount(unsigned int n) { unsigned int c = 0 ; // 计数器 while (n > 0 ) { if ((n & 1 ) == 1 ) // 当前位是1 ++ c ; // 计数器加1 n >>= 1 ; // 移位 } return c ; }
一个更精简的版本如下
int BitCount1(unsigned int n) { unsigned int c = 0 ; // 计数器 for (c = 0 ; n; n >>= 1 ) // 循环移位 c += n & 1 ; // 如果当前位是1,则计数器加1 return c ; }
快速法
这种方法速度比较快,其运算次数与输入n的大小无关,只与n中1的个数有关。如果n的二进制表示中有k个1,那么这个方法只需要循环k次即可。其原理是不断清除n的二进制表示中最右边的1,同时累加计数器,直至n为0,代码如下
int BitCount2(unsigned int n) { unsigned int c = 0 ; for (c = 0 ; n; ++ c) { n &= (n - 1 ) ; // 清除最低位的1 } return c ; }
查表法
动态建表
由于表示在程序运行时动态创建的,所以速度上肯定会慢一些,把这个版本放在这里,有两个原因
1. 介绍填表的方法,因为这个方法的确很巧妙。
2. 类型转换,这里不能使用传统的强制转换,而是先取地址再转换成对应的指针类型。也是常用的类型转换方法。
int BitCount3(unsigned int n) { // 建表 unsigned char BitsSetTable256[ 256 ] = { 0 } ; // 初始化表 for ( int i = 0 ; i < 256 ; i ++ ) { BitsSetTable256[i] = (i & 1 ) + BitsSetTable256[i / 2 ]; } unsigned int c = 0 ; // 查表 unsigned char * p = (unsigned char * ) & n ; c = BitsSetTable256[p[ 0 ]] + BitsSetTable256[p[ 1 ]] + BitsSetTable256[p[ 2 ]] + BitsSetTable256[p[ 3 ]]; return c ; }
静态表-4bit
原理和8-bit表相同,详见8-bit表的解释
int BitCount4(unsigned int n) { unsigned int table[ 16 ] = { 0 , 1 , 1 , 2 , 1 , 2 , 2 , 3 , 1 , 2 , 2 , 3 , 2 , 3 , 3 , 4 } ; unsigned int count = 0 ; while (n) { count += table[n & 0xf ] ; n >>= 4 ; } return count ; }
静态表-8bit
首先构造一个包含256个元素的表table,table[i]即i中1的个数,这里的i是[0-255]之间任意一个值。然后对于任意一个32bit无符号整数n,我们将其拆分成四个8bit,然后分别求出每个8bit中1的个数,再累加求和即可,这里用移位的方法,每次右移8位,并与0xff相与,取得最低位的8bit,累加后继续移位,如此往复,直到n为0。所以对于任意一个32位整数,需要查表4次。以十进制数2882400018为例,其对应的二进制数为10101011110011011110111100010010,对应的四次查表过程如下:红色表示当前8bit,绿色表示右移后高位补零。
第一次(n & 0xff) 10101011110011011110111100010010
第二次((n >> 8) & 0xff) 00000000101010111100110111101111
第三次((n >> 16) & 0xff)00000000000000001010101111001101
第四次((n >> 24) & 0xff)00000000000000000000000010101011
int BitCount7(unsigned int n) { unsigned int table[ 256 ] = { 0 , 1 , 1 , 2 , 1 , 2 , 2 , 3 , 1 , 2 , 2 , 3 , 2 , 3 , 3 , 4 , 1 , 2 , 2 , 3 , 2 , 3 , 3 , 4 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 1 , 2 , 2 , 3 , 2 , 3 , 3 , 4 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 1 , 2 , 2 , 3 , 2 , 3 , 3 , 4 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 4 , 5 , 5 , 6 , 5 , 6 , 6 , 7 , 1 , 2 , 2 , 3 , 2 , 3 , 3 , 4 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 4 , 5 , 5 , 6 , 5 , 6 , 6 , 7 , 2 , 3 , 3 , 4 , 3 , 4 , 4 , 5 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 4 , 5 , 5 , 6 , 5 , 6 , 6 , 7 , 3 , 4 , 4 , 5 , 4 , 5 , 5 , 6 , 4 , 5 , 5 , 6 , 5 , 6 , 6 , 7 , 4 , 5 , 5 , 6 , 5 , 6 , 6 , 7 , 5 , 6 , 6 , 7 , 6 , 7 , 7 , 8 , }; return table[n & 0xff ] + table[(n >> 8 ) & 0xff ] + table[(n >> 16 ) & 0xff ] + table[(n >> 24 ) & 0xff ] ; }
平行算法
网上都这么叫,我也这么叫吧,不过话说回来,的确有平行的意味在里面,先看代码,稍后解释
int BitCount4(unsigned int n) { n = (n & 0x55555555 ) + ((n >> 1 ) & 0x55555555 ) ; n = (n & 0x33333333 ) + ((n >> 2 ) & 0x33333333 ) ; n = (n & 0x0f0f0f0f ) + ((n >> 4 ) & 0x0f0f0f0f ) ; n = (n & 0x00ff00ff ) + ((n >> 8 ) & 0x00ff00ff ) ; n = (n & 0x0000ffff ) + ((n >> 16 ) & 0x0000ffff ) ; return n ; }
完美法
int BitCount5(unsigned int n) { unsigned int tmp = n - ((n >> 1 ) & 033333333333 ) - ((n >> 2 ) & 011111111111 ); return ((tmp + (tmp >> 3 )) & 030707070707 ) % 63 ; }
位标志法
感谢网友 gussing提供
struct _byte { unsigned a: 1 ; unsigned b: 1 ; unsigned c: 1 ; unsigned d: 1 ; unsigned e: 1 ; unsigned f: 1 ; unsigned g: 1 ; unsigned h: 1 ; }; long get_bit_count( unsigned char b ) { struct _byte * by = ( struct _byte * ) & b; return (by -> a + by -> b + by -> c + by -> d + by -> e + by -> f + by -> g + by -> h); }
指令法
感谢网友 Milo Yip提供
使用微软提供的指令,首先要确保你的CPU支持SSE4指令,用Everest和CPU-Z可以查看是否支持。
unsigned int n = 127 ; unsigned int bitCount = _mm_popcnt_u32(n) ;