代码改变世界

求二进制数中1的个数

2013-08-08 17:05  夜与周公  阅读(494)  评论(0编辑  收藏  举报

  <<编程之美>>中有这么个题目:对于一个字节的无符号整形变量,求其二进制表达形式中“1”的个数。

  基础算法:辗转相除法

  辗转相除法是十进制采用的算法,该算法如下:

int count_one_cnt(int value)
{
    int cnt = 0;
    while (value)
    {
        if (value % 2 == 1)
        {
            cnt += 1;
        }
        value /= 2;
    }
    return cnt;
}

  上述算法的时间复杂度O(logV)(V是value比特数),上述算法的操作对象的级别是“数”级别的,题目的要求是“位”级别的,因此如果能够通过位运行完成题目要求功能,将会更加高效:1)除法采用右移实现;2)通过与1位与运算判断是否有1的存在。

int count_one_cnt(int value)
{
    int cnt = 0;
    while (value)
    {
        cnt += value & 1;
        value >>= 1;
    }
    return cnt;
}

  位与法:

  上面的两个算法时间复杂度均依赖于欲求的数据类型在存储空间中占有的比特数,位与法算法复杂度则仅与待求数据中1的个数相关。首先,观察某个数a与a-1:在二进制下,a-1在最右端加上1就等于a,a与a-1进行位与运算则会消除最右边的1,可以从以下角度理解;

  1)a与a-1仅差一个1,而这个1是从a-1最低端加上便等于a

  2)假设a最低端出现的位置pos,pos后面均是0,因此与b位与运算,pos位置后面均是0

  3)a-1的对应pos位置,一定是0。假设a-1在pos位置等于1,a-1后面必然均是0,否则a-1将会大于a,但根据1)a等于a-1处最低端位加1,而a-1从pos位置开始均是0,在a-1最低端位加1得到的a,在pos位置到最低端一定有一个1,与2)矛盾。

  虽然理解起来有些绕弯,但是代码却是非常简洁的:

int count_one_cnt(int value)
{
    int cnt = 0;
    while (value)
    {
        value &= value-1;
        cnt += 1;
    }
    return cnt;
}

  位图法:

  <<编程之美>>提到了这种方法:8bit的无符号整数的范围是[0, 255),因此直接将256个数还有的1位数做出表的形式,以时间换空间~实现方法在此省略。

  Hamming weight方法:

  Hamming weight:是指在给定的字符串中,所有不等于0的字符的个数,在一串二进制的字符串中,等于该二进制中1的个数。Hamming weight方法的一种快速实现,采用了“隔位相加”的方法:相邻位相加,并存在与当前位置中。算法过程如下(来源与维基百科):

可以从下图快速理解算法的执行过程:

  

实现方法在此不一一列出,有兴趣可以参考维基百科中给出了三种实现方法~