位运算
位运算概述
在现代计算机中所有的数据都以二进制的形式存储在设备中,即 0、1 两种状态。计算机对二进制数据进行的运算 (+、-、*、/) 都叫作 位运算,即将符号位共同参与运算的运算。
例如计算两个数的和:
int a = 35;
int b = 47;
int c = a + b;
因为计算机中都是以二进制来进行运算,所以 int 变量会先在机器内部转换为二进制再进行相加:
35: 0 0 1 0 0 0 1 1
47: 0 0 1 0 1 1 1 1
———————————————————
82: 0 1 0 1 0 0 1 0
相比于在代码中直接使用 (+、-、*、/ ) 运算符,有时合理的运用位运算能提高代码在机器上的执行效率。
另外还需要了解下 源码、反码、补码 :
(1)正数的源码、反码、补码是一致的;
(2)负数的反码是对源码按位取反,只是最高位(符号位)不变,补码是反码加 1 .
(3)计算机的数字运算均是基于 补码 的。
例如:127 的源码、反码、补码如下所示
127 | -127 | |
---|---|---|
源码 | 0111 1111 | 1111 1111 |
反码 | 0111 1111 | 1000 0000 |
补码 | 0111 1111 | 1000 0001 |
位运算符
位运算符作用于位,并逐位执行操作。
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,不同为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
用途
1、按位与
运算规则(全为 1,才为1)
0 & 0 = 0 | 0 & 1 = 0 | 1 & 0 = 0 | 1 & 1 = 1 |
---|
注意:负数按补码形式参加按位与运算。
(1)清零
如果想将一个单元清零,使其全部二进制位为 0,只要__与一个各位都为零的数值相与__,结果为零。
(2)取一个数的指定位
比如取数 X = 1010 1110 的低 4 位,只需要另找一个数 Y,令 Y 的低 4 位为 1,其余位为 0,即 Y = 0000 1111,然后将 X 与 Y 进行按位与运算(X & Y = 0000 1110)即可得到 X 的指定位。
(3)判断奇偶
只要根据最未位是 0 还是 1 来决定,为 0 就是偶数,为 1 就是奇数。因此可以用 if ((a & 1) == 0)
代替 if (a % 2 == 0)
来判断 a 是不是偶数。
2、按位或
运算规则(全为 0,才为 0)
0 | 0 = 0 | 0 | 1 = 1 | 1 | 0 = 1 | 1| 1 = 1 |
---|
(1)常用来对一个数据的某些位设置为1
比如将数 X = 1010 1110 的低 4 位设置为 1,只需要另找一个数 Y,令 Y 的低 4 位为 1,其余位为 0,即 Y = 0000 1111,然后将 X 与 Y 进行按位或运算(X | Y = 1010 1111)即可得到。
3、按位异或
运算规则(相同为 0,不同为 1)
0 ^ 0 = 0 | 0 ^ 1 = 1 | 1 ^ 0 = 1 | 1 ^ 1 = 0 |
---|
(1)翻转指定位
比如将数 X = 1010 1110 的低 4 位进行翻转,只需要另找一个数 Y,令 Y 的低 4 位为 1,其余位为 0,即 Y = 0000 1111,然后将 X 与 Y 进行异或运算(X ^ Y = 1010 0001)即可得到。
(2)与 0 相异或值不变
例如:1010 1110 ^ 0000 0000 = 1010 1110
(3)交换两个数
void swap(int &a, int &b){
if (a != b){
a ^= b;
b ^= a;
a ^= b;
}
}
4、按位取反
运算规则(0 变 1,1 变 0)
~1 = 0 | ~0 = 1 |
---|
(1)使一个数的最低位为 0
使 x 的最低位为 0,可以表示为:a & ~1
。~1 的值为 1111 1111 1111 1110
,再按 与
运算,最低位一定为 0。因为 ~
运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高。
5、左移(<<)
定义:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
设 a = 1010 1110,a = a << 2 将 a 的二进制位 左移2位、右补0,即得 a = 1011 1000。
若左移时舍弃的高位不包含 1,则每左移一位,相当于该数乘以 2 。比如 左移 k 位,即乘上 2k .
6、右移(>>)
定义:将一个数的各二进制位全部右移若干位,正数左补 0 ,负数左补 1 ,右边丢弃。
例如:a = a >> 2 将 a 的二进制位右移 2 位,左补 0 或者 左补 1 得看被移数是正还是负。
操作数每右移一位,相当于该数除以 2。比如 右移 k 位,即除以 2k .
【例题一】n 的二进制表示中第 k 位数
思路:先把第 k 位数字移动到最后一位,n 右移 k 位,即 n >> k
,再看个位是几,用 n & 1
,合并两步后,即 n >> k & 1
.
例如求 10 的二进制表示,代码如下:
#include <iostream>
using namespace std;
int main()
{
int n = 10; //10的二进制表示为4位数
for (int k = 3; k >= 0; k--)
cout << (n >> k & 1);
return 0;
}
输出如下:
1010
【例题二】 801. 二进制中1的个数 - AcWing题库
给定一个长度为 1 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n .
第二行包含 n 个整数,表示整个数列
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
思路:使用 lowbit(x)
来解决,其表达式为 x & -x
,其中 -x
表示补码,即源码取反加 1 ,-x = (~x + 1)
。
作用:返回 x 的最后一位 1,比如 x = 1010, 则 lowbit(x) = 10
,x = 101000, 则 lowbit(x) = 1000
.
代码如下:
#include <iostream>
using namespace std;
const int N = 100010;
int lowbit(int x)
{
return x & -x;
}
int main()
{
int n;
cin >> n;
while (n--){
int x;
cin >> x;
int res = 0;
while (x){
x -= lowbit(x);
res++;
}
cout << res << " ";
}
return 0;
}
求 a 乘 b 对 p 取模的值。(1 <= a, b, p <= 1018)
思路:两个 1018 级别的数相乘会超范围,但两个 1018 级别的数相加不会超范围,所以就可以将乘法转化为加法。即将 a * b
转化为 a + a + a + ... + a
(共 b 个 a).
这里利用二进制来简化运算
a * 1 = a
a * 2 = 2a
a * 4 = 4a
a * 8 = 8a
...
a * (2^k) = 2^(k * a)
代码如下:
#include <iostream>
#define ull unsigned long long
using namespace std;
int main()
{
ull a, b, p;
cin >> a >> b >> p;
ull res = 0;
while (b){
if (b & 1) //如果个位是1
res = (res + a) % p;
b >>= 1; // b/2
a = a * 2 % p;
}
cout << res << endl;
return 0;
}