异或(^)的含义与基本用法
异或的含义
异或(^)和与运算(&)、或运算(|)都是位运算,因为计算机的运算都是将数据转换成二进制来进行的,所以一般来说,位运算比加减乘除的算数运算快得多。异或的运算法则如下:
1 ^ 0 == 1
1 ^ 1 == 0
0 ^ 0 == 0
即相同为0,不同为1。
异或的运算性质
异或有几大运算性质:
-
交换律: ab==ba
-
结合律: aba==aab
-
任何非0的数和0异或的结果都是它本身
-
任何数和自己本身异或的结果都为0
如果你看到这里,感觉上面列的运算法则和性质有所冲突的时候,请考虑进制不同的情况。本质上异或是在二进制层面上的,这是它的运算法则,当直观表现在十进制上的,才是它的运算性质。
在二进制层面上,异或的运算可以看成是无进位相加,举个例子
1010110
1100011
^ 0110101
相加的两个二进制数,从右往左数第二位上都是1,1+1本来应该进位1,本位变成0的,然后^运算,无进位,所以结果直接写成0。这样的理解在后续的运用中能更加形象的帮我们解释一些结果。
就此,异或的运算性质也概括完了,接下来让我们看看它的运用情况。
异或的运用
在需要交换两个变量的值时,你是否马上想到再设置一个中间变量来进行交换?那么如果我跟你说不用中间变量,两者可以直接交换呢?下面我们就用异或来实现
void swap(int a,int b)
{
a=a^b;
b=a^b;
a=a^b;
}
这样三个格式一样的语句就完成了值的交换,是不是觉得很不可思议?下面我们来分析一下,加深对异或的理解。
在第一条语句结束后,分析一下a,b的值:a=a^b b=b;
在第二条语句结束后,分析一下:a不变,a=a^b 分析b我们先交换律,再结合律:b=ba=b(a^b);
这里我们对b的值进一步化简,因为性质——任何数跟它自己异或的结果都为0,所以b=a^0=a
最后分析第三条语句,a=ab=(ab)^a,同理,a=b;
就此,我们完成了a,b值的交换。
但需要注意的是,这种方法其实是抖机灵的做法,它的可读性很差。同时使用它有一个隐藏的限制条件,即交换的两值在内存空间中并不能属于同一区域,如排序数组中元素的值,因为当它们为同一性质的东西,即两者完全相同时,使用异或将会使它们都归零。
下面我们来看两个有关异或的实际问题:
Q1:在一堆数中,只有一种数,它出现了奇数次,而其他种的数,都出现了偶数次。请找出出现了奇数次的这种数。
直接上代码:
int arr[20];
int i,a;
for(i=0;i<20;i++)
{
a^=arr[i];
}
//a就是出现奇数次的数
为什么这么说?结合两点,第一点,异或的性质,任何数和它本身异或的结果都是0;第二点,根据题意,除了需要找的这个数以外,其他数都能找到和它自己相同的数两两配对变成0。最后可以化简成该数^0,答案浮出水面。
Q2:在一堆数中,有且仅有两种数,出现了奇数次,而其他种的数,都出现了偶数次,请找出出现了奇数次的这两种数。
上代码:
int main()
{
int a=0,b=0;//设出现奇数次的数为a,b
int t=0,i;
int rightone;//二进制下,从右往左的第一个1
int arr[20];
for( i=0;i<20;i++)
{
t^=arr[i];
}
//现在的t==a^b,这两个出现奇数次的数异或
//因为t==a^b;
//a!=b
//所以t的二进制肯定有一位上是1
rightone=t&(~t+1);
//一个数与(&)上自己取反+1的数,结果得到这个数二进制下最右侧的1
//并且我们知道,a和b两者在这一位上,只有一者位上是1,另一个一定是0.
for(i=0;i<20;i++)
{
if(t^arr[i]==0)//将该最右位是1和0的区分开
{
a^=arr[i];//此时a等于一种奇数次数^一些出现过偶数次数且该位也是1的数
//而出现偶数次可以抵消,所以直接得到了第一个出现了奇数次的数
}
}
b=t^a;//由此再得到b
}
以上有两个难点,第一是得到一个数二进制下最右侧的1所在的位置,为该数&(~该数+1),即原码&补码,不明白的用几个二进制数试验即可,记住以后多可以拿来套用。
第二个是当我们用一个变量t异或了一轮数组后,得到了a^b,之后我们的第二轮异或,是有选择性的异或。如此便区分出了以下情况:
一侧 | 另一侧 |
---|---|
出现过奇数次的a | 出现过奇数次的b |
出现偶数次且最右位为1的数 | 出现偶数次且最右位为0的数 |
第二轮异或的精髓就在于将a,b分开了,而其他数都是出现过偶数次,根据自己^自己==0,全部抵消,所以最后就得到了单独的a和b。
感谢阅览,希望对你有帮助。