写在前面的话 CSAPP课程的lab1有一道这样的题目,而后老师上课讲过一个,就是转帖的那个完美解法。真心好赞。这几天在复习考试的时候又把这个帖子翻出来看了~好有魔性。
这里是做者原来的博客 算法-求二进制数中1的个数。感谢感谢!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,代码以下code
int BitCount2(unsigned int n) { unsigned int c =0 ; for (c =0; n; ++c) { n &= (n -1) ; // 清除最低位的1 } return c ; }
为何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)。
(这个方法开始看了很久~结果只总结出一个道理~不要只是看~看不懂就动动笔啊。)htm
因为表示在程序运行时动态建立的,因此速度上确定会慢一些,把这个版本放在这里,有两个缘由blog
介绍填表的方法,由于这个方法的确很巧妙。get
类型转换,这里不能使用传统的强制转换,而是先取地址再转换成对应的指针类型。也是经常使用的类型转换方法。博客
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 ; }
先说一下填表的原理,根据奇偶性来分析,对于任意一个正整数n
1.若是它是偶数,那么n的二进制中1的个数与n/2中1的个数是相同的,好比4和2的二进制中都有一个1,6和3的二进制中都有两个1。为啥?由于n是由n/2左移一位而来,而移位并不会增长1的个数。
2.若是n是奇数,那么n的二进制中1的个数是n/2中1的个数+1,好比7的二进制中有三个1,7/2 = 3的二进制中有两个1。为啥?由于当n是奇数时,n至关于n/2左移一位再加1。
再说一下查表的原理
对于任意一个32位无符号整数,将其分割为4部分,每部分8bit,对于这四个部分分别求出1的个数,再累加起来便可。而8bit对应2^8 = 256种01组合方式,这也是为何表的大小为256的缘由。
注意类型转换的时候,先取到n的地址,而后转换为unsigned char*,这样一个unsigned int(4 bytes)对应四个unsigned char(1 bytes),分别取出来计算便可。举个例子吧,以87654321(十六进制)为例,先写成二进制形式-8bit一组,共四组,以不一样颜色区分(这里好像无法显示颜色,不过不影响理解。),这四组中1的个数分别为4,4,3,2,因此一共是13个1,以下面所示。
10000111 01100101 01000011 00100001 = 4 + 4 + 3 + 2 = 13
(看到这个方法的时候,我居然想到了动态规划的建表。应该不是一种意思,不过这个思路好巧的说。怎么仍是以为有点像动态规划。)
今天就看到了这里~明天补上。