复习一下位运算
位运算小结(按位与、按位或、按位异或、取反、左移、右移)
位运算不管是在Java语言,还是在C语言中,或者其他语言,都是经常会用到的,所以本文也就不固定以某种语言来举例子了,原始点就从0、1开始。位运算主要包括按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<)、右移(>>)这几种,其中除了取反(~)以外,其他的都是二目运算符,即要求运算符左右两侧均有一个运算量。
1、补码
在总结按位运算前,有必要先介绍下补码的知识,我们知道当将一个十进制正整数转换为二进制数的时候,只需要通过除2取余的方法即可,但是怎么将一个十进制的负整数转换为二进制数呢?其实,负数是以补码的形式表示,其转换方式,简单的一句话就是:先按正数转换,然后取反加1。
要将十进制的-10用二进制表示,先将10用二进制表示:
0000 0000 0000 1010
取反:
1111 1111 1111 0101
加1:
1111 1111 1111 0110
所以,-10的二进制表示就是:1111 1111 1111 0110
2、按位与(&)
参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当相应位上的数都是1时,该位才取1,否则该为0。
将10与-10进行按位与(&)运算:
0000 0000 0000 1010
1111 1111 1111 0110
-----------------------
0000 0000 0000 0010
所以:10 & -10 = 0000 0000 0000 0010
3、按位或(|)
参加运算的两个数,换算为二进制(0、1)后,进行或运算。只要相应位上存在1,那么该位就取1,均不为1,即为0。
将10与-10进行按位或(|)运算:
0000 0000 0000 1010
1111 1111 1111 0110
-----------------------
1111 1111 1111 1110
所以:10 | -10 = 1111 1111 1111 1110
4、按位异或(^)
参加运算的两个数,换算为二进制(0、1)后,进行异或运算。只有当相应位上的数字不相同时,该为才取1,若相同,即为0。
将10与-10进行按位异或(^)运算:
0000 0000 0000 1010
1111 1111 1111 0110
-----------------------
1111 1111 1111 1100
所以:10 ^ -10 = 1111 1111 1111 1100
可以看出,任何数与0异或,结果都是其本身。利用异或还可以实现一个很好的交换算法,用于交换两个数,算法如下:
a = a ^ b;
b = b ^ a;
a = a ^ b;
5、取反(~)
参加运算的两个数,换算为二进制(0、1)后,进行取反运算。每个位上都取相反值,1变成0,0变成1。
对10进行取反(~)运算:
0000 0000 0000 1010
---------------------
1111 1111 1111 0101
所以:~10 = 1111 1111 1111 0101
6、左移(<<)
参加运算的两个数,换算为二进制(0、1)后,进行左移运算,用来将一个数各二进制位全部向左移动若干位。就相当于在右边加2个0。
对10左移2位(就相当于在右边加2个0):
0000 0000 0000 1010
--------------------
0000 0000 0010 1000
所以:10 << 2 = 0000 0000 0010 1000 = 40
注意,观察可以发现,左移一位的结果就是原值乘2,左移两位的结果就是原值乘4。
7、右移(>>)
参加运算的两个数,换算为二进制(0、1)后,进行右移运算,用来将一个数各二进制位全部向右移动若干位。(就相当于在左边加2个0)。
对10右移2位(就相当于在左边加2个0):
0000 0000 0000 1010
--------------------
0000 0000 0000 0010
所以:10 >> 2 = 0000 0000 0000 0010 = 2
注意,观察可以发现,右移一位的结果就是原值除2,左移两位的结果就是原值除4,注意哦,除了以后没有小数位的,都是取整。
为什么python中不在意负数呢??
# 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
class Solution:
def NumberOf1(self, n):
res = 0
for i in range(32):
res+=(n&1)
n = n>>1
return res
# 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
# 位运算
# -*- coding:utf-8 -*-
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# 2^3^3^4^4=2 只出现一次的数,只要异或一遍
sum = 0 # x^y
for x in array:
sum^=x
k = 0 # x^y只要有一位,第k位是1,不等于0,否则会异或得0
# 将第k位移到个位,&1,即可看出第k位是0还是1
while not (sum>>k&1):k+=1
# k存x^y中是1的那一位
first = 0
for x in array:
if x>>k&1: # x的第k位是1的疑惑和
first^=x
return [first,sum^first]
# -*- coding:utf-8 -*-
class Solution:
def Add(self, num1, num2):
while num2:
result = (num1 ^ num2) & 0xffffffff # A与B在无进位时的总和
carry = ((num1 & num2) << 1) & 0xffffffff # 进位的总和
num1 = result
num2 = carry # num1+num2=sum+carry 加法具有交换律
if num1 <= 0x7fffffff: # 正数
result = num1
else: # 负数
result = ~(num1^0xffffffff)
return result
# 其他语言循环最多执行32次,python不是,所以才有这些十六进制
关于0xfffff这些东西,参考剑指offer-[编程题]不用加减乘除做加法(python2实现及解析)
0xFFFFFFFF代表16进制下的边界 (按二进制表示的话,对应4*8=32位)
正负数判断及还原
- 正数与边界数 按位与(&) 操作后 仍得到这个数本身:
- 负数与边界数 按位与(&) 操作后 得到的是对应二进制数的真值:
为了便于理解,以一个小边界为例:
241 对应的二进制数为: 11110001 (按位取反0001110就是15)
答案中,通过查看符号位(最高位,即与0x7FFFFFF比较大小)判断a为正数还是负数,正数则直接返回。负数则返回~(a^0xFFFFFFFF)。 (注: ~ 表示按位取反)
此处操作最终返回的是原来负数的值:
11110001^0xFFFFFFFF -----> 00001110
~(00001110) -----> 11110001(补码) ,对应的十进制数是 -15
此处有一个规律:~n = -(n+1)
参考: