一,十六进制
二,题目引入
1,异或运算的无进位相加
在异或运算(XOR,exclusive OR)中,无进位相加是指一种加法运算,其中不产生任何进位。这个运算在二进制计算中是通过异或操作来实现的。
异或运算的定义
异或运算是针对两个二进制数位进行操作的,它的规则如下:
- 0 ⊕ 0 = 0
- 0 ⊕ 1 = 1
- 1 ⊕ 0 = 1
- 1 ⊕ 1 = 0
可以看出,当两个位相同时,结果是0;当两个位不同时,结果是1。这个特性使得异或运算常被用来表示无进位相加。
无进位相加的实现
无进位相加的核心是每一位进行异或运算。传统的二进制加法会在两个1相加时产生进位,但在无进位加法中,进位被忽略,只关注每一位的异或结果。
举个例子,假设我们要对两个二进制数进行无进位加法:
示例:无进位加法 1011 + 1101
- 对应位进行异或操作:
- 第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;
}