位运算
c语言位运算符
c语言提供了六个位运算符,进行二进制计算。这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型。
1.按位与运算(&)
(1). 运算规则
& | 0 | 1 |
0 | 0 | 0 |
1 | 0 | 1 |
(2). 用途 ①清零 将数a与一个各位是0的数按位与
例如 a&225 (225二进制数为0000 0000 1111 1111),则a的右八位清零,左八位保留
②取一个数指定位的数字 若要取a的x位,则令a 和一个x位为1,其余位为0的数按位与
例如 令a=1110 取a的第二位 则a&0100=0100
2.按位或运算符(|)
(1). 运算规则
| | 0 | 1 |
0 | 0 | 1 |
1 | 1 | 1 |
(2). 用途 将某位 置1 将a的x位变成1 ,则令a和一个x位为1,其余位为0的数按位或
例如 a=1010 0000 令a的左四位置1 则 a | 0000 1111 = 1010 1111
3.按位异或运算符(^)
(1). 运算规则
^ | 0 | 1 |
0 | 0 | 1 |
1 | 1 | 0 |
(2). 性质 ①交换律
②结合律 (a^b)^c=a^(b^c)
③ a^a=0 a^0=a
④自反性 a^b^b=a^0=a
(3). 用途 ①特定位翻转 将a的几位翻转,则令a和一个翻转位为1,其余位为0的数按位异或
例如 a=10101110,使a左四位翻转,用a ^0000 1111 = 1010 0001即可得到
②保留原值 将a与0异或,值不变
③ 交换两数(不需要中间变量temp) 利用自反性
将a与b交换,则 a=a^b; b=b^a; a=a^b;
(4). 例题
1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一 次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?
解法一、将所有数加起来,减去1+2+...+1000的和。(唯一的问题是,如果数列过大,则可能会导致溢出。)
解法二、将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数。
4.按位取反运算符(~)
运算规则 1变为0,0变为1 如 ~1010=0101
5.左移运算符(<<)
将一个二进制数左移若干位,高位左移溢出则舍弃,右边补0
每左移一位,相当于该数乘2(此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况)
6.无符号右移运算符(>>)
将一个二进制数右移若干位,低位舍弃,若是正数,高位补0,若是负数,取决于系统,补0或补1
每右移一位,相当于该数除以2
7.复合赋值运算符
&= 例:a &=b 相当于a=a& b
|= 例:a |=b 相当于a=a |b
>>= 例:a >>=b 相当于a=a>> b
<<= 例:a<<=b 相当于a=a<< b
^= 例:a ^= b 相当于a=a^ b
综合例题 &和^
Input
第一行一个整数T,代表数据的组数(1<=T<=10),接下来T组数据,每组数据的第一行是一个整数n(1<=n<=1000000),第二行是n个整数ai(0 <= ai <= 1000000000),每两个整数之间有一个空格,题目保证有且仅有两个不同的整数出现一次,其他的整数都是出现两次。
Output
对于每组数据,输出两个整数,分别代表两个不同的整数,中间有一个空格,并且喜悦感较小的先输出。
Sample Input
2
6
2 2 1 1 3 4
4
1 1 3 4
Sample Output
3 4
3 4
代码如下
重复利用自反性
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
long long a[1000005];
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
long long x=0,num1=0,num2=0,y=1;
for(int i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
x^=a[i]; //x为只出现一次的两个数的异或值①
}
while(!(y & x)) y <<= 1; //从x的最低位起找两个数第一次数不相同的位②
for(int i=1; i<=n; i++)
{
if(y & a[i]) num1 ^= a[i];
else num2 ^= a[i]; //再次利用自反性,其余数出现两次,为0,不产生影响③
}
printf("%lld %lld\n",min(num1,num2),max(num1,num2));
}
return 0;
}
①
}
while(!(y & x)) y <<= 1; //从x的最低位起找两个数第一次数不相同的位②
for(int i=1; i<=n; i++)
{
if(y & a[i]) num1 ^= a[i];
else num2 ^= a[i]; //再次利用自反性,其余数出现两次,为0,不产生影响③
}
printf("%lld %lld\n",min(num1,num2),max(num1,num2));
}
return 0;
}
解释代码
①将所有数异或,根据自反性,最后x的值为只出现一次的两个数(a,b)的异或值
②从最低位起求a,b第一次数不相同的位(可以先看③区分a,b的办法,可以更快理解这步的用意)
这里假设a=1001,b=0001 则x=1000,因为异或的运算规则,同为假,所以x为0的位代表该位a,b相同,
为1则代表该位a,b不同,即在这里,a,b从低位数起,第四位不同。
利用这个结论,从最低位起让x&1
while(!(y & x)) y <<= 1; y最初为1,即0001
x=1000 & 0001 最低位相同,结果为0,逻辑非后执行循环,y左移一位,即y=0010
接着 x 1000 & 0010 ,第二位相同,同样y左移一位,y=0100
再按位与,第三位也相同,y再左移一位,y=1000
此时 x 1000 & 1000,结果为1,退出循环,则找出从最低位数起第一次数不相同的位是第四位
③ 求出a,b数不相同的位后,再让所有数与y异或,该位结果无非是0或1,则可以将所有数区分到num1,num2
中,因为其余数出现两次,不管是0是1都会异或到num1或num2同一个数中,利用自反性,异或后,num1,
num2依然是0,而0不管异或几,都不变,所以就把a,b区分开了