《简记位运算》
位运算的重要程度从各种课本上就能感受到,毕竟计算机只认识二进制,在工作当中位运算也是能够提高效率的利器。比如汉明距离就用到了异或运算以及查找位1的个数相关的位运算,如果不了解这些而单纯使用循环的话,那效率就大打折扣了。还有可以进行32以内的元素进行单个存储。以及用 x & 1 来判断奇偶逼格就高多了。
Key Words:位运算、与、或、异或、取反、位移运算
Beijing, 2020
作者:RaySue
Code:
文章目录
概念
-
计算机中所有的数据二进制的形式存储在设备中,即0、1两种状态。
-
计算机对二进制数据进行的运算(+、-、*、/)都是叫位运算,即将符号位共同参与运算的运算。
-
我们每一种语言最终都会通过编译器转换成机器语言(二进制)来执行。
-
直接使用底层的语言就不需要编译器的转换工作从而得到更高的执行效率,当然可读性可能会降低,这也是为什么汇编在大部分情况下有更快的速度。
-
项目中合理的运用位运算能提高我们代码的执行效率。
INT最大值和最小值
计算机内存中都是以二进制形式存储的。位运算就是直接对整数在内存中的二进制位进行操作。32位和64位的计算机是表示计算可以处理的最大位数。拿int类型来说,int类型用4个字节表示,1bytes=8bit,所以int=32bit,如下:
0000 0000 0000 0000 0000 0000 0000 0000
第一位即是符号位也是数值位, 2 31 2^{31} 231是32位,所以int的最大值是 2 31 − 1 2^{31} - 1 231−1,最小值是 − 2 31 -2^{31} −231
-
整型最大值 2 31 − 1 2^{31} - 1 231−1
0111 1111 1111 1111 1111 1111 1111 1111
-
整型最小值 − 2 31 -2^{31} −231
1000 0000 0000 0000 0000 0000 0000 0000
位运算的运算符号
由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理方式非常快,位运算符号如下:
& | ^ ~ >> <<
先上段代码感受下位运算的神奇,解析一下,下面的代码就用到了位运算中的 异或运算 ,其运算机理是同0异1,所以很多满足以下性质:
a^a=0
a^b=b^a
a^0=a
a^a^b=b^0=b
利用这些性质,我们就可以很轻松的交换两个变量的值了,注意要先判断这两个变量是否相等,如果相等再做异或运算结果就只剩下0了。
void swap(int& a,int& b)
{
if(a!=b)
a^=b^=a^=b;
}
上段代码解析如下:
a = a^b; // ab
b = a^b; // abb -> a
a = a^b; // ababb -> b
位运算概览
符号 | 描述 | 运算规则 | 例子 |
---|---|---|---|
& | 与 | 两个都为1时,结果是1 | 1&1=1; 1&0=0; 一零则零 |
| | 或 | 两个都为0时,结果是0 | 0&0=0; 1&0=1; 全零则零 |
^ | 异或 | 同0;异1 | 1^1=0; 0^0=0; 1^0=1; |
~ | 取反 | 0变1;1变0 | ~1=0; ~0=1 |
<< | 左移 | 按位左移,左移n位相当于乘以2的n次方 | 2 << 2 = 8 |
>> | 右移 | 相当于除以2的n次方,如果大于数字返回0 | 4 >> 2 = 1; 5 >> 3 = 0 |
正负数的二进制互转
- 都是 先 取反 再 补码(+1)
-
正数变负数
- 取反
- 补码(首位+1)
-
负数变正数
- 减一(首位-1)
- 取反
位运算衍生功能(想通每个功能并记住)
-
X & 1 == 1 奇数, X & 1 == 0 偶数 (x % 2 == 1)
-
X = X & (X - 1) 清零最低位的 1 【191. 位1的个数】
-
X & (X - 1) 判断是否是 2 的幂,等于 0 就是2的幂,否则不是
-
X & -X 得到最低位的 1 对应的值(2的几次方就是几)
-
如果 x 是一个高位的1(000100…000),那么x-1就是(000011…111)
-
~0 (表示-1, 全1二进制,符号位也是1)
-
x & (~0 << n) 将 x 最右边的 n 位清零
-
(x >> n) & 1 获取 x 的第 n 位值(0 或者 1)
-
x & (1 << (n - 1)) 获取 x 的第 n 位的幂值
-
x | (1 << n) 将第 n 个位置的值为1
-
x & (~(1 << n)) 将第 n 个位置的值为0
-
x & ((1 << n) - 1) 将最高位到第 n 位清零
-
x & (~((1 << (n + 1)) - 1)) 将第0位到第 n 位清零
位运算经典题目
136.只出现一次的数字
在一些出现2次的数字中找到只出现一次的数字
-
方法一:暴力解法利用 unordered_map 来对所有的数字进行记录,然后取出现次数为 1 的数字
-
方法二:利用异或运算符 ( ^ ),对于出现两次的数字,做异或运算为 0 ,而只出现1次的数字与0异或后就是其本身
371.两整数之和
不使用运算符 + 和 - 来完成加法
0 + 0 = 0
0 + 1 = 1
1 + 0 = 1
1 + 1 = 0(进位 1)
仔细观察发现其实就是异或运算(同 0 异 1),a ^ b
,所以无进位加法使用异或运算可以计算得出
0 + 0 = 0
0 + 1 = 0
1 + 0 = 0
1 + 1 = 1 (没进位)
上面的结果就是 a & b
的结果即不进位,而 进位结果 使用与运算和移位运算计算即可得出
- a + b 的问题拆分为 (a 和 b 的无进位结果) + (a 和 b 的进位结果)
- 进位结果使用
与运算
和移位运算
计算得出: (a & b) << 1 - 无进位加法使用
异或运算
计算得出: a ^ b - 循环此过程,直到进位为 0
int getSum(int a, int b)
{
while(b != 0)
{
// 注意可能为负数的情况,所以要转换成 unsigned int
// 位运算最好都转为 unsigned int
auto carry = (unsigned int)((a & b) << 1);
a ^= b;
b = carry;
}
return a;
}
190.颠倒二进制的位数
对二进制数进行逆序,比如 0010000101 -> 1010000100
假设输入为 n,那么颠倒位数的过程:
- 定义一个变量用于记录颠倒的结果 res
- res <<= 1; res做左移运算,把首位空出来
- res += n & 1 (判断末位是否为 1)如果为 1,那么加1,如果为0,那么加0
- n = n >> 1 n 做右移运算
// 方法一:
// 和反转整数一样的思路
uint32_t reverseBits(uint32_t n)
{
int res = 0;
for (int i = 0; i < 32; ++i)
{
res <<= 1; // res = res * 10;
res += n & 1; // res += n % 10;
n >>= 1; // n /= 10;
}
return res;
}
// 方法二:
// 拿最高位的 1 不断右移运算,遍历 n,如果出现了 1,那么相应的在低位上+1
uint32_t reverseBits(uint32_t n)
{
uint32_t ans = 0;
uint32_t cur = static_cast<uint32_t>(INT_MAX) + 1;
for (int i = 0; i < 32; ++i)
{
if (cur & n != 0)
{
ans += (1 << i);
}
cur >>= 1;
}
return ans;
}