九度1513求二进制数中1的个数

  1. 算法-求二进制数中1的个数  
  2. 问题描述  
  3. 任意给定一个32位无符号整数n,求n的二进制表示中1的个数,比如n = 5(0101)时,返回2,n = 15(1111)时,返回4  
  4.   
  5. 这也是一道比较经典的题目了,相信不少人面试的时候可能遇到过这道题吧,下面介绍了几种方法来实现这道题,相信很多人可能见过下面的算法,但我相信很少有人见到本文中所有的算法。如果您上头上有更好的算法,或者本文没有提到的算法,请不要吝惜您的代码,分享的时候,也是学习和交流的时候。  
  6.   
  7. 普通法  
  8. 我总是习惯叫普通法,因为我实在找不到一个合适的名字来描述它,其实就是最简单的方法,有点程序基础的人都能想得到,那就是移位+计数,很简单,不多说了,直接上代码,这种方法的运算次数与输入n最高位1的位置有关,最多循环32次。  
  9.   
  10. <!--  
  11.   
  12. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  13. http://www.CodeHighlighter.com/  
  14.   
  15. -->int BitCount(unsigned int n)  
  16. {  
  17.     unsigned int c = 0 ; // 计数器  
  18.     while (n > 0)  
  19.     {  
  20.         if((n & 1) == 1) // 当前位是1  
  21.             ++c ; // 计数器加1  
  22.         n >>= 1 ; // 移位  
  23.     }  
  24.     return c ;  
  25. }  
  26. 一个更精简的版本如下  
  27.   
  28. <!--  
  29.   
  30. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  31. http://www.CodeHighlighter.com/  
  32.   
  33. -->int BitCount1(unsigned int n)  
  34. {  
  35.     unsigned int c = 0 ; // 计数器  
  36.     for (c = 0; n; n >>= 1) // 循环移位  
  37.         c += n & 1 ; // 如果当前位是1,则计数器加1  
  38.     return c ;  
  39. }  
  40.  快速法  
  41. 这种方法速度比较快,其运算次数与输入n的大小无关,只与n中1的个数有关。如果n的二进制表示中有k个1,那么这个方法只需要循环k次即可。其原理是不断清除n的二进制表示中最右边的1,同时累加计数器,直至n为0,代码如下  
  42.   
  43. <!--  
  44.   
  45. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  46. http://www.CodeHighlighter.com/  
  47.   
  48. -->int BitCount2(unsigned int n)  
  49. {  
  50.     unsigned int c = 0 ;  
  51.     for (c = 0; n; ++c)  
  52.     {  
  53.         n &= (n - 1) ; // 清除最低位的1  
  54.     }  
  55.     return c ;  
  56. }  
  57. 为什么n &= (n – 1)能清除最右边的1呢?因为从二进制的角度讲,n相当于在n - 1的最低位加上1。举个例子,8(1000)= 7(0111)+ 1(0001),所以8 & 7 = (1000)&(0111)= 0(0000),清除了8最右边的1(其实就是最高位的1,因为8的二进制中只有一个1)。再比如7(0111)= 6(0110)+ 1(0001),所以7 & 6 = (0111)&(0110)= 6(0110),清除了7的二进制表示中最右边的1(也就是最低位的1)。  
  58.   
  59. 查表法  
  60. 动态建表  
  61. 由于表示在程序运行时动态创建的,所以速度上肯定会慢一些,把这个版本放在这里,有两个原因  
  62.   
  63. 1. 介绍填表的方法,因为这个方法的确很巧妙。  
  64.   
  65. 2. 类型转换,这里不能使用传统的强制转换,而是先取地址再转换成对应的指针类型。也是常用的类型转换方法。  
  66.   
  67. <!--  
  68.   
  69. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  70. http://www.CodeHighlighter.com/  
  71.   
  72. -->int BitCount3(unsigned int n)   
  73. {   
  74.     // 建表  
  75.     unsigned char BitsSetTable256[256] = {0} ;   
  76.   
  77.     // 初始化表   
  78.     for (int i = 0; i < 256; i++)   
  79.     {   
  80.         BitsSetTable256[i] = (i & 1) + BitsSetTable256[i / 2];   
  81.     }   
  82.   
  83.     unsigned int c = 0 ;   
  84.   
  85.     // 查表  
  86.     unsigned char * p = (unsigned char *) &n ;   
  87.   
  88.     c = BitsSetTable256[p[0]] +    
  89.         BitsSetTable256[p[1]] +    
  90.         BitsSetTable256[p[2]] +    
  91.         BitsSetTable256[p[3]];   
  92.   
  93.     return c ;   
  94. }   
  95. 先说一下填表的原理,根据奇偶性来分析,对于任意一个正整数n  
  96.   
  97. 1.如果它是偶数,那么n的二进制中1的个数与n/2中1的个数是相同的,比如4和2的二进制中都有一个1,6和3的二进制中都有两个1。为啥?因为n是由n/2左移一位而来,而移位并不会增加1的个数。  
  98.   
  99. 2.如果n是奇数,那么n的二进制中1的个数是n/2中1的个数+1,比如7的二进制中有三个1,7/2 = 3的二进制中有两个1。为啥?因为当n是奇数时,n相当于n/2左移一位再加1。  
  100.   
  101. 再说一下查表的原理  
  102.   
  103. 对于任意一个32位无符号整数,将其分割为4部分,每部分8bit,对于这四个部分分别求出1的个数,再累加起来即可。而8bit对应2^8 = 256种01组合方式,这也是为什么表的大小为256的原因。  
  104.   
  105. 注意类型转换的时候,先取到n的地址,然后转换为unsigned char*,这样一个unsigned int(4 bytes)对应四个unsigned char(1 bytes),分别取出来计算即可。举个例子吧,以87654321(十六进制)为例,先写成二进制形式-8bit一组,共四组,以不同颜色区分,这四组中1的个数分别为4,4,3,2,所以一共是13个1,如下面所示。  
  106.   
  107. 10000111 0110010101000011 00100001 = 4 + 4 + 3 + 2 = 13  
  108.   
  109. 静态表-4bit  
  110. 原理和8-bit表相同,详见8-bit表的解释  
  111.   
  112. <!--  
  113.   
  114. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  115. http://www.CodeHighlighter.com/  
  116.   
  117. -->int BitCount4(unsigned int n)  
  118. {  
  119.     unsigned int table[16] =   
  120.     {  
  121.         0, 1, 1, 2,   
  122.         1, 2, 2, 3,   
  123.         1, 2, 2, 3,   
  124.         2, 3, 3, 4  
  125.     } ;  
  126.   
  127.     unsigned int count = 0 ;  
  128.     while (n)  
  129.     {  
  130.         count += table[n & 0xf] ;  
  131.         n >>= 4 ;  
  132.     }  
  133.     return count ;  
  134. }  
  135. 静态表-8bit  
  136. 首先构造一个包含256个元素的表table,table[i]即i中1的个数,这里的i是[0-255]之间任意一个值。然后对于任意一个32bit无符号整数n,我们将其拆分成四个8bit,然后分别求出每个8bit中1的个数,再累加求和即可,这里用移位的方法,每次右移8位,并与0xff相与,取得最低位的8bit,累加后继续移位,如此往复,直到n为0。所以对于任意一个32位整数,需要查表4次。以十进制数2882400018为例,其对应的二进制数为10101011110011011110111100010010,对应的四次查表过程如下:红色表示当前8bit,绿色表示右移后高位补零。  
  137.   
  138. 第一次(n & 0xff)             10101011110011011110111100010010  
  139.   
  140. 第二次((n >> 8) & 0xff) 00000000101010111100110111101111  
  141.   
  142. 第三次((n >> 16) & 0xff)00000000000000001010101111001101  
  143.   
  144. 第四次((n >> 24) & 0xff)00000000000000000000000010101011  
  145.   
  146. <!--  
  147.   
  148. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  149. http://www.CodeHighlighter.com/  
  150.   
  151. -->int BitCount7(unsigned int n)  
  152. {      
  153.     unsigned int table[256] =       
  154.     {          
  155.         0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,          
  156.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,          
  157.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,          
  158.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,          
  159.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,          
  160.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,          
  161.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,          
  162.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,         
  163.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,          
  164.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,          
  165.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,          
  166.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,          
  167.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,          
  168.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,          
  169.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,          
  170.         4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,      
  171.     };      
  172.       
  173.     return table[n & 0xff] +  
  174.         table[(n >> 8) & 0xff] +  
  175.         table[(n >> 16) & 0xff] +  
  176.         table[(n >> 24) & 0xff] ;  
  177. }  
  178. 当然也可以搞一个16bit的表,或者更极端一点32bit的表,速度将会更快。  
  179.   
  180. 平行算法  
  181. 网上都这么叫,我也这么叫吧,不过话说回来,的确有平行的意味在里面,先看代码,稍后解释  
  182.   
  183. <!--  
  184.   
  185. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  186. http://www.CodeHighlighter.com/  
  187.   
  188. -->int BitCount4(unsigned int n)   
  189. {   
  190.     n = (n & 0x55555555) + ((n >> 1) & 0x55555555) ;   
  191.     n = (n & 0x33333333) + ((n >> 2) & 0x33333333) ;   
  192.     n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f) ;   
  193.     n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff) ;   
  194.     n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff) ;   
  195.   
  196.     return n ;   
  197. }   
  198. 速度不一定最快,但是想法绝对巧妙。 说一下其中奥妙,其实很简单,先将n写成二进制形式,然后相邻位相加,重复这个过程,直到只剩下一位。  
  199.   
  200. 以217(11011001)为例,有图有真相,下面的图足以说明一切了。217的二进制表示中有5个1  
  201.   
  202.   
  203.   
  204. 完美法  
  205. <!--  
  206.   
  207. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  208. http://www.CodeHighlighter.com/  
  209.   
  210. -->int BitCount5(unsigned int n)   
  211. {  
  212.   
  213.   
  214.     unsigned int tmp = n - ((n >> 1) & 033333333333) - ((n >> 2) & 011111111111);  
  215.     return ((tmp + (tmp >> 3)) & 030707070707) % 63;  
  216. }  
  217. 最喜欢这个,代码太简洁啦,只是有个取模运算,可能速度上慢一些。区区两行代码,就能计算出1的个数,到底有何奥妙呢?为了解释的清楚一点,我尽量多说几句。  
  218.   
  219. 第一行代码的作用  
  220.   
  221. 先说明一点,以0开头的是8进制数,以0x开头的是十六进制数,上面代码中使用了三个8进制数。  
  222.   
  223. 将n的二进制表示写出来,然后每3bit分成一组,求出每一组中1的个数,再表示成二进制的形式。比如n = 50,其二进制表示为110010,分组后是110和010,这两组中1的个数本别是2和3。2对应010,3对应011,所以第一行代码结束后,tmp = 010011,具体是怎么实现的呢?由于每组3bit,所以这3bit对应的十进制数都能表示为2^2 * a + 2^1 * b + c的形式,也就是4a + 2b + c的形式,这里a,b,c的值为0或1,如果为0表示对应的二进制位上是0,如果为1表示对应的二进制位上是1,所以a + b + c的值也就是4a + 2b + c的二进制数中1的个数了。举个例子,十进制数6(0110)= 4 * 1 + 2 * 1 + 0,这里a = 1, b = 1, c = 0, a + b + c = 2,所以6的二进制表示中有两个1。现在的问题是,如何得到a + b + c呢?注意位运算中,右移一位相当于除2,就利用这个性质!  
  224.   
  225. 4a + 2b + c 右移一位等于2a + b   
  226.   
  227. 4a + 2b + c 右移量位等于a  
  228.   
  229. 然后做减法  
  230.   
  231. 4a + 2b + c –(2a + b) – a = a + b + c,这就是第一行代码所作的事,明白了吧。  
  232.   
  233. 第二行代码的作用  
  234.   
  235. 在第一行的基础上,将tmp中相邻的两组中1的个数累加,由于累加到过程中有些组被重复加了一次,所以要舍弃这些多加的部分,这就是&030707070707的作用,又由于最终结果可能大于63,所以要取模。  
  236.   
  237. 需要注意的是,经过第一行代码后,从右侧起,每相邻的3bit只有四种可能,即000, 001, 010, 011,为啥呢?因为每3bit中1的个数最多为3。所以下面的加法中不存在进位的问题,因为3 + 3 = 6,不足8,不会产生进位。  
  238.   
  239. tmp + (tmp >> 3)-这句就是是相邻组相加,注意会产生重复相加的部分,比如tmp = 659 = 001 010 010 011时,tmp >> 3 = 000 001 010 010,相加得  
  240.   
  241. 001 010010 011  
  242.   
  243. 000 001010 010  
  244.   
  245. ---------------------  
  246.   
  247. 001 011 100101  
  248.   
  249. 001 + 101 = 1 + 5 = 6,所以659的二进制表示中有6个1  
  250.   
  251. 注意我们想要的只是第二组和最后一组(绿色部分),而第一组和第三组(红色部分)属于重复相加的部分,要消除掉,这就是&030707070707所完成的任务(每隔三位删除三位),最后为什么还要%63呢?因为上面相当于每次计算相连的6bit中1的个数,最多是111111 = 77(八进制)= 63(十进制),所以最后要对63取模。  
  252.   
  253. 位标志法  
  254. 感谢网友 gussing提供  
  255.   
  256. <!--  
  257.   
  258. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  259. http://www.CodeHighlighter.com/  
  260.   
  261. -->struct _byte    
  262. {    
  263.     unsigned a:1;    
  264.     unsigned b:1;    
  265.     unsigned c:1;    
  266.     unsigned d:1;    
  267.     unsigned e:1;    
  268.     unsigned f:1;    
  269.     unsigned g:1;    
  270.     unsigned h:1;    
  271. };    
  272.   
  273. long get_bit_count( unsigned char b )    
  274. {  
  275.     struct _byte *by = (struct _byte*)&b;    
  276.     return (by->a+by->b+by->c+by->d+by->e+by->f+by->g+by->h);    
  277. }  
  278. 指令法  
  279. 感谢网友 Milo Yip提供  
  280.   
  281. 使用微软提供的指令,首先要确保你的CPU支持SSE4指令,用Everest和CPU-Z可以查看是否支持。  
  282.   
  283. <!--  
  284.   
  285. Code highlighting produced by Actipro CodeHighlighter (freeware)  
  286. http://www.CodeHighlighter.com/  
  287.   
  288. -->unsigned int n = 127 ;  
  289. unsigned int bitCount = _mm_popcnt_u32(n) ;  
  290.   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值