题目:请实现一个函数,输入一个整数,输出该二进制表示中1的个数,例如把9表示成二进制是1001,有2位1,因此如果输入9,该函数输出2.
要求:
1、整数,包括边界值1、0x7FFFFFFF
2、负数,包括边界值0x80000000、0xFFFFFFFF
3、0
显然,本题的难点也就是表示负数的情况,因为计算机中是用二进制来表示的,而又是用二进制的补码来表示的,所以当出现负数的情况下,我们不可以将负数右移来达到我们的目的,但是如果是正数的情况下,我们是可以通过右移来完成的。
那么我们应该采取什么做法呢?
我们可以反过来想,既然我们不能移动负数的位数,我们可以用一个数向左移,这样就可以和原来数做比较了。
现在贴出远吗如下:
#include<stdio.h>
int binary_one(int n)
{
int k=0,tag=1;
while(tag)
{
if(n&tag)k++;
tag<<=1;
}
return k;
}
int main()
{
printf("请输入一个整数:\n");
int n;
scanf("%d",&n);
printf("此整数二进制中有 %d 个 1\n",binary_one(n));
return 0;
}
如图所示,函数binary_one就是通过tag=1作初始数据来左移的。每次左移都要和原始数值做&操作,这样可以得到1的个数。
PS:上面的程序还是优点小瑕疵的,因为当n为整数的时候,是不需要从低位一直比较到最高位的,而是应该从高位到低位,所以改进的程序如下,分为两种情况:
#include <stdio.h>
#include <stdlib.h>
int numberOf1(int n)
{
int count=0;
int tag=1;
if(n==0)return 0;
if(n>0)
{
while(n)
{
if(n&1)count++;
n>>=1;
}
}
else
{
while(tag)
{
if(tag&n)count++;
tag<<=1;
}
}
return count;
}
int main()
{
printf("请输入一个整数:\n");
int n;
scanf("%d",&n);
printf("整数 %d 二进制中有 %d 个1\n",n,numberOf1(n));
return 0;
}
PS:下面介绍一种非常新奇的解法:
对于一个整数的二进制表示,如果这个整数的最右边一位是1,那么减去1时,最后一位变成0而其他所有位都保持不变,也就是相当于对最后一位取反操作。现在我们假设最后一位不是1而是0 的情况,该整数的二进制表示中最右边1位位于第m位,那么减去1时,第m位由1变成0,而第m位之后的所有0都变成1,整数中第m位之前的所有位都保持不变,然后我们使用 (n-1)&n,这样每次操作就可以消去最右边的1。
下面是次程序代码:
#include<stdio.h>
int numberOf11(int n)
{
int count=0;
while(n)
{
count++;
n=(n-1)&n;
}
return count;
}
int main()
{
printf("请输入一个整数:\n");
int n;
scanf("%d",&n);
printf("此整数 %d 二进制有 %d 个1\n",n,numberOf11(n));
return 0;
}
最后一种解法代码十分简洁,但是这种做法不易想到,希望能好好总结,提炼出一些普遍规律。
推广:
1、用一条语句判断一个整数是不是2个整数次方,一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其他所有的位都是0,所以这题实际上就是要求大家判断一个整数中是否只有一个1.
显然这题只要用上面的语句 n&(n-1)就行了
2、输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n,比较10的二进制表示为1010,13的二进制表示为1101,则需要改变1010中的3位才能得到1101.我们可以分为两步来解决这个问题:
(1)求这两个数的异或,
(2)统计异或结果中1的位数。