剪枝
💡 核心思想
剪枝是在搜索过程中,通过某些条件判断提前排除不可能产生最优解的子树,从而减少搜索量。常见策略:可行性剪枝、最优性剪枝(上下界剪枝)、冗余剪枝。
剪枝是搜索优化的灵魂。朴素 DFS 在数据稍大时就会 TLE,但加上巧妙的剪枝后,往往能通过看似不可能的数据范围。剪枝的核心原则是"宁可多算不能漏算"——剪枝条件必须是安全的,不能把正确答案也剪掉了。
🎯 直觉理解
想象你在找东西,从一楼找到三楼都没找到。剪枝就是:"如果二楼没声音,三楼肯定没有,不用上去了"。但前提是你确定判断条件是对的——万一三楼很安静但东西就在那呢?
📝 算法流程
- 可行性剪枝:当前状态已经不满足约束,直接回溯
- 最优性剪枝:当前代价已经超过已知最优解,不用继续
- 排序优化:按某种顺序搜索,更早找到优解
- 记忆化:相同状态不重复搜索
$$\text{剪枝后的搜索量} \ll \text{朴素搜索量,但最坏情况不变}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | 最好大幅减少,最坏不变 |
| 空间 | $O(\text{搜索深度})$ |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
// 剪枝示例:子集和问题
int n, target, a[25];
bool dfs(int i, int sum) {
if (sum > target) return false; // 可行性剪枝
if (i == n) return sum == target;
// 最优性剪枝:即使全选也不够
if (sum + /* remaining sum */ 0 < target) return false;
if (dfs(i+1, sum + a[i])) return true; // 选
if (dfs(i+1, sum)) return true; // 不选
return false;
}
int main() {
cin >> n >> target;
for (int i = 0; i < n; i++) cin >> a[i];
sort(a, a+n, greater<int>()); // 大的先试,更早触发剪枝
cout << (dfs(0, 0) ? "Yes" : "No") << endl;
return 0;
}⚠️ 常见坑点
剪枝条件不安全导致漏掉正确答案
剪枝不够强力仍然 TLE
忘记排序优化搜索顺序
剪枝判断本身的耗时也要考虑
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P1120 小木棍 | 洛谷 | CSP-S | 经典剪枝搜索 |
| P1074 靶形数独 | 洛谷 | CSP-S | 搜索+剪枝 |