目录
一.计算二进制位中1的个数
思路1
通过% 2 和 / 2 可以得到二进制的每一位
15 的二进制为 0000 1111 (前面的0暂且忽略,但我们要知道在x86下一个数的二进制位有32位)
15 % 2 == 7……1 取 1 这个1可以看作是二进制位最小位上的1 (因为二进制位最小位权重为2^0 它为1的时候就表示
十进制中的1
15 / 2 == 7 》》 0000 0111
7 % 2 == 3……1 --取1
7 / 2 == 3 》》 0000 0011
3 % 2 == 1……1 取1
3 / 2 == 1 》》 0000 0001
1 % 2 == 0……1 取1
1/ 2 == 0 此时停止计算。也就是说,计算的数为0时停止,因此可以考虑将这个数当作进入循环的条件
此时我们可以将这个函数先写出来
int num_count(int n)
{
int count = 0;
while (n)
{
if (n % 2 == 1) //这个二进制位上为1
count++;
n /= 2;
}
return count;
}
但是这么写有个问题,就是输入-1的时候得到的是0.
负数时内存中保存的是补码,所以负数中的二进制位的1的个数,看的是它的补码
如 -1 》》 补码为 11111111111111111111111111111111 有32个1
%2的时候也不等于1,count不++,且 -1 /2 的时候不够商,得0 ,无法进入下一次循环
但是如果把-1传参的时候,传为无符号类型的,那这个11111111111111111111111111111111 就相当于一个很大的正数
原本的符号位(二级制位最左边那一位)也表示一个真实的数 ,这时就没有了原反补码概念,可以直接计算
代码实现
#include <stdio.h>
int num_count(unsigned int n) //这里定为unsigned int 是为了计算负数
{
int count = 0;
while (n)
{
if (n % 2 == 1) //这个二进制位上为1
count++;
n /= 2;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = num_count(n);
printf("%d", ret);
return 0;
}
思路2
当传的参数固定是int型时,也可以用按位与操作来解决
& 两个操作数对应的二进制位有0则为0,同时为1才是1
用 一个数 & 1 (1的二进制位为0000000000000000000000000000000001) 这样就可以判断这个数的最低位是否为1接着用右移操作符 >>
每次移动一个二进制位,最左边补一个0.最右边的二进制位舍去
判断完一次进行右移 >> 将原本的第二位二进制数放到第一位
遍历32次,就可以得出这个数的二进制位有多少个1了。
但这同时也是这个方法的缺点,每一次的计算都要遍历32次,时间效率非常低
代码实现
#include <stdio.h>
int num_count(int n)
{
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
if ((n >> i) & 1 == 1) //注意这里进行右移操作对n原本的值不造成影响,所以可以重复利用
count++; //但是也要注意,此时右移的距离就要根据循环来改变,才能成功遍历二进制位
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = num_count(n);
printf("%d", ret);
return 0;
}
思路3
神奇的等式 n = n&(n-1)
假设输入n=14
二进制
n 1110 原本的数的二进制位上有3个1
n-1 1101 每次计算都会去掉最右边的一个1
n& (n-1) 1100 最后得到0n 1100
n-1 1011
n& (n-1) 1000n 1000
n-1 0111
n& (n-1) 0000如果把n放进while循环 就可以实现,有多少个1,就执行多少次(最后一次是n为0的时候)
代码实现
#include <stdio.h>
int num_count(int n)
{
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = num_count(n);
printf("%d", ret);
return 0;
}
扩展:判断一个数n是不是2的幂次方
2的幂次方的里面二进制数只有1个1
当 n= n&(n-1)只执行一次的时候,这个数就是二的幂次方
二.打印出一个二进制数的奇数位和偶数位上的数
要求:获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列
思路
预期输出结果,一行输出偶数位上的二进制数,一行输出奇数位上的二进制数
我们只需要利用>>这个右移操作符,在每次移动的时候移动2位即可实现取得都是偶数位的二进制位和都是奇数位的二进制位。
再将偶数位和奇数位的打印分别放到两个for循环中
注意要得到的二进制数是从左到右得到的,所以循环初始值i从31 和30开始
右移31位就得到二进制最左边的二进制位(也就是第32位),右移30位就得到二进制数从左到右的第二个数(也就是第31位)
(二进制位的最右边那一位才是最低位哦)
而打印偶数位的限制条件设为 i>0 也就是 i 最小为1 ,右移1位,得到的就是原本二进制位中的第二位。
打印奇数限制条件则位 i >= 0 ,也就是 i 最小为0. 最后一次循环,右移0位,也就是得到原本的第一位。
这里的得到第几位都是用的&1操作,和上面那题原理相同。
代码实现
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
for (i = 31; i > 0; i-=2) //偶数位
{
printf("%d ", (n >> i) & 1);
}
printf("\n");
for (i = 30; i >= 0; i -= 2) //奇数位
{
printf("%d ", (n >> i) & 1);
}
return 0;
}
三.打印出两个数中有几个二进制位的数不同
思路
求两个二进制位有多少个位不同,可以利用操作符 按位异或^ (在键盘上是shift + 数字6)
按位异或的两个操作数的二进制相同时 为0,不同时为1
问题也就转换成,计算进行完按位异或的二进制数中有多少个1了
这时我们就又可以用到神奇的式子n=n&(n-1)来进行计算
代码实现
#include <stdio.h>
int print(int s)
{
int count = 0;
while (s)
{
s = (s - 1) & s;
count++;
}
return count;
}
int main()
{
int n, m;
scanf("%d %d", &n, &m);
int ret = print(n^m); //此时 n^m得到的数就保存了n和m中有多少个不同的值
printf("%d", ret); //因为按位异或 相同为0,不同为1
return 0;
}