(超详细)求一个数中二进制位多少个1的n种境界

 问题:设计一个程序,实现输入一个数算出它的二进制位中的1有多少个,如输入3,它的二进制位中的1有两个,所以输入3,输出2

境界1:

   假设我们输入7,它的二进制表示为00000111,我们只需要创建一个变量count,然后对00001111模2(7%2),只要它的结果为1,说明它最右边的一位为1,这时我们就得到了一个1,只要得到一个1,就让count加1,完成这个动作后我们还需要对左边的每一位二进制位判断,是1count加一,不是则不进行任何操作,那么我们就要在做完一次以上动作之后把最右边的数丢掉,让原本输入的数每次除2便可以依次得到左边的每一位数,这样的操作我们使用循环实现,当我们输入的数变成0时循环结束。

#include<stdio.h>
int count_one(int n)
{
	int count = 0;
	while (n)
	{
		if (n % 2 == 1)
		{
			count++;
		}
		n = n>>1;//右移一位,相当于除二
	}
	return count;

}
int main()
{
	int num = 0;
	scanf("%d", &num);
	int ret = count_one(num);
	printf("%d", ret);
	return 0;
}

对我们输入的数进行以上操作,当这个数最终为0时,count中也存放着这个数二进制位中1的个数,此时运行我们的代码:

输入7,果然得到了3,当我们输入-1时,我们应该得到32个1,我们运行程序看看吧

我们输入-1,在此程序运行什么也没输出,看来,这个程序还是有一点小小的bug,这个bug就是我们下一个程序要解决的问题。

境界2

  上一个程序为什么输入-1会没有任何输出呢?那是因为当n=-1时,进入循环,-1模2还是为-1,不等于1,不会执行if语句,直接执行下面的右移一位(相当于n➗2),执行完这个操作后n变成了0,不满足进入循环的条件,直接返回了count,但是由于count没有任何变化,便没有进行输出,那么我们是解决这个问题的呢,很简单,规则只要求我们求出一个数的二进制序列中的1的个数,但是我们的算法负数不能使用,那我们能不能把负数变成正数呢,答案是可以,我们只需要用一个无符号整型接收我们输入的数字,让它每次模2都不会是-1,只需把我们的count_one函数的参数由int类型变成unsigned int类型就可以实现:

#include<stdio.h>
int count_one(unsigned  int n)//用无符号类型解决负数无法计算的问题
{
	int count = 0;
	while (n)
	{
		if (n % 2 == 1)
		{
			count++;
		}
		n = n>>1;//右移一位,相当于除二
	}
	return count;

}
int main()
{
	int num = 0;
	scanf("%d", &num);
	int ret = count_one(num);
	printf("%d", ret);
	return 0;
}

让我们再次输入-1

32就被我们的程序计算出来了,那有没有负数也可以用的算法呢,当然有,下一个程序我们对算法进一步优化。

境界3 

  在进行算法讲解前,我们先要介绍两个操作符,以便大家更好地理解:

1.>>(算术右移操作符):对一个二进制序列向右移动n位,如果我们写出7>>1,表示我们要对7的二进制序列左移一位,7的二进制序列为00000111,右移一位之后最右边的1就被丢掉,这时只剩下7位,最左边就会补一个0,所以7>>1的结果为00000011,转换成十进制为3,所以右移一位有整个数除以2的效果。

2.&(算数与操做符):对两个数二进制序列的运算对齐每一位二进制,同为1则结果为1,有一个0或者两个0结果则为0,例如7&1,就是00000111对00000001进行与运算,而只有第一位两个数都为1,所以得到的结果为00000001,转换成十进制则为1。

了解了这两个操作符 ,我们发现,任何数只要和1进行与运算,只要最右边为1,结果都为1,如10101011和00000001进行与运算为1,11111111和00000001进行运算也为1,我们就可以利用这个特性,对它的每一位和1进行与运算,如果结果等于1,那么1的数量加一,一个整型变量的内存占4个字节,一个字节占8位,所以4×8等于32,我们输入的数都有32位二进制,我们需要对这个数和1进行与运算,接着右移一位再和1进行与运算,那么这个数和1进行与运算和右移一位各执行32次,有程序:

#include<stdio.h>
int count_one(int n)
{
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if ((n&1) == 1)
		{
			n >>= i;
			count++;
			
		}

	}
		
	return count;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = count_one(n);
	printf("%d", ret);
	return 0;
}

让我们测试一下:

没有意外,我们的算法成功完成了求1的个数的任务 ,但是我们又发现这个程序不论输入什么都会执行32次,我们能不能再对它进行优化,使程序更加的高效,让我们试试吧。

境界4

  当我们了解到了&(与)运算的特性之后,我们就可以来看到n&(n-)的规律,当n=11,也就是00001011时,n-1=00001010,再执行n&(n-)后,此时n又变成了00001000,再执行n&(n-1)后,n变成了00000000,从这三次n&(n-1)的操作中,不难发现,每次执行这样的操作,都会使左边消失一位1,如果我们把消失了多少个1计算出来,是不是也能求出n有多少位二进制1呢。

代码实现:

#include<stdio.h>
int count_one(int n)
{
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
}
int main()
{
	int num = 0;
	scanf("%d", &num);
	int ret = count_one(num);
	printf("%d", ret);
	return 0;
}

输入测试

设置一个n不为0时进入循环,大大减少了执行代码的次数,结果与前面的算法相同,却提高了算法的效率。

总结:求一个数的二进制序列中1的个数是一个非常有趣的编程问题,它有许多方法可以实现,而我们追求算法越来越简洁 ,高效,这就要求我们养成多思考的习惯,去创造出更多巧妙,高效,简洁的算法。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值