第06章 约束满足问题 (Constraint Satisfaction Problems)

摘要:本章系统介绍了约束满足问题(CSP)的形式化定义、核心求解算法及其应用。CSP 通过变量、值域和约束三元组对组合搜索问题进行结构化建模,相比传统状态空间搜索能利用约束传播大幅剪枝。核心算法包括回溯搜索及其优化(前向检验、MRV、LCV)、弧一致性(AC-3/MAC)以及局部搜索(最小冲突)。本章还通过对比表格和典型应用案例(如地图着色、调度)阐明了不同技术的适用场景,并探讨了 CSP 与搜索、SAT 求解、约束优化等章节的理论关联,最后展望了 CSP 与大模型协同求解等前沿方向。

本章导读

本章将围绕约束满足问题(CSP)的核心内容展开,依次讲解以下四个部分:

  • 1. CSP定义与建模:介绍CSP的形式化三元组(变量、值域、约束)以及状态、一致性、完备性等基本概念,为后续算法奠定基础。
  • 2. 回溯搜索与启发式:讲解基础的深度优先回溯搜索,以及前向检验、MRV、度启发式、最少约束值等优化技术,提升搜索效率。
  • 3. 约束传播与弧一致性:深入解析AC-3算法及其在回溯中的集成(MAC),通过全局约束传播大幅缩减搜索空间。
  • 4. 局部搜索与扩展应用:探讨最小冲突等局部搜索方法,并简要介绍约束优化、软约束等扩展方向与实际应用场景。

1. 章节概述

本章将一类特殊组合搜索——约束满足问题(CSP)单独提炼:变量集合 + 各自值域 + 变量间约束。相比第 3 章显式状态空间,CSP 用“变量赋值”与“约束传播”大幅压缩搜索,是调度、布局、配置等现实问题的标准建模工具。

2. 关键概念与定义

  • CSP 形式化:三元组 ⟨X, D, C⟩,其中 X 为变量集,D 为各变量值域,C 为约束集(每个约束限定若干变量的允许取值组合)。
  • 状态(赋值):对部分/全部变量赋予值域中的值;一致(consistent)指不违反任何约束;完备(complete)指所有变量均已赋值。
  • 约束类型:一元(单变量)、二元(两变量)、高阶(多变量);可分硬约束(必满足)与软约束(偏好,含代价)。
  • 回溯搜索(Backtracking Search):DFS 式逐变量赋值,遇冲突即回溯。
  • 前向检验(Forward Checking, FC):每赋值后删去邻居值域中导致冲突的值,若某变量值域空则剪枝。
  • MRV(最小剩余值 / Minimum Remaining Values):优先选“当前合法值最少”的变量(最受限优先)。
  • 度启发式(Degree Heuristic):优先选约束其他未赋值变量最多的变量。
  • 最少约束值(Least Constraining Value):优先选留下他人选择余地最大的值。
  • 弧一致性(Arc Consistency)/ AC-3:使每条弧(有向约束)方向上一端变量的每个值都能在另一端找到兼容值。
  • MAC(Maintaining Arc Consistency):在回溯搜索中,每次赋值后运行 AC-3 维护全局弧一致性。
  • 约束传播(Constraint Propagation):利用局部约束缩减变量域,减少后续搜索。
  • 局部搜索求解 CSP:用最小冲突(min-conflicts)启发在赋值空间中迭代修正。

3. 核心理论与算法

CSP 与搜索的关系:状态 = 部分赋值;目标是找“完备且一致”的赋值。相比第 3 章,CSP 无需显式定义“行动”与“转移”,而是以“给某变量选值”为隐式行动,并借约束传播大幅剪枝。

回溯搜索(基础 DFS)

function Backtracking-Search(csp):
    return Recursive-BT({}, csp)
function Recursive-BT(assignment, csp):
if assignment complete: return assignment
var ← Select-Unassigned-Variable(csp, assignment)   # 用 MRV
for value in Order-Domain-Values(var, csp):         # 用 LCV
if value consistent with assignment:
assignment[var] = value
inferences ← Inference(csp, var)            # FC / MAC
if inferences ≠ failure:
result = Recursive-BT(assignment, csp)
if result ≠ failure: return result
assignment.remove(var); undo inferences
return failure

Python 实现示例

def backtracking_search(csp):
    """
    回溯搜索(基础DFS)求解CSP。
    输入:csp对象,包含变量、值域和约束。
    输出:一个完整且一致的赋值字典,若无解则返回None。
    """
    return recursive_backtrack({}, csp)
def recursive_backtrack(assignment, csp):
# 如果赋值已完备,返回解
if len(assignment) == len(csp.variables):
return assignment
# 选择未赋值变量(此处简化为顺序选择,实际可用MRV)
var = select_unassigned_variable(csp, assignment)
按顺序尝试值域中的每个值(实际可用LCV排序)
for value in order_domain_values(var, csp, assignment):
# 检查该值是否与当前赋值一致
if is_consistent(var, value, assignment, csp):
assignment[var] = value
# 可在此处加入前向检验或MAC推理
result = recursive_backtrack(assignment, csp)
if result is not None:
return result
# 回溯:移除该赋值
del assignment[var]
return None
辅助函数示例(需根据具体CSP实现)
def select_unassigned_variable(csp, assignment):
简单实现:返回第一个未赋值的变量
for var in csp.variables:
if var not in assignment:
return var
return None
def order_domain_values(var, csp, assignment):
简单实现:返回该变量的值域列表
return csp.domains[var]
def is_consistent(var, value, assignment, csp):
检查该赋值是否满足所有相关约束
for constraint in csp.constraints[var]:
if not constraint(var, value, assignment):
return False
return True

前向检验(FC):在 Inference 步,对 var 的相邻未赋值变量,移除与刚赋值冲突的值;若某邻域变空 → 回溯(比纯回溯更早剪枝,但只做“局部”检查)。

MRV 启发式:选剩余合法值最少的变量,力图尽早暴露失败(失败优先原则),显著减少扩展节点。

AC-3 算法(弧一致性)

function AC-3(csp):
    queue ← all arcs (Xi, Xj) in csp
    while queue not empty:
        (Xi, Xj) ← REMOVE(queue)
        if Revise(csp, Xi, Xj):               # Xi 中是否有值无法与 Xj 兼容
            if size(Domain(Xi)) == 0: return failure
            for each Xk ≠ Xj adjacent to Xi:
                ADD((Xk, Xi), queue)          # 重新检查受影响弧
    return true
function Revise(csp, Xi, Xj):
revised = false
for x in Domain(Xi):
if no y in Domain(Xj) satisfies constraint(Xi,Xj):
delete x from Domain(Xi); revised = true
return revised

Python 实现示例

def ac3(csp):
    """
    AC-3算法:实现弧一致性,缩减变量值域。
    输入:csp对象,包含变量、值域和二元约束。
    输出:若成功使所有弧一致则返回True,否则返回False。
    """
    # 初始化队列,包含所有弧 (Xi, Xj)
    queue = [(Xi, Xj) for Xi in csp.variables for Xj in csp.neighbors[Xi]]
while queue:
    Xi, Xj = queue.pop(0)
    if revise(csp, Xi, Xj):
        # 如果Xi的值域被清空,问题无解
        if not csp.domains[Xi]:
            return False
        # 将受影响的弧重新加入队列
        for Xk in csp.neighbors[Xi]:
            if Xk != Xj:
                queue.append((Xk, Xi))
return True
def revise(csp, Xi, Xj):
"""
检查弧(Xi, Xj)是否一致,不一致则从Xi的值域中删除无兼容值的值。
返回:是否对Xi的值域进行了修改。
"""
revised = False
# 遍历Xi值域的每个值
for x in list(csp.domains[Xi]):
# 检查是否存在Xj的值y满足约束
if not any(satisfies_constraint(Xi, x, Xj, y, csp) for y in csp.domains[Xj]):
csp.domains[Xi].remove(x)
revised = True
return revised
def satisfies_constraint(Xi, x, Xj, y, csp):
"""
检查赋值(Xi=x, Xj=y)是否满足Xi和Xj之间的约束。
实际实现需根据具体约束条件编写。
"""
# 示例:地图着色问题,相邻区域颜色不能相同
if (Xi, Xj) in csp.constraints:
return csp.constraints[(Xi, Xj)](x, y)
return True  # 若无约束则默认满足

AC-3 使每条弧两端一致,最坏 O(ed^3)(e 弧数,d 最大域大小)。MAC = 回溯中每次赋值后调用 AC-3,是最高效的完备求解组合之一。

局部搜索:最小冲突算法(Min-Conflicts)

function Min-Conflicts(csp, max_steps):
    assignment ← random complete assignment
    for step = 1 to max_steps:
        if assignment consistent: return assignment
        var ← random variable in conflict
        value ← argmin_{v} number-of-conflicts(var=v)
        assignment[var] = value
    return failure

Python 实现示例

def min_conflicts(csp, max_steps=1000):
    """
    最小冲突算法:通过局部搜索求解CSP。
    输入:csp对象,最大迭代步数max_steps。
    输出:一个完整且一致的赋值字典,若无解则返回None。
    """
    # 1. 生成随机完整赋值
    assignment = {}
    for var in csp.variables:
        assignment[var] = random.choice(csp.domains[var])
for step in range(max_steps):
    # 2. 检查当前赋值是否一致(无冲突)
    conflicted_vars = conflicted_variables(assignment, csp)
    if not conflicted_vars:
        return assignment  # 找到解
# 3. 随机选择一个冲突变量
var = random.choice(conflicted_vars)
4. 选择使冲突数最小的值
conflict_counts = []
for value in csp.domains[var]:
assignment[var] = value
conflict_counts.append((count_conflicts(var, assignment, csp), value))
assignment[var] = random.choice(csp.domains[var])  # 恢复原值用于比较
找出最小冲突值(可能有多个,随机选一个)
min_conflict = min(conflict_counts, key=lambda x: x[0])[0]
best_values = [value for cnt, value in conflict_counts if cnt == min_conflict]
best_value = random.choice(best_values)
5. 更新赋值
assignment[var] = best_value
return None  # 超过最大步数未找到解
def conflicted_variables(assignment, csp):
"""返回当前赋值中所有冲突的变量列表"""
conflicted = []
for var in csp.variables:
if count_conflicts(var, assignment, csp) > 0:
conflicted.append(var)
return conflicted
def count_conflicts(var, assignment, csp):
"""计算变量var在当前赋值下的冲突数"""
conflicts = 0
for neighbor in csp.neighbors[var]:
if neighbor in assignment:
if not satisfies_constraint(var, assignment[var], neighbor, assignment[neighbor], csp):
conflicts += 1
return conflicts

对大规模 CSP(如 N 皇后、调度)常比回溯更快找到解,但不保证完备。

约束类型扩展:软约束可用**约束优化问题(COP)**框架,给违反约束赋代价,目标最小化总代价(汇合第 3 章代价思想)。

4. 关键图示/表格说明

CSP 求解技术对比表

技术 类型 作用 完备/最优
回溯搜索 深度优先 基线完备搜索 完备
前向检验 FC 预剪枝 赋值后局部删值 完备、更快
MRV / DH 变量序 失败优先 不增完备性、减节点
AC-3 / MAC 约束传播 全局域缩减 完备、最强剪枝
最小冲突 局部搜索 迭代修正 不完备、快

弧一致性示意(文字):如 Australia 地图着色,某区域值被固定后,沿边界弧传播,相邻区域值域立即剔除该色;递归传播直至无弧可修。

典型应用建模(文字)

  • 地图着色:变量=地区,域=颜色,约束=相邻异色。
  • 调度:变量=任务,域=时段,约束=资源/先后。
  • 电路/布局:变量=元件,域=位置,约束=不重叠/连线短。

5. 与其他章节的关联

  • CSP 是第 3 章搜索的"结构化"特例:用变量/约束替代显式状态转移,约束传播替代盲目扩展。
  • 回溯 + 启发式思想延伸至逻辑 SAT 求解与规划(第 7、11 章)。
  • 软约束/代价化衔接约束优化效用(第 2、16 章)
  • 局部搜索(最小冲突)复用第 4 章爬山/局部优化思路。
  • 变量消除、传播算法与概率图模型(第 13–14 章变量消元)在结构上有对应。

6. 延伸思考

  • CSP 与大模型约束生成:让 LLM 生成满足复杂业务规则(合规、排班、配置)的方案时,常违反隐性约束。能否把问题建模为硬/软 CSP,用 AC-3/MAC 在模型输出后做"可满足性校验与修正",构建可靠的人机协同求解?
  • 可满足性与现实规模:现代 SAT/SMT 求解器已能处理百万级变量。当 AI 系统(如芯片设计、课程表、交通信号)依赖此类约束求解,如何平衡"求解速度"与"约束表达的完备性/可解释性"?

更多推荐