【转】算法-求二进制数中1的个数

写在前面的话 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

  1. 介绍填表的方法,由于这个方法的确很巧妙。get

  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 ; 
}

先说一下填表的原理,根据奇偶性来分析,对于任意一个正整数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

(看到这个方法的时候,我居然想到了动态规划的建表。应该不是一种意思,不过这个思路好巧的说。怎么仍是以为有点像动态规划。)


今天就看到了这里~明天补上。

相关文章
相关标签/搜索