CPH-第五章 搜索
-
Complete search(穷举搜索),也称为暴力搜索或枚举。
-
核心思想:当问题规模允许时,系统性地遍历所有可能的候选解,是解决问题最直接、最可靠的方法。
5.1 子集生成(Generating Subsets)
- 生成一个包含 \(n\) 个元素的集合的所有子集,是穷举搜索的基础问题。一个包含 \(n\) 个元素的集合共有 \(2^n\) 个子集。
方法一:递归`
- 使用递归优雅地遍历所有子集。对于每个元素,都有“选取”和“不选取”两种选择,递归函数沿着这个决策树展开,最终生成所有组合。
void search(int k) {
if (k == n) {
// process subset
} else {
search(k+1);
subset.push_back(k);
search(k+1);
subset.pop_back();
}
}
方法二:利用整数的位表示
- 每个子集都可以用一个 \(n\) 位的二进制数表示,其中第 \(i\) 位为 1 代表选取第 \(i\) 个元素,为 0 则代表不选取。通过遍历从 \(0\) 到 \(2^n - 1\) 的所有整数,就能系统地生成所有子集。
for (int b = 0; b < (1 << n); b++) {
vector<int> subset;
for (int i = 0; i < n; i++) {
if (b & (1 << i)) subset.push_back(i);
}
}
5.2 排列生成(Generating Permutations)
-
与子集不同,排列关心元素的顺序。一个包含 \(n\) 个元素的集合共有 \(n!\) 种排列。
- 函数
search()通过逐步为每个位置选择尚未使用的元素,来构建排列。
- 函数
-
由于阶乘增长极快,排列生成通常只适用于 (n) 较小的情况。
5.3 回溯(Backtracking)
-
回溯是一种有组织的穷举搜索,它通过递归系统地构建候选解。其过程可以看作是对一棵搜索树进行深度优先遍历,当发现当前路径无法导向有效解时,就“回溯”到上一步,尝试其他分支。
-
回溯的核心优势在于其通用性。许多经典问题,如八皇后问题、数独求解、图着色等,都可以用回溯框架清晰地表达和解决。
5.4. 搜索剪枝(Pruning the Search)
-
纯粹的穷举搜索可能会探索大量无效的分支。剪枝是一种优化技术,它通过提前判断某些分支不可能产生有效解,从而直接跳过对这些分支的探索,显著减少搜索空间。
-
一个有效的剪枝策略需要满足两个条件:
- 正确性:剪枝判断必须保证不会误删包含有效解的分支。
- 高效性:剪枝判断本身的开销,应当远小于它所能节省的搜索开销。
5.5 折半查找(Meet in the Middle)
-
当问题规模 \(n\) 使得 \(2^n\) 或 \(n!\) 的穷举不可行时(例如 \(n\) 在 40 左右),折半查找是一种强大的优化技术。
-
它的核心思想是将搜索空间一分为二:
- 分别生成两半的所有可能状态(例如,各 \(2^{n/2}\) 个子集)。
- 对其中一半的状态进行排序或存入哈希表。
- 遍历另一半的状态,并在已处理的一半中高效地查找能与当前状态匹配的解。
-
这种方法将时间复杂度从 \(O(2^n)\) 显著降低到 \(O(2^{n/2} \log 2^{n/2})\) 或 \(O(2^{n/2})\),使得一些原本无法解决的问题变得可行。典型的应用是子集和问题(Subset Sum)。
5.6 总结:穷举搜索的策略选择
- 穷举搜索的核心在于根据问题规模和结构,选择合适的完整搜索策略:
| 策略 | 核心思想 | 适用场景/复杂度直觉 |
|---|---|---|
| 子集/排列生成 | 系统地枚举所有组合或顺序 | 问题规模 \(n\) 很小 (\(2^n\) 或 \(n!\) 可接受) |
| 回溯 | 有组织地深度优先搜索解空间 | 需要满足一系列约束条件的构造性问题 |
| 回溯 + 剪枝 | 在回溯基础上,提前排除无效分支 | 搜索空间大,但存在有效的可行性判断 |
| 折半查找 | 将搜索空间拆分,利用排序/哈希连接两半 | \(n\) 稍大(如 40),\(2^n\) 不可行但 \(2^{n/2}\) 可行 |
- 系统枚举思想,是后续学习更高级算法的重要基础。
浙公网安备 33010602011771号