1. 利用与运算或者非运算
(1)把一个整数减去1之后再和原来的整数做与运算,得到的结果相当于是把整数的二进制表示中的最右边一个1变成0 。
int odd_ones(int x)
{
int count = 0;
while(x){
x &= (x-1);
count++;
}
return count;
}
(2)把一个整数加上1之后再和原来的整数做或运算,得到的结果相当于是把整数的二进制表示中的最右边一个0变成1 。
int odd_ones(int x)
{
int n = 0;
while((x+1)){ //将0全部替换为1时溢出
n++;
x|=(x+1);
}
return 32-n;//长度-0的个数
}
2. 移位法
int count(unsigned x){
int count=0;
while(x){
if(x & 0x1)
++count;
x=(x>>1);
}
return count;
}
3. 分冶法
这个方法是Hamming weight Wikipedia上面提出来的,很高效,比上面的两种方法都要高效。采用了分治法的思想,具体实现如下:
int Hamming_weight(uint32_t 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;
}
代码解析: 乍一看,立马懵逼,很难看懂为何那么写。先将代码中用到的几个常数对比一下其特点,再联想到分治的思想,你可能就懂了。
0x5555……这个换成二进制之后就是01 01 01 01 01 01 01 01……
0x3333……这个换成二进制之后就是0011 0011 0011 0011……
0x0f0f………这个换成二进制之后就是00001111 00001111……
看出来点什么了吗? 如果把这些二进制序列看作一个循环的周期序列的话,那么第一个序列的周期是2,每个周期是01,第二个序列的周期是4,每个周期是0011,第三个的周期是8,每个是00001111,第四个和第五个以此类推。看出了这些数的特点,再回头看代码你会轻松的发现代码的意义。算法的实现原理是将32位无符号整数分成32个段,每个段即1bit,段的取值可表示当前段中1的个数,所以将32个段的数值累加在一起就是二进制中1的个数,如何累加呢?这就是代码做的事情。 (n&0x55555555)+((n>>1)&0x55555555) 将32位数中的32个段从左往右把相邻的两个段的值相加后放在2bits中,就变成了16个段,每段2位。同理(n&0x33333333)+((n>>2)&0x33333333)将16个段中相邻的两个段两两相加,存放在4bits中,就变成了8个段,每段4位。以此类推,最终求得数中1的个数就存放在一个段中,这个段32bits,就是最后的n。
4. 个人见到的一种方法,跟分冶法相似,但只能计算出1的个数为奇数个还是偶数个,因为每次计算后有效的只有最后一位
bool odd_ones(unsigned x)
{
x = x ^ (x >> 1);
x = x ^ (x >> 2);
x = x ^ (x >> 4);
x = x ^ (x >> 8);
x = x ^ (x >> 16);
return x & 1;
}
………………………………
本文只是在博主Dablelv的文章做了一些修改,仅为个人学习使用。原文内容更加全面翔实,特附上链接
原文: https://blog.csdn.net/k346k346/article/details/53316953