1 问题

实现一个函数,输入一个函数,输出该二进制数据中1的个数。例如9表示二进制数据1001,有2位是1,因此输入9,该函数会输出2。


2 分析

我们先了解下计算机里面位运算,有5种

1)& 这个是与操作,规律如下

1 & 1 = 1     1 & 0 = 0     0 & 1= 0    0 & 0 = 0

2)| 这个是或运算,规律如下

1 | 1 = 1     1 | 0 = 1     0 | 1= 1    0 | 0 = 0

3)^ 异或运算,规律如下

1 ^ 1 = 0     1 ^ 0 = 1      0 ^ 1 = 1     0 ^ 0 = 0

4) 左移 m<<n 表示把m左移n位,在左移n位的时候,最左边的n位丢弃,同时右边补上n个0 比如

00001010 << 2 = 00101000

10001010 << 3 = 01010000

5) 右移 左移 m >> n 表示把m右移n位,在右移n位的时候,最右边补上n个0 ,最左边分2种情况,如果数字是一个无符号整形

则用0填补最左边的n位,如果是一个有符号的数据,则最左边用数字的符号填补n个数据。如果是正数,左边补n个0,是负数左边补n个1.

00001010 >> 2 = 00000010

10001010 >> 3 = 11110001


这里我们可以把原数据和1进行&操作,如果二进制数据尾巴进行&操作,如果包含1的话&1操作就是1,返之结果为0,然后我们把数据进行右移一位就行。


如果一个正数要除以2,我们效率最高的是把这个数据进行右移一位。


3 代码实现

C++版本

#include <stdio.h>

/*
*二进制数据里面包含数字1的个数
*/
int containOneNumber(int value)
{
int count = 0;
while (value != 0)
{
//这里是(value & 1)不是(value & 0)
if (value & 1)
++count;
//这里是value = value >> 1,而不是value >> 1; 我们要用变量接收它
//不然不管就只执行了一次也就是value除以了2,所以导致死循环。
value = value >> 1;
}
return count;

}

int main(void)
{
int result = containOneNumber(9);
printf("result is %d\n", result);
return 0;
}

java版本

public int containOneNumber(int value)
{
int count = 0;
while (value != 0)
{
if ((value & 1) != 0)
{
count++;
}
value = value >>> 1; //>>>就是java中的无符号右移
}
return count;
}

我们知道java用 >>> 是无符号右移,右移的时候,所以最高位左边都是0,如果这个数是负数的时候,右移的话最高位会补1,

C++版本就会变成死循环。


4 优化

方法1:

n与1做与运算,判断n的最低位是不是为1,接着把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1….这样反复左移

int containOneNumber1(int n)
{
int flag = 1;
int count = 0;
while (flag != 0)
{
if ((flag & n) != 0)
{
count++;
}
flag = flag << 1;
}
return count;
}


方法2:

把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0,那么一个整数的二进制表示中有

多少个1,就可以进行多少次这样的操作

int containOneNumber1(int n)
{
int flag = 1;
int count = 0;
while (n != 0)
{
++count;
n = n & (n - 1);
}
return count;
}


5 总结

1) 把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0,那么一个整数的二进制表示中有多少个1

2)n与1做与运算,判断n的最低位是不是为1,接着把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1….这样反复左移

3) 如果是正整数的情况下,我们可以把正整数右移动和1进行&操作,然后再去统计。