Backtracking Search for CSPs

Overview

Backtracking search is the standard complete algorithm for CSPs. Unlike generic DFS, it exploits the commutativity of CSP assignments (order doesn’t matter) and uses domain-specific heuristics to dramatically reduce the search space.

Key insight: We only need to consider assignments to one variable at a time, and a partial assignment that violates a constraint can be abandoned immediately — no need to explore the subtree.


The BACKTRACKING-SEARCH Algorithm

function BACKTRACKING-SEARCH(csp) returns solution or failure
    return BACKTRACK({}, csp)

function BACKTRACK(assignment, csp) returns solution or failure
    if assignment is complete then return assignment
    var ← SELECT-UNASSIGNED-VARIABLE(csp, assignment)
    for each value in ORDER-DOMAIN-VALUES(var, assignment, csp) do
        if value is consistent with assignment then
            add {var = value} to assignment
            inferences ← INFERENCE(csp, var, assignment)
            if inferences ≠ failure then
                add inferences to csp
                result ← BACKTRACK(assignment, csp)
                if result ≠ failure then return result
            remove {var = value} and inferences from assignment and csp
    return failure

Three pluggable components: - SELECT-UNASSIGNED-VARIABLE — which variable to assign next - ORDER-DOMAIN-VALUES — in what order to try values - INFERENCE — how much propagation to do after each assignment


Variable Ordering: MRV and Degree Heuristic

MRV (Minimum Remaining Values) — “Fail-First”

Choose the variable with the fewest legal values remaining in its domain.

Why it works: Assigns the most constrained variable first, detecting failures earlier. Reduces the branching factor where it matters most.

Example: In map coloring, if SA has only 1 value left and WA has 3, assign SA first.

Degree Heuristic — Tie-Breaker

When multiple variables are tied on MRV, choose the one involved in the most constraints on remaining unassigned variables.

Why: High-degree variables are most likely to create conflicts, so assigning them early catches more failures sooner.


Value Ordering: LCV (Least Constraining Value) — “Succeed-First”

Given a chosen variable, try the value that eliminates the fewest choices from neighboring variables’ domains.

Order values by: Σ_{Xⱼ neighbor of Xᵢ} |{y ∈ Dⱼ : y inconsistent with v}|
(ascending — least constraining first)

Why: We want to find a solution, so try values that leave maximum flexibility for remaining variables. If a value rules out many neighbors’ options, it’s likely a dead end.

Contrast with MRV: MRV is fail-first (for variable choice), LCV is succeed-first (for value choice). Both are greedy but complementary.


Inference: Forward Checking and MAC

After assigning Xᵢ = v:

Forward Checking

For each unassigned variable Xⱼ that shares a constraint with Xᵢ: - Remove from Dⱼ any value inconsistent with v. - If Dⱼ becomes empty → backtrack immediately.

Cost: O(d) per neighbor.

MAC (Maintaining Arc Consistency)

Run AC-3 starting from arcs {(Xⱼ, Xᵢ) : Xⱼ unassigned neighbor of Xᵢ}. Propagates failures further than forward checking.

MAC is the default in modern CSP solvers — the extra propagation cost is worth the reduction in search tree size.


Backtracking Variants

Chronological Backtracking

Standard: when a conflict is detected, undo the most recent assignment and try the next value. Simple but can thrash — if the conflict is caused by a variable assigned 20 steps ago, we keep undoing recent unrelated choices.

Backjumping

When a dead end is reached at variable Xⱼ, jump back to the deepest variable Xᵢ in the conflict set — the set of past variables that directly caused the failure.

Conflict set: variables that are (a) assigned before Xⱼ and (b) constrained with Xⱼ.

Supersedes chronological backtracking with no overhead in terms of nodes visited.

Conflict-Directed Backjumping (CBJ)

When backjumping to Xᵢ, merge the conflict sets: the conflict set at Xᵢ gets augmented with Xⱼ’s conflict set (minus Xᵢ itself). This propagates failure explanations upward.

More powerful than simple backjumping; avoids repeatedly rediscovering the same failures.

Constraint Learning / No-Good Recording

Record the no-good: the partial assignment that led to the failure. Add it as a new constraint. Future search will prune any branch that contains this no-good.

Expensive in memory but can yield exponential speedups on structured problems.


Worked Example: 4-Queens

Variables: Q1, Q2, Q3, Q4 (column of queen in row i) Domains: {1, 2, 3, 4}

Step 1: MRV → all tied → choose Q1. LCV → try Q1=1.
  Forward checking: removes values from Q2, Q3, Q4.

Step 2: MRV → Q2 has fewest. Try Q2=3 (LCV).
  Forward checking removes more values.

Step 3: Q3 domain empty → forward checking detected failure.
  Backtrack to Q2. Try Q2=4.
  ...

Continue until Q1=2, Q2=4, Q3=1, Q4=3 found.

Theoretical Properties

Property Value
Complete? Yes — finds solution if one exists
Optimal? Not applicable (satisfaction, not optimization)
Time complexity O(dⁿ) worst case
Space complexity O(n·d) — linear in depth × domain

In practice: MRV + degree + LCV + MAC reduces effective branching dramatically. Many structured CSPs (Sudoku, scheduling) are solved in milliseconds.