写一个函数返回参数二进制中 1 的个数。
比如: 15 0000 1111 4 个 1
方法一:遍历二进制位
暴力求解,拿到这个数的二进制中的每一位,通过移位运算,让最低位按位与1。
运算规则:只有两个数的二进制同时为1,结果才为1,否则为0。(负数按补码形式参加按位与运算)
int number_of_1(int m)
{
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
if (((m >> i) & 1) == 1)
count++;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);//15
int ret = number_of_1(n);
printf("%d\n", ret);//4
return 0;
}
运行结果:若n为15,结果为:4;
若n为-1,结果为:32
方法二:取模运算
从十进制中,我们可以知道一个数对10取模就可以得到最低位的数字,例如123%10=3,想要得到2,只需要让123/10=12,12%10=2,让12/10=1,1%10=1,这样123的每一位都被获得了。
对付二进制的数,想要拿到每一位,依然可以同理操作,例如15%2=1,这一步拿到第一个1,15/2=7,7%2=1,这次拿到第二个1,7/2=3,3%2=1,这里拿到第三个1,3/2=1,1%2=1,这是最后一个1,下一步是1/2=0,可知这个数的二进制位已经被拿完了,不需要再往下计算。(每次得到一个余数,就往下除进制数,二进制除2,十进制除10)
需要注意的是,传参过来的是int类型,如果用int接收有符号整数,比如说,-1在while循环里,进行if语句判断一定不等于1,对负数是没办法统计1的个数的。形参接收的部分需要改为无符号整型unsigned int才可以,他会把负数中最高位的符号位当作一个大的正数处理。
int number_of_1(unsigned int m)
{
int count = 0;
while (m)
{
if (m % 2 == 1)
count++;
m /= 2;
}
return count;
}
int main()
{
//-1
//10000000000000000000000000000001 -- 原码
//11111111111111111111111111111110 -- 反码
//11111111111111111111111111111111 -- 补码 32个1
int n = 0;
scanf("%d", &n);
int ret = number_of_1(n);
printf("%d\n", ret);//输入-1,所得到的结果为32
return 0;
}
方法三:巧妙按位与
这里介绍一个神奇的公式, n = n&(n-1); 这个表达式会让n的二进制中最右边的1就消失了。(不妨大家在纸上举例几个数据进行验证一下,这里不过多解释)
这种方法大大提高了代码效率,有多少个1,循环就进行多少次。不需要像方法一那样,不管数字有几个1都要循环32次,也不需要像方法二考虑传参类型。
int number_of_1(int m)
{
int count = 0;
while (m)
{
m = m & (m - 1);
count++;
}
return count;
}