Xor运算是位运算的一种,和And、Or运算类似,假如a、b都是布尔变量,则a Xor b被定义为:
- 1
其真值表为:
- 1
- 2
- 3
- 4
众所周知,位运算也可以用于两个数之间,其定义就是把这两个数转化为二进制,然后一位一位的进行位运算。比如说1 Xor 4 = (001) Xor (100) = (101) = 5。位运算除了具有交换律、结合律这样的普通性质之外,还有几条神奇的性质。
1 交换律
- 1
2 结合律
- 1
3 自己是自己的逆运算
- 1
由于xor满足交换律,所以上述特性这样表述也是对的:
- 1
- 2
实例1:swap
大家应该都知道swap可以这么做:
- 1
- 2
- 3
- 4
- 5
现在我们知道了Xor运算是本身的逆运算之后,就可以把上面的函数改成这个样子:
- 1
- 2
- 3
- 4
- 5
乍一看肯定会觉得这个交换函数写的非常诡异,但是仔细一看就知道其原理和刚才那个是一模一样的。而且因为计算机在执行位运算的时候肯定比加减法要快,所以用Xor写的交换函数实际上还更快呢。
实例2:找出单独的数
这里有一个有意思的小问题:现在给你2n+1个正整数,其中有n对数和1个单独的数,(这里规定一对数的意思是这两个数相等),然后让你设计一种算法,把这个单独的数给找出来,要求时间复杂度为O(n)。比如说这2n+1个数是1 2 3 2 1,那么这个单独的数就是3。如果你的思路是依次挑出一个数然后和其余所有数比较一下看看是否相等,那就换个思路吧,因为这样的时间复杂度是O(n2)的。
答案::根据Xor运算是本身的逆运算的性质,只要把所有数都Xor起来就可以了!比如说,1Xor2Xor3Xor2Xor1就一定是3了。就这么简单!
实例3:硬盘恢复
由这条性质还可以干一件很有意义的事情:当硬盘的一个部分损坏之后可以推算出来损坏部分数据!假定我们的硬盘划分成了4个区域,前三个区域用来存放真正的数据,而第四个部分则用来以防不时之需,这上面的数据定义为前三个部分的数据异或之后的结果。举个例子:假如说abc三个部分存放的数据如下:
a: 1 0 0 1
b: 0 1 1 1
c: 1 0 1 0
则第四部分根据定义便是
d: 0 1 0 0
现在假如说系统检测到硬盘的第一部分损坏了,我们就可以利用现成的数据把它给恢复出来,只需要把现有的未损坏的几个部分都异或起来就可以了!因为:a xor b xor c=d → a xor a xor b xor c xor d xor d=d xor d xor a → b xor c xor d=a!就这样,Xor运算应用在了乍一看完全不相干的地方。当然,硬盘分的部分更多一点也不影响这个结论的正确性。
4 消去率
- 1
很好证明:
- 1
- 2
- 3
- 4
- 5
- 6
这一点是And、Or运算都不能满足的,是加法减法拥有的性质。有了这样一条性质是很有用的,比如说证明Nim游戏的必胜策略就需要用到,下面我们进入Nim游戏必胜策略的介绍和证明。
因为3堆硬币的情况和N堆的策略是一样的,我就直接拿N堆说事。设这N堆硬币的数量分别为a1,a2,…,an。因为总是打Xor太麻烦,下面我就用C++的习惯用^来代替Xor。
要知道,像Nim游戏这种博弈问题,最重要的是寻找必败态。这个必败态的的意思就是,这样一种局面摆在面前的话先手必败。其严格定义如下:1.无法进行任何移动的局面是必败态;2.可以移动到必败态的局面是非必败态;3.在必败态做的所有操作的结果都是非必败态。这个还是很好理解的吧,就是自己处在非必败态上总能移动到必败态把必败态留给对方,而对方处在必败态的话总是只能移动到非必败态,把非必败态留给自己,然后自己继续虐对方。
而对于Nim游戏,局面是必败态当且仅当所有堆硬币的数量都异或起来结果为0,即a1^a2^…^an=0!!!为了证明之,我们只要证明它满足上述必败态的三条性质即可。
第一个命题显然,最终局面只有一个,就是全0,异或仍然是0。
第二个命题,对于某个局面(a1,a2,…,an),若a1^a2^…^an!=0(不等号就用C++的习惯用!=来表示了),一定存在某个合法的移动,将ai改变成ai’后满足a1^a2^…^ai’^…^an=0。不妨设a1^a2^…^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k