异或运算的骚操作

一,十六进制

二,题目引入

1,异或运算的无进位相加

在异或运算(XOR,exclusive OR)中,无进位相加是指一种加法运算,其中不产生任何进位。这个运算在二进制计算中是通过异或操作来实现的。

异或运算的定义

异或运算是针对两个二进制数位进行操作的,它的规则如下:

  • 0 ⊕ 0 = 0
  • 0 ⊕ 1 = 1
  • 1 ⊕ 0 = 1
  • 1 ⊕ 1 = 0

可以看出,当两个位相同时,结果是0;当两个位不同时,结果是1。这个特性使得异或运算常被用来表示无进位相加。

无进位相加的实现

无进位相加的核心是每一位进行异或运算。传统的二进制加法会在两个1相加时产生进位,但在无进位加法中,进位被忽略,只关注每一位的异或结果。

举个例子,假设我们要对两个二进制数进行无进位加法:

示例:无进位加法 1011 + 1101
  1. 对应位进行异或操作:
    • 第1位:1 ⊕ 1 = 0
    • 第2位:0 ⊕ 0 = 0
    • 第3位:1 ⊕ 1 = 0
    • 第4位:1 ⊕ 1 = 0

结果是:0000

无进位加法和传统加法的区别

在传统的二进制加法中,当两位都是1时会产生进位,而在无进位加法中,进位会被忽略掉,只保留每一位的异或结果。因此,无进位加法并不考虑进位的传播,只关心当前位的加法结果。

总结

  • 无进位相加:通过对每一位使用异或运算来实现,只关心每一位的结果,不处理进位。
  • 异或运算:是实现无进位相加的基础,它遵循的规则是:相同为0,不同为1。

补充

1)异或运算满足交换律,结合律。

0 ^ n = n 

n ^ n = 0

2)

如果a ^ b = c,则  a = c ^ b, b = c ^ a.

证明:

a ^ b = c, -> a ^ b ^ b = c ^ b ,->a ^ ( b ^ b) = c ^ b  --> a ^ 0 = c ^ b.

应用:

有一个数组A,所有元素都异或称为异或和,为X。然后其中一部分进行异或后的结果是Y。那么剩下的部分异或和就是 X ^ Y .

解题

可以将白球,黑球分别想象成异或运算中的0 和 1,当不同颜色相遇就放黑球,相同放入白球。

所以每次操作就相当于取两个数字进行异或运算,再将结果放回,与其它数字进行异或运算。

所以整个操作就像求这个数组的异或和。而异或操作就是无进位相加,而无进位相加由1的个数决定,偶数为0,奇数为1.     

三,骚操作

1,交换两个数

原理:

有两个数字a和b,分别设其值为x和y,

a = a ^ b = x ^ y 

b = a ^ b = x ^ y ^ y = x

a = a ^ b = x ^ x ^ y = y

这样实现交换两个变量的值。

代码:

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	int b = 15;
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;

	cout<<a<<" "<<b<<endl;
   return 0;
}

在数组中交换两个数字的位置时,例如swap函数,如果索引相等,就会将索引处的值变为0.因为共用一个内存空间,所以一直都是自己异或自己。

2,不使用任何判断语句和比较操作,返回两个数的最大值

原理:

通过右移操作获取两数之差的符号位数字,然后通过异或运算决定结果中a和b的权重

代码:

#include<iostream>

using namespace std;

int flip(int n)
{
	return n ^ 1;
}
//在获取了符号位上的值后,负数为1,非负为0.
// 异或后,负数为0,非负为1.


int sign(unsigned int n)
{
	return flip(n >> 31);
}

int getmax(int a, int b)
{
	int c = a - b;//获取差值,由c正负决定,但是不能使用判断语句。
	int returnA = sign(c);
	int returnB = flip(returnA);
	return (a * returnA + b * returnB);
}

int main()
{
	cout << sign(5) << endl;
	cout << flip(1) << endl;
	cout << "the max is " << getmax(8, 10);
	return 0;
}

关键是:也是我一直没弄对的原因就是区分逻辑右移与算术右移。所以我们需要将c转为无符号int类型。

  • 算术右移:对有符号整数执行右移时,符号位扩展到高位,确保符号正确性。
  • 逻辑右移:对有符号或无符号整数执行右移时,都会用 0 填充高位,符号位不扩展。

虽然 C++ 默认的右移操作(>>)对于无符号整数执行逻辑右移,对于有符号整数则执行算术右移。如果你需要逻辑右移有符号整数,可以通过将其转换为无符号类型后进行右移操作,或者手动实现逻辑右移函数。

逻辑右移与算术右移的区别

  • 算术右移:对于有符号整数,符号位(最高位)会被扩展到高位,以确保符号的正确性。这意味着负数的右移操作会保持符号位为 1,从而避免符号的丢失。

  • 逻辑右移:无论符号位是什么,逻辑右移都将高位填充为 0。这种右移方法常用于无符号整数(如 unsigned int 类型),因为无符号整数没有符号位。

逻辑右移

逻辑右移操作将所有的位(包括符号位)向右移动,并用 0 填充高位。在逻辑右移中,不考虑符号位的扩展,所有位都会平等对待。例如:

  • 对于无符号整数,右移时填充的高位总是 0。
  • 对于有符号整数,符号位并不会被扩展。

3,寻找缺失的数字

1,题目

0到x的一串数字,从中取出一个后剩余的数字放入到长度为x-1的数组里面,请你找出缺失的数字。

2,原理

看补充那一栏目录下的应用。

3,代码

#include<iostream>

using namespace std;

int find_lost_number(int* arr,int len)
{
	int origin = 0;
	int current = arr[0];

	for (int i = 1; i <= len; i++)
	{
		origin ^= i;
	}

	for (int i = 1; i < len; i++)
	{
		current ^= arr[i];
	}

	return origin ^ current;
}


int main()
{
	int arr[9] = { 0,1,2,3,4,5,7,8,9 };
	cout << "the lost number is " << find_lost_number(arr, 9) << endl;
	return 0;
}

还可以有合并的地方

int find_lost_number(int* arr,int len)
{
	int origin = len;
	int current = arr[0];

	for (int i = 1; i < len; i++)
	{
		origin ^= i;
		current ^= arr[i];
	}


	return origin ^ current;
}

4,异或消消乐

存在一个数组,有一个数字出现奇数次,其他数字出现偶数次。请找出哪个出现奇数次的数字。

1,原理

全部异或,偶数次的异或后会等于0.奇数次的等于它本身。

2,代码

#include<iostream>

using namespace std;

int find_odd(int* arr,int len)
{
	int ans = 0;
	for (int i = 0; i < len; i++)
	{
		ans ^= arr[i];
	}

	return ans;
}

int main()
{
	int arr[9] = {7,7,7,4,4,4,4,6,6};
	cout << "the odd is " << find_odd(arr, 9) << endl;
	return 0;
}

5,二进制数字最右侧的1

过程:

n & (~n + 1)

代码:

#include<iostream>
#include<bitset>

using namespace std;


int main()
{
	int binary = 0b11101100;
	int ans = binary & (~binary + 1);
	bitset<32> bin(ans);
	cout << bin << endl;
	return 0;
}

二进制输出的几种方式:

6,寻找数组中两个奇数次的数字

题目

一个数字有两个奇数次的数字,其他数字都是偶数次,请你找出这两个数字出来。

原理

先将数组里所有元素异或得到x,x = a ^ b.在使用方法5提取x的最右侧的1,这个位置上的1意味着a和b肯定一个是1一个是0,如果相同的话异或结果会是0.其他偶数次的数字对异或结果没有影响。

然后又因为数组里面的数字肯定可以分为两类,一类该位上是1,一类是0.通过提取的最右侧的1和&运算来筛选,筛选后再逐个异或得到a或b。

代码

#include<iostream>

using namespace std;


int main()
{
	int arr[] = { 1,2,2,4,3,3,6,6,6,6 };
	int len = sizeof(arr) / sizeof(int);

	int eor1 = 0, eor2 = 0, rightone;
	for (int i = 0; i < len; i++)
	{
		eor1 ^= arr[i];
	}

	int n = eor1 ^ eor2;
	rightone = n & (-n);
	for (int num : arr)
	{
		if ((num & rightone) == 0) eor2 ^= num;
	}

	cout << "the numbers are " << eor2 <<" "<< (eor1 ^ eor2) << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值