位运算相关基础知识
最近做leetcode关于位运算的题,将相关知识总结如下:
1. 基础运算
-
与运算
将这2个数的二进制进行与操作, 只有当2个数对应的位都为1,该位运算结果为1,否则运算结果为0。即:1&1=1;1&0=0;0&0=0
-
或运算
将这2个数的二进制进行或操作, 只要2个数对应的位有一个为1,该位运算结果为1,否则运算结果为0。即:1|1=1;1|0=1;0|0=0
-
取反
就是将一个整数中位为1的变成0,位为0的变成1。即:1=0;0=1
-
异或
将这2个数的二进制进行异或操作, 只要2个数对应的位相同,该位运算结果为0,否则运算结果为1。即:11=0;10=1;0^0=0
-
左移
将一个数左移N位相当于将一个数乘以2^N
将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0
将12向左移动2位如何计算呢?12的二进制为00001100,那么左移动2位为:00110000
-
右移
将一个数右移N位相当于将一个数除以2^N
将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃
将一个数a向右移动n位记为:a>>n。比如将12向右移动2位如何计算呢?12的二进制为00001100,那么右移动2位为:00000011,即3。 即12>>2为3
-
无符号右移
不管正负标志位为0还是1,将该数的二进制码整体右移,左边部分总是以0填充,右边部分舍弃
-5用二进制表示1111 1011,红色为该数标志位
-5>>2: 1111 1011-------------->11 1 11110。
11为标志位
-5>>>2: 1111 1011-------------->00 1 11110。
00为补充的0
-
总结基础运算
- 按位与&:两位全为1,结果为1
按位 - 或|:两位有一个为1,结果为1
按位 - 异或^:两位一个为0,一个为1,结果为1
- 按位取反:0->1,1->0
- << 左移运算,向左进行移位操作,高位丢弃,低位补 0,如
int a = 8; a << 3; 移位前:0000 0000 0000 0000 0000 0000 0000 1000 移位后:0000 0000 0000 0000 0000 0000 0100 0000
- >>右移运算,向右进行移位操作,对无符号数,高位补 0,对于有符号数,高位补符号位,如
unsigned int a = 8; a >> 3; 移位前:0000 0000 0000 0000 0000 0000 0000 1000 移位后:0000 0000 0000 0000 0000 0000 0000 0001 int a = -8; a >> 3; 移位前:1111 1111 1111 1111 1111 1111 1111 1000 移位前:1111 1111 1111 1111 1111 1111 1111 1111
- 按位与&:两位全为1,结果为1
2. 一些操作
-
取最右边的第一个1(与)
-
a & (a – 1)
-
异或性质
a^0=a
a^a=0 -
翻转指定位(异或)
比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。
-
消除最低位(取反)
使a的最低位为0,可以表示为:a & 1。1的值为 1111 1111 1111 1110,再按"与"运算,最低位一定为0
-
如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算
-
位运算实现乘除法
数 a 向右移一位,相当于将 a 除以 2;数 a 向左移一位,相当于将 a 乘以 2
int a = 2; a >> 1; ---> 1 a << 1; ---> 4
-
位运算交换(注意a和b不能一样)
位操作交换两数可以不需要第三个临时变量,虽然普通操作也可以做到,但是没有其效率高
//普通操作 void swap(int &a, int &b) { a = a + b; b = a - b; a = a - b; } //位与操作 void swap(int &a, int &b) { a ^= b; b ^= a; a ^= b; }
-
判断奇偶数
//偶数 0 == (a & 1)
-
交换符号
交换符号将正数变成负数,负数变成正数
int reversal(int a) { return ~a + 1; }
整数取反加1,正好变成其对应的负数(补码表示);负数取反加一,则变为其原码,即正数
-
位操作求绝对值
整数的绝对值是其本身,负数的绝对值正好可以对其进行取反加一求得,即我们首先判断其符号位(整数右移 31 位得到 0,负数右移 31 位得到 -1,即 0xffffffff),然后根据符号进行相应的操作。
int abs(int a) { int i = a >> 31; return i == 0 ? a : (~a + 1); } ```
-
位操作高低位交换
给定一个 16 位的无符号整数,将其高 8 位与低 8 位进行交换,求出交换后的值,如:
34520的二进制表示: 10000110 11011000 将其高8位与低8位进行交换,得到一个新的二进制数: 11011000 10000110 其十进制为55430
unsigned short a = 34520; a = (a >> 8) | (a << 8);
3. 题目(技巧)
-
x & (x - 1) 用于消去x最后一位的1
-
用 O(1) 时间检测整数 n 是否是 2 的幂次
N如果是2的幂次,则N满足两个条件:
1.N >0
2.N的二进制表示中只有一个1
因为N的二进制表示中只有一个1,所以使用N & (N - 1)将N唯一的一个1消去,应该返回0
-
计算在一个 32 位的整数的二进制表式,有多少个1
不断使用 x & (x - 1) 消去x最后一位的1,计算总共消去了多少次即可
-
如果要将整数A转换为B,需要改变多少个bit位
将A和B进行异或,相同位为1,不同位为0,则转成了上题
-
-
使用二进制进行子集枚举
-
给定一个含不同整数的集合,返回其所有的子集
思路就是使用一个正整数二进制表示的第i位是1还是0,代表集合的第i个数取或者不取
所以从0到2n-1总共2n个整数,正好对应集合的2^n个子集
S = {1,2,3} N bit Combination 0 000 {} 1 001 {1} 2 010 {2} 3 011 {1,2} 4 100 {3} 5 101 {1,3} 6 110 {2,3} 7 111 {1,2,3}
-
-
a ^ b ^ b = a
-
只有一个数出现一次,剩下都出现两次,找出出现一次的数
因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或起来,就可以得到唯一的那个数
-
只有一个数出现一次,剩下都出现三次,找出出现一次的
方法一:状态机
方法二:循环(计算模3结果,或等在一起)
-
只有两个数出现一次,剩下都出现两次,找出出现一次的
因为a!=b!=0,所以a和b必然至少有一位不一样,不妨设最右边的1不一样,按此规则进行分组,分别异或,由于其他数出现两次,就会被异或掉,分别只剩下a和b
-
-
异或表示无进位加法,加法可表示为无进位加法+进位
-
不用加减乘除做加法
无进位加法:异或 a
进位:与运算后左移1 b
(和 s )=(非进位和 n )+(进位 c)
-