GESP 5级
位移运算
5级 · 基础算法与数据类型
📖 什么是位移运算?
位移运算就是把一个数的二进制位"整体移动"——向左移或向右移。就像你把一排书整体向左或向右推。
C++ 提供两种位移运算符:
<< 左移(Left Shift):所有位往左移,右边补 0
>> 右移(Right Shift):所有位往右移,左边补 0
>> 右移(Right Shift):所有位往右移,左边补 0
核心结论(先记住结论,后面解释为什么):
a << n = a × 2ⁿ(左移 n 位 = 乘以 2 的 n 次方)
a >> n = a ÷ 2ⁿ(右移 n 位 = 除以 2 的 n 次方,向下取整)
a >> n = a ÷ 2ⁿ(右移 n 位 = 除以 2 的 n 次方,向下取整)
🌟 为什么重要?
- 快速乘除 2 的幂:
x << 3比x * 8更快,因为位移只需一步操作。 - 计算 2 的 n 次方:
1 << n就是 2ⁿ,这在竞赛中非常常用。比如1 << 10 = 1024。 - 状态压缩的基石:用
(1 << i)表示"第 i 位为 1",是状态压缩的基本操作。 - 二进制枚举:
for (int mask = 0; mask < (1 << n); mask++)可以枚举 n 个元素的所有子集。 - 权限/标志位:操作系统用位来表示权限,
chmod 755就是八进制的位运算。
📋 前置知识
- ✅ 了解二进制的表示方法
- ✅ 了解"位运算 与/或/非/异或"中的基本概念
- ✅ 知道 2 的幂次序列:1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024...
➡️ 左移 << 详解
操作:把二进制位整体向左移动 n 位,右边空出来的位补 0,最左边溢出的位丢弃。
就像电影院的座位向左平移:最左边的人出去了(丢弃),右边空出位置补上新人(0)。
逐步演示:5 << 1(5 左移 1 位)
5 的二进制:0 0 0 0 0 1 0 1
左移 1 位:0 0 0 0 1 0 1 0 ← 右边补了个 0
结果:0 0 0 0 1 0 1 0 = 10
验证:5 × 2¹ = 5 × 2 = 10 ✅
左移 1 位:0 0 0 0 1 0 1 0 ← 右边补了个 0
结果:0 0 0 0 1 0 1 0 = 10
验证:5 × 2¹ = 5 × 2 = 10 ✅
再看一个:5 << 2(5 左移 2 位)
5 的二进制:0 0 0 0 0 1 0 1
左移 2 位:0 0 0 1 0 1 0 0 ← 右边补了两个 0
结果:0 0 0 1 0 1 0 0 = 20
验证:5 × 2² = 5 × 4 = 20 ✅
左移 2 位:0 0 0 1 0 1 0 0 ← 右边补了两个 0
结果:0 0 0 1 0 1 0 0 = 20
验证:5 × 2² = 5 × 4 = 20 ✅
为什么左移等于乘以 2ⁿ?
在二进制中,每一位的权重是 2⁰, 2¹, 2², 2³...
把所有位左移 n 位,相当于每个位的权重都多乘了 n 个 2,
也就是整体乘了 2ⁿ。
类比十进制:12 左移 1 位(补0)变成 120,就是 12 × 10。道理一样!
把所有位左移 n 位,相当于每个位的权重都多乘了 n 个 2,
也就是整体乘了 2ⁿ。
类比十进制:12 左移 1 位(补0)变成 120,就是 12 × 10。道理一样!
⬅️ 右移 >> 详解
操作:把二进制位整体向右移动 n 位,最右边的位直接丢弃,左边补 0。
右移就是左移的逆操作:左边空出来的位补 0,最右边溢出的位丢弃。
逐步演示:20 >> 1(20 右移 1 位)
20 的二进制:0 0 0 1 0 1 0 0
右移 1 位:0 0 0 0 1 0 1 0 ← 左边补了个 0,最右边的 0 被丢弃
结果:0 0 0 0 1 0 1 0 = 10
验证:20 ÷ 2 = 10 ✅
右移 1 位:0 0 0 0 1 0 1 0 ← 左边补了个 0,最右边的 0 被丢弃
结果:0 0 0 0 1 0 1 0 = 10
验证:20 ÷ 2 = 10 ✅
再看一个:21 >> 1(21 右移 1 位)
21 的二进制:0 0 0 1 0 1 0 1
右移 1 位:0 0 0 0 1 0 1 0 ← 左边补 0,最右边的 1 被丢弃
结果:0 0 0 0 1 0 1 0 = 10
验证:21 ÷ 2 = 10.5,向下取整 = 10 ✅
右移 1 位:0 0 0 0 1 0 1 0 ← 左边补 0,最右边的 1 被丢弃
结果:0 0 0 0 1 0 1 0 = 10
验证:21 ÷ 2 = 10.5,向下取整 = 10 ✅
注意:右移是整数除法,会向下取整(丢掉小数部分),不是四舍五入。
🧮 常用速记表
1 << 0 = 1 1 >> 0 = 1
1 << 1 = 2 1 >> 1 = 0
1 << 2 = 4 2 >> 1 = 1
1 << 3 = 8 4 >> 1 = 2
1 << 4 = 16 8 >> 1 = 4
1 << 5 = 32 16 >> 1 = 8
1 << 6 = 64 10 >> 1 = 5
1 << 7 = 128 21 >> 1 = 10
1 << 8 = 256 100 >> 2 = 25
1 << 9 = 512
1 << 10 = 1024
1 << 1 = 2 1 >> 1 = 0
1 << 2 = 4 2 >> 1 = 1
1 << 3 = 8 4 >> 1 = 2
1 << 4 = 16 8 >> 1 = 4
1 << 5 = 32 16 >> 1 = 8
1 << 6 = 64 10 >> 1 = 5
1 << 7 = 128 21 >> 1 = 10
1 << 8 = 256 100 >> 2 = 25
1 << 9 = 512
1 << 10 = 1024
💻 完整代码示例
#include <iostream>
#include <bitset>
using namespace std;
int main() {
// ====== 左移演示 ======
int x = 5;
cout << "x = " << x << " (二进制: " << bitset<8>(x) << ")" << endl;
cout << "x << 1 = " << (x << 1) << endl; // 10 (5 * 2)
cout << "x << 2 = " << (x << 2) << endl; // 20 (5 * 4)
cout << "x << 3 = " << (x << 3) << endl; // 40 (5 * 8)
// ====== 右移演示 ======
int y = 21;
cout << "y = " << y << " (二进制: " << bitset<8>(y) << ")" << endl;
cout << "y >> 1 = " << (y >> 1) << endl; // 10 (21 / 2)
cout << "y >> 2 = " << (y >> 2) << endl; // 5 (21 / 4)
cout << "y >> 3 = " << (y >> 3) << endl; // 2 (21 / 8)
// ====== 实用技巧 ======
// 技巧1:快速计算 2 的 n 次方
int n = 10;
cout << "2^" << n << " = " << (1 << n) << endl; // 输出: 1024
// 技巧2:用位移代替乘除法(更高效)
int price = 15;
cout << "价格翻倍: " << (price << 1) << endl; // 30
cout << "价格减半: " << (price >> 1) << endl; // 7 (不是7.5!)
// 技巧3:判断一个数是不是 2 的幂
// 2的幂的二进制只有一个1,x & (x-1) 会把这个1清掉变成0
int p = 16; // 10000
if (p > 0 && (p & (p - 1)) == 0) {
cout << p << " 是 2 的幂" << endl; // 会执行
}
// 技巧4:用位移枚举所有子集
// 3个物品,子集用二进制表示
int items = 3;
for (int mask = 0; mask < (1 << items); mask++) {
cout << "子集 " << mask << " = " << bitset<3>(mask) << ": ";
for (int i = 0; i < items; i++) {
if (mask & (1 << i)) {
cout << "物品" << i << " ";
}
}
cout << endl;
}
// 输出:
// 子集 0 = 000: (空集)
// 子集 1 = 001: 物品0
// 子集 2 = 010: 物品1
// 子集 3 = 011: 物品0 物品1
// 子集 4 = 100: 物品2
// 子集 5 = 101: 物品0 物品2
// 子集 6 = 110: 物品1 物品2
// 子集 7 = 111: 物品0 物品1 物品2
return 0;
}
⚠️ 易错点
🚨 常见错误
- 右移不是四舍五入:
5 >> 1 = 2而不是 3。右移是向下取整(截断小数),和5 / 2 = 2一样。 - 负数右移:负数的右移行为依赖编译器(算术右移还是逻辑右移),竞赛中一般避免对负数右移。
- 1 << 31 溢出:
1 << 31在 32 位 int 下会溢出变成负数(-2147483648)。如果需要更大的数,用1LL << 31(long long)。 - 运算符优先级:
a << n + 1 实际上是a << (n + 1),因为+优先级高于<<。要写(a << n) + 1需要加括号。同样,a << n == 0也是坑,要用括号。 - 移位量不能为负:
a << -1是未定义行为,移位数必须是非负整数且小于位宽。 - 不要和位运算搞混:
<<和>>是位移,&|^是位运算,它们是不同的操作。
📝 练习建议
💡 怎么练?
- 手写二进制验证:把
5 << 3、21 >> 2等在纸上画出来,对比程序结果。 - 背熟 2 的幂次:2¹=2, 2²=4, ..., 2¹⁰=1024,做到看到
1<<8就知道是 256。 - 练习"判断 2 的幂":用
x > 0 && (x & (x-1)) == 0,理解为什么这个表达式能判断 2 的幂。 - 练习子集枚举:用
for (int mask = 0; mask < (1 << n); mask++)枚举所有子集,这是状态压缩的基础。 - 推荐题目:LeetCode 231(2的幂)、LeetCode 338(比特位计数)、洛谷 P1896(互不侵犯,状态压缩入门)。