c语言:统计整数二进制表示中1的个数(汉明重量)

问题描述:对于一个字节的无符号整型变量,求其二进制表示中1的个数。

第一次见到这个问题应该是icephone第一次例会的时候,问题虽然简单,但也值得深思。

后来查阅资料的时候才知道这个问题有个正式的名字叫Hamming_weight,也被一些公司当做面试题。

下面通过几个不同阶段的算法,谈谈这个问题。

一、逐个数

刚刚接触这个问题的时候是上学期吧,大一,还刚接触软件工程,接触c语言,对一些问题的看法也比较单纯。

那时候,就想着纯粹的一个个数来着,声明一个计数变量,满足条件(尾数是1),就加一,然后 / 2(二进制),直到该数为0为止。

当然,就可行性来说,这样的算法完全没有问题。简单,明了。

下面给出具体代码:

#include<stdio.h>//Hamming_weight算法1—逐个数int Hamming_weight_1( int n ){int count_ = 0; //声明计数变量while ( n != 0 ) //遍历{if( n%2 == 1 ) //满足尾数是1.count_++;n /= 2;//除2,右移一位。(二进制)}return count_;}int main(){int n;while ( scanf("%d", &n) != EOF ) //读入整数和打印1的个数{printf("%d \n", Hamming_weight_1( n ));}return 0;}上面的这种算法很常规,也很简单,就不多做说明。

因为今天的主角是位运算,所以相应的,给出位运算的版本,虽然比较简单,但是还是写出来,便于做比较。

#include<stdio.h>//Hamming_weight算法1—逐个数(位运算)int Hamming_weight_1( int n ){int count_ = 0; //声明计数变量while ( n != 0 ) //遍历{count_ += n & 1; //尾数按位与1n >>= 1; // 右移一位}return count_;}int main(){int n;while ( scanf("%d", &n) != EOF ) //读入整数和打印1的个数{printf("%d \n", Hamming_weight_1( n ));}return 0;}

好了,现在分析下逐个数算法的性能。不难看出,对于任何一个整数n(对应的二进制数有

位),它都要进行

次判断。可以说,算法效率比较低,每一位都进行了判断。

当然,,我们不能排斥效率低的算法,任何算法,没有绝对的优越,都是在比较中体现。

下面谈谈另外一种位运算,也比较易懂。

二、number&= number-1 —–只与二进制中1的位数相关的算法

逐个数的方法效率是比较低下的,因为它把每一位都考虑进去了,没有进行筛选,一个劲的蛮干。

现在,我们可以考虑每次找到从最低位开始遇到的第一个1,计数,再把它清零,清零的位运算操作是与一个零(任何数与零都等于零)。

但是在有1的这一位与零的操作要同时不影响未统计过的位数和已经统计过的位数,于是可以有这样一个操作number&= number-1。

这个操作对比当前操作位高的位没有影响,对低位则完全清零。

拿6(110)来做例子,

第一次 110&101=100,这次操作成功的把从低位起第一个1消掉了,同时计数器加1。

第二次100&011=000,同理又统计了高位的一个1,此时n已变为0,不需要再继续了,于是110中有2个1。

下面先看代码,一会再举例说明。

#include<stdio.h>//Hamming_weight算法二—只考虑1的位数int Hamming_weight_2( int number ){int count_ = 0; //声明计数变量while ( number != 0 ) //遍历{number &= number-1;count_ ++;}return count_;}int main(){int n;while ( scanf("%d", &n) != EOF ) //读入整数和打印1的个数{printf("%d \n", Hamming_weight_2( n ));}return 0;}这里,关键是:number &=(number-1),也是巧妙所在。

精髓就是:这个操作对比当前操作位高的位没有影响,对低位则完全清零。

[ 2. 判断一个数是否是2的方幂n > 0 && ((n & (n – 1)) == 0 ) ]

看完代码,再举一例、

拿7(111)来做例子,

第一次 111&110=110,这次操作成功的把从低位起第一个1消掉了,同时计数器加1。

第二次110&101=100,同理又统计了高位的一个1,同时计数器加1。

第三次100&011=000,同理又统计了高位的一个1,同时计数器加1。

此时n已变为0,不需要再继续了,于是111中有3个1。

相信看完代码和例子不难理解了。

以我目前水平,我觉得这个算法已经很巧妙了。不过,,,看了wikipedia上解的Hamming_weight问题,才知道什么叫大神…

三、wiki上高效解法。

先给代码,,

#include<stdio.h>//Hamming_weightint Hamming_weight_3( int n ){n = (n&0x55555555) + ((n>>1)&0x55555555);n = (n&0x33333333) + ((n>>2)&0x33333333);n = (n&0x0f0f0f0f) + ((n>>4)&0x0f0f0f0f);n = (n&0x00ff00ff) + ((n>>8)&0x00ff00ff);n = (n&0x0000ffff) + ((n>>16)&0x0000ffff);return n;}int main(){int n;while ( scanf("%d", &n) != EOF ) //读入整数和打印1的个数{printf("%d \n", Hamming_weight_3( n ));}return 0;}说实在,以我个人的能力。

我看这个跟看天书一样,完全不懂。

下面通过学习过程,附带一些大牛的讲解,来解释下。

本段讲解来源:?id=11

说简单点,就是一个 错位分段相加,然后递归合并的过程 。下面是细节分析:首先先看看那些诡异的数字都有什么特点:0x5555……这个换成二进制之后就是0101010101010101……0x3333……这个换成二进制之后就是0011001100110011……0x0f0f………这个换成二进制之后就是0000111100001111……看出来点什么了吗?如果把这些二进制序列看作一个循环的周期序列的话,

那么第一个序列的周期是2,每个周期是01,第二个序列的周期是4,每个周期是0011,第三个的周期是8,每个是00001111……这样的话,我们可以看看如果一个数和这些玩意相与之后的结果:

这一次是一个告别,或者一个永远的告别,

c语言:统计整数二进制表示中1的个数(汉明重量)

相关文章:

你感兴趣的文章:

标签云: