二进制位运算题

在这里插入图片描述

本期介绍🍖
主要介绍:1. 在不创建临时变量的情况下交换两个变量,2. 计算变量在内存中存放2进制位“1”的个数,3. 求两个数的二进制中不同位的个数,4. 分别打印整数的二进制中奇数位和偶数位,5. 判断一个整数是不是2的n次方,6. 在成对元素的数组中找单身狗。



1. 交换两个变量(不创建临时变量)

  题目: 交换两个整数的内容,在交换的过程中不允许创建第三个临时变量。
  一般想要交换两个变量,会通过创建第三个临时变量来完成,就如同想要将两瓶饮料进行交换,需要第三个空瓶一样。代码实现如下所示:

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int tmp = 0;
	printf("交换前:a=%d, b=%d\n", a, b);
	//交换变量a和变量b
	tmp = a;
	a = b;
	b = tmp;
	printf("交换后:a=%d, b=%d\n", a, b);
	return 0;
}

在这里插入图片描述


1.1 加减法

  解题思路: 求出两个变量的和,通过和与变量的差,在不创建第三个临时变量的情况下交换两个变量。代码如下所示:

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d, b=%d\n", a, b);
	//交换变量a和变量b
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:a=%d, b=%d\n", a, b);
	return 0;
}

在这里插入图片描述

  其实不难看出加减法是存在缺陷的,当a和b的值都很大的时候(即都非常接近int类型所能表示范围中的最大值),那么a+b的值必然会超过int型所能存放的极限,导致一些二进制位的丢失,最后交换的结果必然会是错误的。


1.2 异或法

  所谓的异或法就是通过异或操作符符 “ ^ ” 来实现交换两变量中的值。在这里不得不说一下异或运算符的一些性质:

  性质1: 计算3 ^ 3表达式,3的二进制位为011,那两个同为011的二进制位进行异或操作,结果必然会是000;同理5 ^ 5的结果也是000,所以不难看出异或的第一条性质为:x^x = 0

  性质2: 计算3 ^ 0表达式,3的二进制位为011,0的二进制位为000,进行异或操作结果为011,也为3。由此发现不管什么数与0进行异或结果必然会是该数本身,所以异或的第二条性质为:x^0=a

  性质3: 表达式(3 ^ 3 ^ 5)的计算结果为5,表达式(3 ^ 5 ^ 3)的结果也为5。所以不难看出异或的第三条性质(交换律):(x ^ x ^ y) = (x ^ y ^ x)

  代码实现如下所示:

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d, b=%d\n", a, b);
	//交换变量a和变量b
	a = a ^ b;//a里面存放a^b
	b = a ^ b;//此时的a^b其实是(a^b)^b,结果为a
	a = a ^ b;//此时的a为a^b,b为a,故a^b其实是(a^b)^a,结果为b
	printf("交换后:a=%d, b=%d\n", a, b);
	return 0;
}

在这里插入图片描述

  其实可以将a^b得到的结果看作是一个秘盒,a与b通过异或与秘盒结合都可以得到另个数。异或法就没有加减法的缺陷,不会因数据过大使得存储不下。


2. 计算变量在内存中存放2进制位“1”的个数

  题目:输入一个整数 n ,计算n在内存中存放的二进制位中1的个数(其中负数用补码表示)。下面会用3种不同的方法来解决这道笔试题。


2.1 除二取余法

  在数学中要将十进制数转换成二进制数老师肯定教过一种方法“ 除2取余法 ”。举例如下图所示,计算十进制89化为二进制数。除完所有数,把所有余数从下往上排序后,得到的结果就是89的二进制表示形式为:1011001。
在这里插入图片描述
  引用此法,每次除2后统计余数为1的情况,这样就可以做到计算变量在内存中存放2进制位为“1”的个数啦。代码如下:

#include<stdio.h>
int sta_bit_num(int n)
{
	int count = 0;
	while (n)
	{
		if (n % 2 == 1)
		{
			count++;
		}
		n /= 2;
	}
	return count;
}
int main()
{
	int n = 0;
	int count = 0;
	printf("请输入要计算的数:");
	scanf("%d", &n);
	count = sta_bit_num(n);
	printf("二进制中1的个数:%d\n", count);
}

在这里插入图片描述
  乍一看似乎没什么问题,但当输入负数时就会发生错误。就譬如输入-1。如下图所示,得出的结果为0,如下图所示:

在这里插入图片描述

  由于在编写代码时,压根就没有思考过负数的情况。而负数再内中存放的补码的形式与正数是不同的,所以如果还按照原先计算正数的除2取余法来计算负数必然错误。解决方法:将输入的有符号数强制类型转换为无符号数,用除2取余法再对该无符号数进行统计就间接达到效果了。代码如下:

#include<stdio.h>
int sta_bit_num(unsigned int n)
{
	int count = 0;
	while (n)
	{
		if (n % 2 == 1)
		{
			count++;
		}
		n /= 2;
	}
	return count;
}
int main()
{
	int n = 0;
	int count = 0;
	printf("请输入要计算的数:");
	scanf("%d", &n); 
	count = sta_bit_num(n);
	printf("二进制中1的个数:%d\n", count);
}

在这里插入图片描述


2.2 移位与1法

  解题思路:一个数只要" &1 "就可以求得该数在内存中存放的二进制位的最低位,再通右移操作符“ >> ”就可以统计32位上所有1的个数了。如下图所示:

在这里插入图片描述
  代码如下:

#include<stdio.h>
int sta_bit_num(int n)
{
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if ((n >> i) & 1 == 1)
		{
			count++;
		}
	}
	return count;
}
int main()
{
	int n = 0;
	int count = 0;
	printf("请输入要计算的数:");
	scanf("%d", &n);
	count = sta_bit_num(n);
	printf("二进制中1的个数:%d\n", count);
}

在这里插入图片描述

  值得注意,移位与1法会做多余的判断,不管被求的那个数在内存中的二进制位有几个1,始终都是要循环判断32次的。


2.3 消1计数法

  根据题意可知,所需统计的仅仅只是二进制位中1的个数,但上面两种方法在判断统计二进制位1的同时还会多余的判断二进制位0。
  现在讲解一种非常牛逼的算法“ 消1计数法 ”。该方法是将被求的数" & "比被求数小1的数,即:n & (n-1) 。计算结果会让二进制序列中最低位的那个1变为0。如果再多来几次这样的操作,被求的那个数最终会变成0,这样做就效率不就上来了嘛。如下图所示:
在这里插入图片描述
  代码为:

#include<stdio.h>
int sta_bit_num(int n)
{
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
}
int main()
{
	int n = 0;
	int count = 0;
	printf("请输入要计算的数:");
	scanf("%d", &n);
	count = sta_bit_num(n);
	printf("二进制中1的个数:%d\n", count);
}

在这里插入图片描述
在这里插入图片描述


3. 求两个数二进制中不同位的个数

  题目: 求两个整数m和n的二进制序列中,有多少位不相同的?输入例子:1999 , 2299;输出例子:7。
  大家在没看过上面的算法思路时,来求解这道题目的思路一定是逐位进行比较的是否相等,效率太低。现在再思考一下这道题的解法,只需要两步:

  第1步:按位异或^计算两个数的相异处。

  第2步:用“ 消1计数法 ”统计二进制位中1的个数,即:二进制位相异处的个数。

  代码如下:

#include<stdio.h>
int sta_bit_num(int x, int y)
{
	int count = 0;
	int n = x ^ y;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
}
int main()
{
	int a = 0;
	int b = 0;
	int count = 0;
	printf("请输入要计算的两个数:");
	scanf("%d%d", &a, &b);
	count = sta_bit_num(a, b);
	printf("两个数二进制数中不同位的个数:%d\n", count);
}

在这里插入图片描述


4. 打印整数二进制的奇数位和偶数位

  题目: 获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列。
  该题先假设二进制序列中最低位为奇数位1,然后我们想办法从高位向低位打印奇数位、偶数位。这样就需要两个循环分别打印奇数位和偶数位了。

#include<stdio.h>
void print_odd(unsigned int n)
{
	int i = 0;
	printf("打印奇数位:\n");
	for (i = 30; i >= 0; i -= 2)
	{
		printf("%d ", (n >> i) & 1);
	}
	printf("\n");
}
void print_even(int n)
{
	int i = 0;
	printf("打印偶数位:\n");
	for (i = 31; i >= 1; i -= 2)
	{
		printf("%d ", (n >> i) & 1);
	}
	printf("\n");
}
int main()
{
	int n = 0;
	printf("请输入一个数:");
	scanf("%d", &n);
	print_odd(n);
	print_even(n);
	return 0;
}

在这里插入图片描述
在这里插入图片描述


5. 判断一个整数是不是2的n次方

  解题思路:由于整数 2n 的二进制序列只可能存在一个1。所以在求一个数是否为2 ^ n的整数时,只需要判断该数的二进制序列中只存在一个1就可以了。那这里不想也知道用消1计数法最为高效。代码如下:

#include<stdio.h>
int is_pow(int n)
{
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
	
}
int main()
{
	int n = 0;
	printf("请输入一个数:");
	scanf("%d", &n);
	if (is_pow(n) == 1)
	{
		printf("整数%d是2的n次方\n", n);
	}
	else
	{
		printf("整数%d不是2的n次方\n", n);
	}
	return 0;
}

在这里插入图片描述
在这里插入图片描述


6. 在成对元素的数组中找单身狗

6.1 题目1

  题目:{1,2,3,4,5,4,3,2,1}的一组整型数组中,只有一个数出现一次,其他数都是成对出现的,请找出那个只出现过一次的数字。
  解题思路:异或操作符有两个个性质x^x = 0x^0 = x,由于这一组数中只有一个数出现一次,其余数都是成对出现的,将所有数都异或在一起,就可以得到那个单身狗了。实现代码如下:

#include<stdio.h>
int find_once(int arr[], int sz)
{
	int i = 0;
	int tmp = 0;
	for (i = 0; i < sz; i++)
	{
		tmp = tmp ^ arr[i];
	}
	return tmp;
}
int main()
{
	int arr[] = { 1,2,3,4,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int result = find_once(arr, sz);
	printf("那个单身狗是:%d\n", result);
	return 0;
}

在这里插入图片描述


6.2 题目2

  题目:{1,2,3,4,5,4,3,2,1,6}的一组整型数组中,有两个数出现一次,其他数都是成对出现的,请找出那两个只出现过一次的数字。
  解题思路:异或操作符有两个个性质x^x = 0x^0 = x,由于这一组数中有两个数只出现一次,其余数都是成对出现的。可以找这两个数的区别,然后将这一组数分成两组,各占一个单身狗。最后分别将两组数异或在一起,就可以得到那两个单身狗了。实现代码如下:

#include<stdio.h>
void find_once(int arr[], int sz, int* pa, int* pb)
{
	//1.找两个单身狗的差异
	int diff = 0;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		diff = diff ^ arr[i];
	}
	//2.确定差异的二进制位
	int pos = 0;
	for (i = 0; i < 32; i++)
	{
		if ((diff & 1) == 1)
		{
			pos = i;
			break;
		}
		diff = diff >> 1;
	}
	//3.按差异分组并将异或后的结果返回
	for (i = 0; i < sz; i++)
	{
		if ((arr[i] >> pos) & 1 == 1)
			*pa ^= arr[i];
		else
			*pb ^= arr[i];
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int a = 0;
	int b = 0;
	find_once(arr, sz, &a, &b);
	printf("找到了单身狗:%d,%d\n", a, b);
	return 0;
}

在这里插入图片描述


在这里插入图片描述

这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值