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

来源:互联网 发布:中科院对学生调查数据 编辑:程序博客网 时间:2024/05/16 19:21

                           挑战面试编程:计算整数二进制位中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++;}elsesum += 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的个数。


所有内容的目录

  • CCPP Blog 目录


1 0
原创粉丝点击