计算一个整数的二进制表示有多少个1(别人的最快算法)

最近看了这篇文章 的第一道题

题目本意是怎么判断这段代码的输出

int func(x)
{
    int countx =0;
    while(x)
    {
          countx ++;
          x = x&(x-1);
     }
    return countx;
}

但深入一想,这不就是求任意整数的二进制表示里有多少个1吗?

要想解决上述问题,我能想到的最快方法是按位右移,累计每个移出的数,直到原数为0

int func2(int x)
{
    int countx =0;
    while (x)
    {
        if (x & 1)
            countx ++;
        x = x >> 1;
    }
    return countx;
}

但是很明显,我的算法在二进制表示含有较多中间0时,效率很低,而别人的算法就不存在该问题,而且没有if分支判断,避免CPU指令Cache miss,方便并行化

总之,该算法求解1个数,类似于辗转相除法求最大公约数,都是基于某种数学原理,性能自然比愚蠢的枚举不知道高到哪里去了


该算法的原理,我晚上思考了下,觉得是对【判断一个整数是否为2的幂】算法的扩展应用

【判断一个整数是否为2的幂】算法

bool power_of_2(int x)
{
    if (0 == (x & x-1))
        return true;
    else
        return false;
}

该算法的思路是,如果整数i为2的幂,则其二进制表示必然只有1位为1,假设是第N位为1,则0到N-1位必然为0,且N+1到31位(假设是32位CPU)全零

令j = i-1,则j的二进制表示就是第N位为00到N-1位全1,N+1到31位还是全零,

因为减法需要从高位借位,对于2的幂整数,能借位的只有唯一的那个1,所以N位必然变为0

又因为减数为1,所以0到N-1位必然全为1

这样i和j的【按位与】运算,必然等于0


将上述思路拓展

对于任意一个整数,都可以看成一系列幂的和,比如

520,就是512+8

1111,就是1024+64+16+4+2+1

当对该整数进行【减一】和【按位与】操作时,最低等级的幂先变为零

然后再次执行【减一】和【按位与】操作,次低等级的幂也变为零

以此类推,每次循环都将一个非零幂项清除(为零的幂项不发生借位,所以自动忽略)

最后变为全零,循环退出

发布了159 篇原创文章 · 获赞 27 · 访问量 46万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章