C# 异或运算原理及加密、交换、备份等应用

异或,英文为exclusive OR,缩写成xor。异或(xor)是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:

a⊕b = (¬a ∧ b) ∨ (a ∧¬b)(¬为非)

异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1表示真,0表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法。
运算法则:

    1. a ^ a = 0
    2. a ^0 = a
    3. a^ b = b ^a
    4. a ^b ^ c = a ^ (b ^ c) = (a^ b) ^ c;
    5. d = a ^b ^ c 可以推出 a = d ^ b ^ c.
    6. a^ b ^ a = b.(可由3推出)

按位异或的几个常见用途:

c语言中使用^来表示异或运算(很多语言^表示乘方)

我们先来计算一下这个问题,5^3=?

5和3转为二进制分别为:0101 、0011,所以0101^0011=0110,换为十进制即为6。

6^3=0110^0011=0101,化为10进制即为5,也验证了上面第4条运算法则

常见应用

(1)判断两个数是否相等

一个数与自身异或,总是为 0,我们可以使用这一点来判断两个变量是否相等

( a ^ b ) == 0

ab相等时,表达式为真,否则为假

(2)不使用临时变量交换两个数的值

要交换两个数的值,通常做法是借助一个临时变量,然后再进行交换,比如:tmp是临时变量,现需要交换 ab两个变量值,过程如下

tmp = a
a = b
b = tmp;

利用异或的一些性质,不用临时变量也能实现交换两个数的值,具体过程如下

a  =  a  ^  b
b  =  a  ^  b
a  =  a  ^  b


(3) 使某些特定的位翻转

    例如对数10100001的第2位和第3位翻转,则可以将该数与00000110进行按位异或运算。

    10100001^00000110 = 10100111

  (4)  实现两个值的交换,而不必使用临时变量。

    例如交换两个整数a=10100001,b=00000110的值,可通过下列语句实现:

    a = a^b;   //a=10100111

    b = b^a;   //b=10100001

    a = a^b;   //a=00000110

   (5)简化表达式

根据交换性,可以优化表达式中重复变量的异或运算,比如:表达式 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

(6)加密

利用异或运算加密是很常见的加密手段,它涉及到三个变量:明文、密钥、密文,假如它们分别记为 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

异或(XOR)运算加密/解密算法原理:

  从加密的主要方法看,换位法过于简单,特别是对于数据量少的情况很容易由密文猜出明文,而替换法不失为一种行之有效的简易算法。

  从各种替换法运算的特点看,异或运算最适合用于简易加解密运算,这种方法的原理是:当一个数A和另一个数B进行异或运算会生成另一个数C,如果再将C和B进行异或运算则C又会还原为A。

 

  相对于其他的简易加密算法,XOR算法的优点如下。

  (1)算法简单,对于高级语言很容易能实现。

  (2)速度快,可以在任何时候、任何地方使用。

  (3)对任何字符都是有效的,不像有些简易加密算法,只对西文字符有效,对中文加密后再解密无法还原为原来的字符。

如果密钥是随机的(不重复),而且与消息长度相同,异或密码就会更为安全。当密钥流由伪随机数发生器生成时,结果就是流密码。若密钥是真正随机的,结果就是一次性密码本,这种密码在理论上是不可破解的。

在这些密码的任何部分中,密钥运算符在已知明文攻击下都是脆弱的,这是因为明文

(7)备份

根据异或的性质,异或运算还可以用于文件的备份

现有两个文件 fileafileb,它们进行异或运算,会产生一个新的备份文件 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文件损坏了,可以通过 fileabakfile进行异或运算得到完好的 fileb文件

应用举例


(1)最后举一个小例子,leetcode上的习题:

    给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。

    例如:输入: [3,0,1] 输出: 2

               输入: [9,6,4,2,3,5,7,0,1] 输出: 8

这道题方法较多,比如们可以求出 [0..n]的和,减去数组中所有数的和,就得到了缺失的数字。

        int missingNumber(vector<int>& nums) {
            int n=nums.size();
            int sum=n*(n+1)/2;
            for(int i=0;i<n;i++)sum-=nums[i];
            return sum;
        }

我们还可以使用位运算的方法,由于异或运算(XOR)满足结合律,并且对一个数进行两次完全相同的异或运算会得到原来的数,因此我们可以通过异或运算找到缺失的数字。

我们知道数组中有 n 个数,并且缺失的数在 [0..n]中。因此我们可以先得到 [0..n] 的异或值,再将结果对数组中的每一个数进行一次异或运算。未缺失的数在 [0..n] 和数组中各出现一次,因此异或后得到 0。而缺失的数字只在 [0..n]中出现了一次,在数组中没有出现,因此最终的异或结果即为这个缺失的数字。代码如下:

最快捷的解决方法是把数组的所有元素以及 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,因此算式的结果其实就是缺少的那个数
 

(2)

1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现
一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空
间,能否设计一个算法实现?

解法二(异或)

异或就没有这个问题,并且性能更好。
将所有的数全部异或,得到的结果 与 1 ^ 2 ^ 3 ^ … ^ 1000的结果进行异或,得到的结果就是重复数。

但是这个算法虽然很简单,但证明起来并不是一件容易的事情。这与异或运算的几个特性有关系。

证明

首先是异或运算满足交换律、结合律。
所以,1^2^...^n^...^n^...^1000,无论这两个n出现在什么位置,都可以转换成为1^2^...^1000^(n^n)的形式。

其次,对于任何数x,都有x^x=0,x^0=x。
所以1^2^...^n^...^n^...^1000 = 1^2^...^1000^(n^n)= 1^2^...^1000^0 = 1^2^...^1000(即序列中除了n的所有数的异或)。

令,1^2^...^1000(序列中不包含n)的结果为T
则1^2^...^1000(序列中包含n)的结果就是T^n。
T^(T^n)=n。

所以,将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数。

 

当然有人会说,1+2+…+1000的结果有高斯定律可以快速计算,但实际上1 ^ 2 ^ … ^ 1000的结果也是有规律的,算法比高斯定律还该简单的多。

posted @ 2022-07-02 23:08  小林野夫  阅读(2870)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/