求一个整数二进制中1的个数(详解)

求一个整数二进制中1的个数(详解)

例如:13 ----> 00001101 二进制中有3个1
注意:整数是以二进制的补码在计算机中存储的
三种方法和大家一起分享!

方法一(通过按位与&和按位右移>>来判断)
以13的二进制为例,我们将13和1进行按位与,
即 00001101
& 00000001
得 00000001(对应十进制为1)
  这样就可以很巧妙的获取到13二进制的最低位。与的结果为1,则13的最低位为1,与的结果为0,则13的最低位为0。
  为什么要和1进行按位与呢?原因是:1的二进制最低位为1,其它高位全为0,这些为0的高位会将13相对应的二进制位也与为0,而1的最低位为1,那么与的结果最终就取决于13的二进制最低位,为1则为1,为0则为0。
  目前为止我们已经知道如何判断13的二进制最低位是1还是0,那么剩余的二进制位也就很简单了。具体这样做:我们把13按位右移一位,即00001101 --> 00000110,原本的次低位变成了现在的最低位,然后和1按位与,判断结果是1还是0,此时我们就是得知了13二进制次低位是1还是0,然后依次右移一位再按位与再判断,直到判断完所有的二进制位,判断的过程中设定计数器计数即可。
  提示:在右移的时候,判断二进制最低位时,不需要右移,判断第二位时右移1位,那么判断第32位时,只需要右移31位,所以在右移的时候可以通过循环来控制。

代码如下:

int main()
{
	int value = 0; //此处以整形为例
	scanf("%d", &value);
	int count = 0; //记录二进制中1的个数
	for (int i = 0; i < 32; i++) //循环遍历二进制中的每一个bit位
	{
		if ((value >> i) & 1 == 1) //判断该value对应的二进制从低位起第i+1位是1还是0
			count++;
	}
	printf("count = %d\n", count);
	return 0;
}
效率分析:我们需要将每一个二进制位都要判断一下,所以有多少个二进制位,我们就需要循环多少次

方法二(通过取模%和除/来判断)
  通过模2可以判断二进制最低位是1还是0,除2舍弃最低位(可以类比10进制进行理解:十进制模10得到个位数,除以10舍弃个位)

代码如下

int main()
{
	printf("%d", -1 % 2);
	int count = 0;
	int value = 0;
	scanf("%d", &value);
	while (value) //value不等于0,一直循环
	{
		if (value % 2 == 1)
			count++;
		value /= 2;
	}
	printf("count = %d", count);
}
效率分析:每次除以二,所以循环n次,n为log以2为底value的结果

  但是方法二好像有一个小小的问题,对于正整数没有问题,但是负数好像并不正确,如-1,-1在计算机中的二进制有32个1,但结果算出来是0,为什么呢?
  先从代码执行的角度看:-1%2结果为-1,不等于1,if判断条件不满足,count不加一,然后-1/2结果为0,跳出while循环,所以最后count等于0。
  错误的原因有两点。
  第一点:正整数取模运算和负整数取模运算有点不一样,正整数的模2确实能判断二进制的最低为是0还是1,但负整数就不行了,主要的区别在于取模计算过程中负整数进行除法运算时操作不同。
  如:取模计算公式:x%y=y*(x/y)+r,x和y是被模数和模数,r是余数。如果x/y的结果是正数(可以是整数,也可以是小数),则结果向下取整;如果结果是负数,则结果向上取整,将取整后的结果带入公式即可的取模结果。正是因为x/y结果的正负不同,处理不同,所以取模结果就不同。
  第二点:正整数除以2的目的是舍弃掉二进制位中最低位,而负整数除以2并不是我们想要的结果。如果不能理解的话,可以这样理解:把移位和除法联系起来,右移1位相当于除2,左移1位相当于乘2,那么代码中value/=2不就是相当于右移1位,右移1位不就舍弃掉了二进制中的最低位。但是这个原理只是对正整数而言的,负数的除以2并不等于右移1位置,即并不会舍弃掉二进制最低位,所以此处负数会出现问题。
  总结:无论原因的第一点还是第二点,都是因为value是负整数惹的祸,那我让value变成无符号的数不就可以了(无符号的数可以看成没有符号位的正整数,其实内存中的二进制位数据并没有发生变化,只不过是计算机向外翻译或者解释不同了),如:-1存放在内存中二进制为11111111(这里只取低8位),看成无符号的数,即为(2^32)-1,这样处理就不会有问题了。

代码改进如下

int main()
{
	int value = 0;
	int count = 0;
	scanf("%d", &value);
	unsigned int n = value; //将有符号数看成无符号数
	while (n)
	{
		if (n % 2 == 1)
			count++;
		n /= 2;
	}
	printf("count = %d\n", count);
	return 0;
}

方法三(通过公式n = n &(n-1))
公式是什么意思,看下面这个例子
000010110 -----n
000010101 -----n-1
000010100 -----n = n & (n-1)
//000010011 -----n-1
//000010000 -----n = n & (n-1)
  从相与第一次得到的结果会发现将原来n的二进制中从右边数第一个1消掉了。因为n-1之后n的二进制从右边往左数遇到的第一个1会变成0,1之后的0全变成1,其余的高位不变,这些发生变化的bit位恰好和之前的bit位相反(0变1,1变0),然后与的结果全为0,这样就很好的消除掉了从右边起遇到的第一个1
  第二次消掉了右边起第二个1。依次类推,直到n的二进制位全为0,即n等于0为止。

代码如下

int main()
{
	int n = 0;
	scanf("%d", &n);
	int count = 0;
	while (n) //n不等于0,一直循环
	{ 
		n = n & (n - 1); //每循环一次,消掉一个1
		count++; //计数器加1
	}
	printf("count = %d", count);
	return 0;
}
效率分析:每次执行一次公式就消掉一个1,所以二进制中有多少个1,就循环多少次

以上呢,是我个人在做这道题的想法,希望能分享出来,看能不能帮助到和我之前一样有困惑的朋友,如果有什么问题,请不吝指教。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值