回溯与约束搜索 · HTML
数学 / 逻辑形态
dfs(partial_assignment):若完整则输出;选择未赋值变量 x,遍历 domain(x) 中与当前约束一致的 v,apply(x=v),传播/递归,再 undo。
识别信号
- 需要构造满足多项局部约束的棋盘、填数、分割或路径。
- 变量域有限且规模适合搜索。
- 部分赋值即可检测冲突。
- 要求具体 witness 或全部解。
- 可用 MRV、最受限变量或候选排序显著剪枝。
容易误判的相似信号
| Signal | Why it is different |
|---|
| 只求图上的连通/最短路 | 状态可按节点复用时应 BFS/Dijkstra,而非枚举整条历史。 |
| 约束图具有可利用的 DP 结构 | 链、树或小树宽可用 DP,避免重复搜索等价部分赋值。 |
| 只要存在性且可化为匹配/2-SAT | 专门多项式算法比通用回溯更可靠。 |
核心不变量
进入递归时 partial assignment 满足所有已激活约束,辅助集合/计数与赋值完全同步;被剪分支不可能扩展为合法完整解。
状态、转移与处理顺序
| Item | Definition |
|---|
| State | 部分赋值、未赋值变量、每类约束的增量摘要与可选 domain。 |
| Transition | 选择一个变量,尝试兼容值,原子地更新赋值与约束,递归后精确回滚。 |
| Frontier / order | 搜索树中的一个一致部分赋值;优先扩展候选最少的变量可更早失败。 |
Python 骨架
def color_graph(graph, color_count):
n = len(graph)
color = [-1] * n
def dfs(done):
if done == n: return True
candidates = [u for u in range(n) if color[u] < 0]
u = max(candidates, key=lambda x: sum(color[v] >= 0 for v in graph[x]))
forbidden = {color[v] for v in graph[u] if color[v] >= 0}
for c in range(color_count):
if c in forbidden: continue
color[u] = c
if dfs(done + 1): return True
color[u] = -1
return False
return color if dfs(0) else None
正确性思路
- 不变量使每个被尝试的部分赋值合法。
- 对所选变量遍历所有当前合法值;任何完整解必给它其中一个值,所以不会漏解。
- 只有立即违反约束或所有候选失败时返回失败;回滚恢复父状态,故找到的完整赋值合法且失败结论可靠。
复杂度
| Time | Space | Parameters |
|---|
| 最坏 O(d^n·check),剪枝影响实际规模但不改变指数最坏界 | O(n+constraint_state) | n 为变量数,d 为最大域大小。 |
边界条件
- 空变量集
- 变量初始预填
- 无候选变量
- 自环/矛盾约束
- 求一个还是全部解
- 辅助状态回滚异常
错误模式
| Pattern | Why it fails / fix |
|---|
| 到叶子才检查约束 | 保留大量必败前缀;应在 apply 后立即验证/传播。 |
| 回滚不完整 | 赋值撤销但集合/计数未撤销会污染兄弟分支;更新应成对封装。 |
| 剪枝只是经验判断 | 每个 prune 必须证明该前缀不可能完成,否则会漏解。 |
| 盲目按输入顺序选变量 | 正确但可能极慢;MRV/最受限启发式通常更早暴露矛盾。 |
选择与排除规则
- 先找能在部分赋值时检测的局部约束。
- 封装 apply/undo 让状态原子同步。
- 优先最受限变量,再排序最有希望的值。
- 在使用通用搜索前排除 matching、SAT 特例或结构化 DP。
相关模板
| Relation | Template | Boundary |
|---|
| composition | Trie 前缀树 | 字符串路径搜索可用 Trie 前缀批量剪枝。 |
原创微型例子
| 环节 | 内容 |
|---|
| Result | 搜索证明无解,无需枚举 2³ 个完整赋值后才检查。 |
| Setup | 三角形三个顶点用两种颜色,邻点颜色不同。 |
| Trace | 前两点被迫异色,第三点的两个颜色都与某邻点冲突,立即回退。 |
Recall prompts
我的补充