大家可能比较熟悉 "与" 运算 和 "或" 运算 ,相对而言, "异或" 运算 平常使用较少,存在感也不强,如果不是刻意提起,可能还想不到它
其实,"异或" 运算也非常重要,它在加密、备份、算法等方面都有应用,每一位开发的同学都应该花点儿时间掌握它的特点和规律,以便在日常工作中能灵活的运用
接下来将介绍异或运算的一些基础知识以及在实际中的一些应用
基础知识
异或是计算机中一种二元逻辑运算, 运算符号是 ^,它按照二进制位进行异或运算,结果为 真 或 假, 它的运算法则如下
x | y | x^y |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
表格 第一列 和 第二列 是异或运算的两个操作数,第三列是异或运算的结果,1 表示真,0 表示假
由表格可知:如果参与运算的两个二进制位相同,则结果为 0 ,否则为 1
也就是说,异或主要用来判断两个值是否相同
重要的性质
下面列出异或运算一些重要的性质,1 表示真,0 表示假, 具体的验证过程比较简单,这里就省略了
1、一个数与自身异或,总是为 0
- x ^ x = 0
2、 一个数与 0 异或,总是其自身
- x ^ 0 = x
3、 交换性
- x ^ y = y ^ x
4、 结合性
- x ^ ( y ^ z ) = ( x ^ y ) ^ z
常见应用
异或运算本身比较简单,重点还是在应用层面,上面列出的性质,在很多方面都有应用
- 判断两个数是否相等
一个数与自身异或,总是为 0,我们可以使用这一点来判断两个变量是否相等
- ( a ^ b ) == 0
当 a 和 b 相等时,表达式为真,否则为假
- 不使用临时变量交换两个数的值
要交换两个数的值,通常做法是借助一个临时变量,然后再进行交换,比如:tmp 是临时变量,现需要交换 a 和 b 两个变量值,过程如下
- tmp = a
- a = b
- b = tmp;
利用异或的一些性质,不用临时变量也能实现交换两个数的值,具体过程如下
- a = a ^ b
- b = a ^ b
- a = a ^ b
假如初始时,a = 1、b = 2
第一个等式 a = a ^ b 结果是 a = 1 ^ 2 = 3
紧接着第二个等式 b = a ^ b 结果是 b = 1 ^ 2 ^ 2 = 1 ^ 0 = 1
最后一个等式 a = a ^ b 结果是 b = 1 ^ 2 ^ 1 = 1 ^ 1 ^ 2 = 0 ^ 2 = 2
可以看到,最终 a = 2、 b = 1 ,它们的值实现了交换
上面的三条语句还可以进一步优化成一条,结果如下
- a ^= b ^= a ^= b
- 简化表达式
根据交换性,可以优化表达式中重复变量的异或运算,比如:表达式 a ^ b ^ c ^ a ^ b 可以做如下简化
- a ^ b ^ c ^ a ^ b # 根据 x ^ y = y ^ x
- = ( a ^ a ) ^ ( b ^ b ) ^ c # 根据 x ^ x = 0
- = 0 ^ 0 ^ c # 根据 x ^ 0 = x
- = c
- 加密
利用异或运算加密是很常见的加密手段,它涉及到三个变量:明文、密钥、密文,假如它们分别记为 plain_text、 encrypt_key、 cipher_text
明文和密钥进行异或运算,可以得到密文
- plain_text ^ encrypt_key = cipher_text
密文和密钥进行异或运算,可以得到明文
- cipher_text ^ encrypt_key
- = ( plain_text ^ encrypt_key ) ^ encrypt_key
- = plain_text ^ ( encrypt_key ^ encrypt_key ) # 根据 x ^ ( y ^ z ) = ( x ^ z ) ^ y
- = plain_text ^ 0 # 根据 x ^ x = 0
- = plain_text
- 备份
根据异或的性质,异或运算还可以用于文件的备份
现有两个文件 filea 和 fileb,它们进行异或运算,会产生一个新的备份文件 bakfile
- bakfile = filea ^ fileb
根据异或的性质,可以通过 bakfile 和 filea 得到 fileb,或者通过 bakfile 和 fileb 得到 filea
后面无论是 filea 或 fileb 文件损坏了,只要不是两个文件同时损坏,都可以通过两者中未损坏的文件 和 bakfile 文件,还原得到另一个文件
当 filea 文件损坏了,可以通过 fileb 和 bakfile 进行异或运算得到完好的 filea 文件
- fileb ^ bakfile
- = fileb ^ ( filea ^ fileb )
- = fileb ^ filea ^ fileb # 根据 x ^ ( y ^ z ) = ( x ^ z ) ^ y
- = fileb ^ fileb ^ filea # 根据 x ^ x = 0
- = 0 ^ filea # 根据 x ^ 0 = x
- = filea
同样,当 fileb 文件损坏了,可以通过 filea 和 bakfile 进行异或运算得到完好的 fileb 文件
f
- filea ^ bakfile
- = filea ^ ( filea ^ fileb )
- = filea ^ filea ^ fileb # 根据 x ^ ( y ^ z ) = ( x ^ z ) ^ y
- = filea ^ filea ^ fileb # 根据 x ^ x = 0
- = 0 ^ fileb # 根据 x ^ 0 = x
- = fileb
解算法题
有些算法可以利用异或的思路进行求解,下面列出力扣上的一道算法题来说明,题目如下:
- 一个长度为 n-1 的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围 0 ~ n-1 之内,
- 在范围 0 ~ n-1 内的 n 个数字中有且只有一个数字不在该数组中,请找出这个数字
- 示例 1:
- 输入: [ 0,1,3 ]
- 输出: 2
- 示例 2:
- 输入: [ 0,1,2,3,4,5,6,7,9 ]
- 输出: 8
最快捷的解决方法是把数组的所有元素以及 0 到 n-1 之间的整数 全部进行异或运算
- arry[0] ^ arry[1] ^ arry[2] ... ^ arry[n-2] ^ 0 ^ 1 ^ 2 .... ^ (n-1)
由于数组元素值范围在 0 到 n-1,且所有元素值都没有重复
所以,上述的计算式子中,0 到 n-1 每个数会出现两次,只有缺少的那个数仅出现一次,根据异或的性质 x ^ x = 0 可知,相同值之间的异或运算等于 0,因此算式的结果其实就是缺少的那个数
下面给出测试文件 test.cpp,代码是用 C++ 实现的,可以自行用其他语言实现
- #include <stdint.h>
- #include <iostream>
- using namespace std;
- int32_t find_missing(int32_t *ary, int32_t len)
- {
- //数组长度小于等于1,直接返回 -1
- if(len <= 1)
- {
- return -1;
- }
- //结果
- int32_t result = 0;
- //result 和 数组中每一个元素值进行异或运算
- for (int32_t i = 0; i < len; i++)
- {
- result ^= ary[i];
- }
- //result 和 0 到 n 中每一个值进行异或运算
- for (int32_t j = 0; j <= len; j++)
- {
- result ^= j;
- }
- return result;
- }
- //编译: g++ -g -o test test.cpp
- int32_t main(int32_t argc , char *argv[])
- {
- int32_t ary[] = { 0, 1, 3 };
- int32_t result = find_missing(ary, sizeof(ary) / sizeof(int32_t));
- std::cout << "result = " << result << std::endl;
- return 0;
- }
使用 g++ -g -o test test.cpp 命令编译,接着运行程序,结果如下:
- [root@localhost test]# ./test
- result = 2
当然,这道题目还有其他的解法,比如:利用加法来解,大家自己去思考下,这里不做介绍了
题目链接 https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/
小结
通过上文,我们可以看到,异或运算在很多方面都有应用,其实,它的应用远不止文中介绍的方面,所以,我们花时间去掌握它是非常值得做的一件事