位运算符的用法——非、与、或、异或

本文深入解析位运算符的计算原则及应用,包括非、与、或、异或四种运算符的功能与特性,以及如何利用它们修改位模式。通过具体实例展示如何实现数值交换、判断奇偶性、查找重复数字等高级算法技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

非!、与&、或|、异或^计算原则

都是针对二进制的位运算符
非!:是一元运算符。对一个二进制的整数按位取反,输入0则输出1,输入1则输出0。
如输入二进制数:

		0100	-(4) 		输入
		1011	-(11)		输出

与&:二元运算符,两个输入。按位计算。输入都是1则输出1;否则输出0;(一0得0)

		0100	-(4)	输入1
		1011	-(11)	输入2
	 &	0000 	-(0)	输出

或|、:二元运算符,两个输入,按位比较。当有1时输出为1否则为零(一1得1)

	0100	输入1
	1011	输入2
|	1111	输出

异或^ :
异或是二元运算符。并不是独立存在的位运算符,而是组合而成。可以用其他的运算符等价表示(相同为0,不同为1)
异或运算按位比较,当输入的两个位相同时输出0,不相同时输出;

	0100	
	1011
^	1111

特性:当一个输入为1时,其对应的输出与另一个输入必相反

应用:
4种位运算符都可以用来修改位模式;
非 ! :用来求反。对一个数进行非运算即可
与 & :对所需位进行置0;
根据与运算的特性(一0得0),只需要在想要的置0的位上与上一个0即可确保为0;其他为不变;

例:将10101101左5位置0
10101101
00000111	&
00000101

或 | :同"与"类似对要改变的为置1,(一1得1)

例:将10100110左5位置1
	10100110
|	11111000
	11111111

异或 ^ :
对指定的位进行反转(输入1时对应的输出必与另一个输出相反)

	例:将10101101左5位反转
		10101101
		11111000	^
		0101101

异或的其他小用途:
关于异或有很多小技巧在一些高级算法中时常使用异或以使算法更加高效节省空间。
关于异或的简单理解:
不进位的加法:1+1=0;1+0=1;0+0=0;
异或的特性:
1、交换律 可任意交换运算因子的位置,结果不变

如:a^ b==b^a

2、结合律(即(a ^ b) ^ c == a^ (b^c))

3、对于任何数x,都有x^ x=0,x^0=x,同自己求异或为0,同0求异或为自己

4、自反性 A ^ B ^ B = A ^ 0 = A ,连续和同一个因子做异或运算,最终结果为自己
5、一个数同自己进行偶数次异或运算结果为0、
一个数同自己进行奇数次异或运算结果为本身。

运用1:交换数字的值

a=4,b=5;

使用异或可以不引入第三个变量而交换ab的值

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

解读:①a=a^b;②b=a ^b可以连接看相当于b=a ^b ^b根据特性5b ^b为0而任何数与0进行异或运算结果都为本身。所以b=a;同理③中a=a ^b ^a;结果为b

a	0100
b	0101
^_______
	1001

使用异或交换数值主要是依靠数字连续欧数次异或自身会干掉自己得0。而任何数与0异或都是本身。

运用2:判断奇偶性

不算是异或的特性,与运算同样可以判断一个数是否为偶数
如果是偶数的话,最低位一定为0;
判断:

num^1==1?偶数:奇数
num&1==0?偶数:奇数	

运用3:找出唯一重复的数字

在一个数组中包含1-1000的所有共1001个元素,其中仅有一个数字重复,设计算法找出来
算法1:
可以将所有的数字加起来再减去1-1000的和结果就是重复的数字
比较麻烦,而且如果数字的范围再大一点的话很可能会溢出
算法2:
使用异或不用担心溢出的问题,而且性能还更加优秀
根据异或的特性,所有的数字同自己进行异或会为0。然后数列的结合律
可以先将1-1000的所有数字进行连续异或即:

1^2^3^4^5.......^1000的数值与数组中的所有数值连续异或的值进行异或即是:1^2^3.....^k......^1000
根据结合律二者相异可等于
1^1^2^2^3^3...........^999^999^1000^1000

这样所有不重复的数字都被本身干掉,只有重复的数字因为有三个相同的数相互异或所以会得到它本身。当所有的数都成对时。就只有它会凸显出来

变形:
google面试题的变形:一个数组存放若干整数,一个数出现奇数次,其余数均出现偶数次,找出这个出现奇数次的数?

方法相同:因为其他数均出现偶数次只要将数组中所有的数连续异或则出现奇数次的数会被凸显:

result=a[0]^a[1]^a[2].............a^[i]
原因是一个数字同自己进行奇数次异或得到自己本身。进行偶数次异或得到0,而任何数与0异或得到本身

再变形:找出唯一落单的数字

一个数组中所有的元素都成对出现,仅有一个数字是单个出现的求出这个数字
道理相同,将数组中的所有元素进行连续异或,异或的结果即为所求数;
难点:

突然变难,找出数组中落单的两个数

如果落单的是两个数,则将所有元素进行连续异或的值是他们两个落单的数异或的结果而怎样分解出这两个数呢?
第一因为进行连续异或后还有落单的两个元素则证明他们二者不同。否则的话就会因为异或而被干掉;那么对于不同的两个数则必然会有某一位不同。根据这个特性将数组中的所有元素根据第N位是否为0进行分组。从最低位开始进行逐位增高。则两个数组假定为S1\S2则在这两个落单的数必然会分别落在这两个数组中。然后再将这两个数组分别进行连续异或。一个数组中连续异或的值既是所得的结果。
伪代码:
xorOfResult = a[1]a[2]…a[n]

N = 0

while(true){

if(xorOfResult & (1<<N++) == 1)

break

}

/*这个得出N位为整数的第一个非0(即1)位的方法很巧妙:和1做按位与运算,如果位上为1结果为1,N求出;

如果位上为0则结果为0,将1移动到下一位继续判断*/

N-- // 循环最后多加了1

s1 = new Array(),s2 = new Array()

for i=1 to n

if(a[i] & (1<<N) == 0)

s1.push(a[i])

else

s2.push(a[i])

define result1 = 0,result2 = 0

for e_s1 in s1

result1 ^= e_s1

for e_s2 in s2

result2 ^= e_s2

代码还可以微调,不使用额外的数组作为存储空间:

…上面求出了N

define result1 = 0,result2 = 0

for i=1 to n

if(a[i] & (1<<N) == 0)

result1 ^= a[i]

else

result2 ^= a[i]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值