挑战面试编程:计算整数二进制位中1的个数

                           挑战面试编程:计算整数二进制位中1的个数

题目:

在计算机中,整数是以2的补码的形式给出的。 给出整数A和B,假设计算机是32位机,求从A到B之间的所有二进制数中,一共用了多少个1。 输入格式: 多组数据,每组数据一行,由两个整数A,B, -2147483648<=A<=B<=2147483647 输出格式: 每组输出一行,从A到B使用的1的个数。(本题取自csdn高校俱乐部线上编程挑战赛)

分析:

        我们知道任何数据在计算机中都是用二进制表示的,即用一堆0或1表示各种类型的数据。当然还要考虑它的字节数,例如在32位机中。sizeof(int)=4,sizeof(float)=4,sizeof(double)=8,sizeof(long long)=8。当然一种数据类型了多少字节来存储,这不仅与cpu的字长、系统有关,还与编译器有关。上面的数据是我的机器上测试值。既然要统计二进位中1的个数,当然优先考虑位运算。


下面介绍几种位运算(位运算即按位运算):

位与  &  

运算规则:0&0=0;0&1=0;1&0=0;1&1=1;

位或 |

运算规则:0|0=0; 0|1=0; 1|0=0; 1|1=1;

位异或 ^

运算规则:0^0=0; 0^1=1; 1^0=0; 1^1=0;


思路:

用该整数与1做32次位与运算,即可统计1的个数。因为一个四个字节的整数1,它的高位有31个0,只有最后一位是1,即00000000 00000000 00000000 00000001。这样就可以判断概数的二进制最低位是1还是0。位于结果若是1,则表明最后一位1,若是0,则表明最后一位是0(对照运算规则,可很容易看出)。每一次判断后,都对该整数右移一位,以判断次最低位。


代码

<span style="font-family:Courier New;font-size:14px;">
#include<iostream>
using namespace std;
long long count_one(int n)
{
	long long count=0;   //考虑到数字可能很大,就用使用long long类型
	for(int i=0; i<32; i++)
	{
	    if(n&1)   //位与运算
		count++;
		n>>=1;    //向右移动一位
	}
	return count;
}
int main()
{
	int i,j;
	int a,b;
	long long sum=0;
	while(cin>>a>>b && a)   //输入0 0结束程序
	{
		for(int j=a; j<=b; j++)
		sum+=count_one(j);
		cout<<sum<<endl;
		sum=0;   //重置
	}
	cin.get();
	return 0;	
}
</span>

运行实例:



update:

感谢一楼的提示,有了以下的新思路:

思路:如果当前数的最小二进位是0,则下一个数不用统计,便可知它的二进制位1的个数比当前数多一个。于是,我们可以两个两个的计算,大概只需遍历 n/2 次,这样是不是快很多。

<span style="font-family:Courier New;font-size:18px;">
int count_one(int a, bool &flag)
{
	int temp = a;
	int count = 0;
	if (temp & 1)
	flag = false;
	while (temp)
	{
	count++;
	temp = temp&(temp-1);
	}
	return count;
}
int main()
{
	int a, b;
	bool flag = true;
	int count = 0;
	double sum = 0;
	while (scanf_s("%d %d", &a, &b) != EOF)
	{
		for (int i = a; i <= b; i++)
		{
			count = count_one(i, flag);
	
			if (flag)
			{
				if (i < b) //若还有下一个数
				{
					sum += 2 * count + 1;
					i++;
				}
				if (i == b) //无下一个数
					sum += count;
			}
			else
			{
				sum += count;
			}
			flag = true;
	
		/* //这个是以上流程的优化写法
		if (flag && i < b)
		{
		sum += 2 * count + 1;
		i++;
		}
		else
		sum += count;
		flag = true;
		*/
		}	
		printf("%.0lf\n", sum);
		sum = 0;
	}
	system("pause");
	return 0;
}
</span>

几点说明:

  1. 右移运算的左边二进位如何填充。这个c语言规范中没有明确规定,那就是说具体实现得看编译器如何处理。好大多数编译器是这样处理的:对于无符号数或正数,用0填充;对于负数用1填充。大家可以在自己的机器上试试。
  2. 右移运算a>>1,是不会改变a自身的值的,要想改变a自身的值,得使用a>>=1。
  3. 为了终止程序,我个人加了对a值非0的判断,即此程序不可原封不动作为挑战赛的答案,得进行必要修改。
  4. 关于位运算的奇妙用处还有很多,希望各位同学多提想法,欢迎不吝赐教。先谢了!

补充:

异或运算^ 可用来交换数据(注意:这里的a,b不能指向同一个元素,否则最后都是0,所加一个是否相等的判断)

void swap(int &a, int &b)
{
	if(a!=b)
	{
		a^=b;
		b^=a; 
		a^=b;
	}
}

关于交换数据的其它方法:

最常见的是增加一个变量 temp,方法如下:

方法一:

void swap(int &a, int &b)
{
	if(a!=b)
	{
		int temp=a;
		a=b;
		b=temp;
	}
}

方法二:

void swap(int &a, int &b)
{
	if(a!=b)
	{
		a=a+b;
		b=a-b;
		a=a-b;
	}
}

小结:方法二容易造成溢出,因为a+b很可能超出一个int型数据的范围,推荐使用前两种方法。


update 2015年10月9日

最快的算法或许是这样:

对于一个整型的数a,做运算a = a & (a - 1);后a二进位为1且位于最右边的那一个二进制位会消失。

如a=3;做运算a&(a-1)

  0000 0011

& 0000 0010

  0000 0010

由于这种现象的存在,只需记录a为0前,运算a&(a-1)可以进行的次数,即可得知a的二进制位上1的个数。


所有内容的目录


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值