位运算提升效率

原文出自知乎LeetCode

1. 位操作符

  • & 运算符:全1为1,反之为0.
  • | 运算符:全0为0,反之为1;只要一个位为1,结果为1.
  • ^ 异或运算符:两个位相同则为 0,不同则为 1
  • ~ 取反运算符:0 则变为 1,1 则变为 0
  • << 左移运算符:向左进行移位操作,高位丢弃,低位补 0
  • >> 右移运算符:向右进行移位操作,对无符号数,高位补 0,对于有符号数,高位补符号位

2. 常见位运算

很多功能采用基本的运算也可以完成,但是位运算的效率更高,因为省去了将int、Integer 等整数转化为计算机可以直接识别的二进制的过程

2.1 乘除法

1
2
3
4
// 数 a 向右移一位,相当于将 a 除以 2;数 a 向左移一位,相当于将 a 乘以 2
int a = 2;
a >> 1; // a = 1
a << 1; // a = 4

2.2 交换两个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 位操作交换两数可以不需要第三个临时变量,虽然普通操作也可以做到,但位运算效率更高
// 普通操作
void swap(int &a, int &b) {
a = a + b;
b = a - b;
a = a - b;
}

// 位与操作
void swap(int &a, int &b) {
a ^= b; // a ^= b ---> a = (a^b);
b ^= a; // b = b^(a^b) ---> b = (b^b)^a = a
a ^= b; // a = (a^b)^a = (a^a)^b = b
}

2.3 判断奇偶数

1
2
3
4
// 在计算机中, 偶数的最后一位是0, 而奇数的最后一位是1
public boolean isEven(int num) {
return (num & 1) == 0;
}

2.4 交换符号

1
2
3
4
5
6
// 将正数变成负数,负数变成正数
// 整数取反加1,正好变成其对应的负数(补码表示)
// 负数取反加1,则变为其原码,即正数
public int reverse(int num) {
return ~a + 1;
}

2.5 求绝对值

1
2
3
4
5
6
7
8
9
10
11
12
// 整数的绝对值是其本身,负数的绝对值正好可以对其进行取反加一求得,即我们首先判断其符号位(整数右移 31 位得到 0,负数右移 31 位得到 -1,即 0xffffffff),然后根据符号进行相应的操作
int abs(int num) {
int i = num >> 31;
return i == 0 ? num : (~num + 1);
}

// 上面的操作可以进行优化,可以将 i == 0 的条件判断语句去掉。我们都知道符号位 i 只有两种情况,即 i = 0 为正,i = -1 为负。对于任何数与 0 异或都会保持不变,与 -1 即 0xffffffff 进行异或就相当于对此数进行取反,因此可以将上面三目元算符转换为((a^i)-i),即整数时 a 与 0 异或得到本身,再减去 0,负数时与 0xffffffff 异或将 a 进行取反,然后在加上 1,即减去 i(i =-1)

int abs(int num) {
int i = num >> 31;
return ((num^i) - i);
}

2.6 高低位交换

1
2
3
4
5
6
7
8
9
10
11
12
// 给定一个 16 位的无符号整数,将其高 8 位与低 8 位进行交换,求出交换后的值
// 34520的二进制表示:
// 10000110 11011000

// 将其高8位与低8位进行交换,得到一个新的二进制数:
// 11011000 10000110
// 其十进制为55430

// 从上面移位操作我们可以知道,只要将无符号数 a>>8 即可得到其高 8 位移到低 8 位,高位补 0;将 a<<8 即可将 低 8 位移到高 8 位,低 8 位补 0,然后将 a>>8 和 a<<8 进行或操作既可求得交换后的结果。

unsigned short a = 34520;
a = (a >> 8) | (a << 8);

评论