Part I · Chapter 4

Constraint Satisfaction Problems

After this page you can model a problem as a CSP, run backtracking search by hand, filter with forward checking and AC-3, and explain exactly why MRV and LCV pull in opposite directions.

Essentials

How to model a problem as a CSP and solve it: backtracking search, plus filtering and ordering so that you discover you are doomed as early as possible.

Everything below is the explanation and the practice. If you are short on time, read this box, then go to the problems.

1. Planning problems vs. identification problems

Every search problem so far — BFS, DFS, UCS and A* — was a planning problem: you knew the goal and were asked how to get there, so the answer is a sequence of actions. Paths have costs (there is a best one) and depths (a shallow goal is cheaper, which is why BFS is complete and DFS is not), and a heuristic $h(n)$ is useful because it estimates remaining distance.

In an identification problem the answer is an assignment of values to variables, and nobody cares how you found it — like a completed crossword, where the order in which you wrote the letters is not part of the answer.

Key idea

Planning: the path to the goal is the important thing. Paths have various costs and depths; heuristics give problem-specific guidance.

Identification: the goal itself is the important thing, not the path. Every solution sits at exactly the same depth — which is why CSPs get their own algorithms.

PropertyPlanning (Ch. 1–3)Identification (CSPs)
What you returna sequence of actionsa complete assignment $\{X_1 = v_1, \dots, X_n = v_n\}$
Depth of solutionsvaries; shallow is betteralways exactly $n$ (one assignment per variable)
Path costmeaningful; we optimise itmeaningless — all solutions are equally good
Heuristicsestimate distance-to-go"distance" is constant, so distance heuristics are useless
Useful guidance instead$h(n)$, admissibility, consistencywhich variable to fill next, which value to try, what becomes impossible

That last row is the whole chapter. Because every solution is at depth $n$, a heuristic saying "you're getting closer" tells us nothing — on every branch we are exactly $n - |\text{assigned}|$ steps from completion. In planning you ask "am I close?"; in identification you ask "am I doomed?", and every algorithm below is a device for discovering that you are doomed as early as possible, so you can stop digging.

2. What exactly is a CSP?

In a standard search problem a state is an arbitrary data structure and the goal test is any function over states. That generality is why generic search can do nothing clever — it cannot look inside either one. A CSP gives up the generality on purpose and gets algorithms in exchange.

Definition

A constraint satisfaction problem is a triple $(X, D, C)$:

A solution is a complete assignment (every variable has a value) that is consistent (every constraint is satisfied).

The clause that does all the work is "for subsets of the variables". A general goal test is a black box over the whole state, so every variable must be filled before you may call it. A constraint mentions two or three variables, so it can be evaluated on a partial assignment the instant those few are filled. Early failure detection is the entire performance story of this chapter.

3. Why bother? Where CSPs show up

An enormous number of real problems are, once you squint, "assign values to variables so that a pile of local rules all hold". The course's list:

Assignment & timetabling

Assignment: who teaches what class. Variables = courses, domain = faculty, constraints = nobody teaches two things at once, everybody qualified, nobody over their load.

Timetabling: variables = courses, domain = (time slot, room) pairs, constraints = no room double-booked, required courses not in conflict, room big enough.

Engineering & industry

Hardware configuration: components whose interfaces, power budgets and form factors are compatible. Transportation and factory scheduling: variables are job start times; B cannot start before A ends, two jobs cannot share a machine. Circuit layout: place and route without overlaps. Fault diagnosis: variables are "is component $i$ broken?", constraints are the observed symptoms.

Some instances involve real-valued variables — a start time is a real number, not a colour — which §8 returns to.

4. The running example: colouring Australia

The whole lecture runs on one instance, so it is worth setting up carefully: colour the seven regions of Australia so that no two regions sharing a border get the same colour, using only three colours.

Definition

Variables: $\text{WA},\ \text{NT},\ \text{Q},\ \text{NSW},\ \text{V},\ \text{SA},\ \text{T}$ — Western Australia, Northern Territory, Queensland, New South Wales, Victoria, South Australia, Tasmania. So $n = 7$.

Domains: $D_i = \{\text{red},\ \text{green},\ \text{blue}\}$ for every $i$, so $d = 3$.

Constraints: adjacent regions must have different colours. Written implicitly this is one inequality per shared border, e.g. $\text{WA} \neq \text{NT}$; written explicitly it is a list of allowed pairs, e.g. $(\text{WA}, \text{NT}) \in \{(\text{red},\text{green}),(\text{red},\text{blue}),(\text{green},\text{red}),\dots\}$ — all six ordered pairs of distinct colours.

Solutions are assignments satisfying all of them, for example $$\{\text{WA}=\text{red},\ \text{NT}=\text{green},\ \text{SA}=\text{blue},\ \text{Q}=\text{red},\ \text{NSW}=\text{green},\ \text{V}=\text{red},\ \text{T}=\text{green}\}.$$

The nine borders — memorise these, every worked example below uses them:

WA–NT   WA–SA   NT–SA   NT–Q   SA–Q   SA–NSW   SA–V   Q–NSW   NSW–V
Tasmania borders nothing.

Verify that solution border by border: WA(red)≠NT(green) ✓, WA(red)≠SA(blue) ✓, NT(green)≠SA(blue) ✓, NT(green)≠Q(red) ✓, SA(blue)≠Q(red) ✓, SA(blue)≠NSW(green) ✓, SA(blue)≠V(red) ✓, Q(red)≠NSW(green) ✓, NSW(green)≠V(red) ✓. All nine hold. T is unconstrained, so any of its three colours works — our first hint about structure.

Figure 1 — the map, and the same instance as a constraint graph
WA NT Q SA NSW V T red = terracotta · green = teal · blue = violet WA NT Q SA NSW V T T has degree 0: an independent subproblem. SA has degree 5: the bottleneck.
What to notice: the two pictures carry exactly the same information. Every shared border on the left is one edge on the right; the colouring shown satisfies all nine. SA touches five other regions, which is why it will be the variable that keeps failing later on — and why MRV will want to assign it first.

5. Constraint graphs

Definition

A CSP is binary if each constraint relates at most two variables.

For a binary CSP, the binary constraint graph has one node per variable and one edge (arc) per constraint: an edge between $X$ and $Y$ means "there is a constraint restricting which pairs $(x,y)$ are allowed".

The right-hand picture above is such a graph, and it is what the general-purpose algorithms operate on. Note what it throws away: it records that WA and NT are constrained, not how. That is the point — an algorithm written against the graph works for map colouring, scheduling and n-queens alike, because it needs a constraint's scope, never its semantics.

Three ways graph structure speeds up search:

  1. Connected components. Tasmania is isolated, so it is an independent subproblem: components of sizes $n_1, n_2$ solved separately cost $O(d^{n_1}) + O(d^{n_2})$ instead of $O(d^{n_1 + n_2})$ — for Australia, $3^6 + 3^1 = 732$ instead of $3^7 = 2187$.
  2. Degree. A high-degree node (SA, degree 5) constrains many neighbours, so assigning it early prunes a lot — the "degree heuristic", MRV's tie-breaker.
  3. Global shape. If the graph is a tree, the CSP is solvable in $O(n d^2)$ with no backtracking. beyond syllabus See §18.
Common mistake

The constraint graph is not the search tree. The graph has 7 nodes and never changes during the search. The search tree has thousands of nodes and is built and destroyed as the algorithm runs. Confusing them is the most common source of "wait, which thing has depth $n$?" — the search tree has depth $n$; the graph has no depth.

Which of these makes a CSP not binary?
Why: "binary" refers to the arity of the constraints — how many variables a single constraint mentions — not to domain size and not to the number of variables. A 9-way alldiff mentions nine variables, so it is a higher-order constraint. (Domain size is $d$; it has nothing to do with arity.)

6. N-queens, modelled twice

The lecture puts up two formulations of n-queens and leaves both blank. That blank is the single most useful exercise in the chapter, because it shows that modelling choices are algorithmic choices: the same puzzle, written down two ways, differs by a factor of $10^{12}$ in search-space size before any clever algorithm is applied.

The puzzle: place $n$ queens on an $n \times n$ chessboard so that no two attack each other — no shared row, column or diagonal.

Formulation 1 — one Boolean per square

Definition

Variables: $X_{ij}$ for $i, j \in \{1,\dots,n\}$ — one variable per square of the board. $i$ is the column, $j$ is the row. There are $n^2$ variables.

Domains: $X_{ij} \in \{0, 1\}$, where $1$ means "there is a queen on this square" and $0$ means "empty". So $d = 2$ and this is a Boolean CSP.

Constraints: four families, written out in full below.

Write the constraints explicitly. For every pair of squares that attack each other, we forbid the combination "both are 1". The allowed pairs are therefore $\{(0,0),(0,1),(1,0)\}$ — everything except $(1,1)$:

FamilyConstraintReads asHow many
Rows$\forall i, j, k:\ (X_{ij},\, X_{kj}) \in \{(0,0),(0,1),(1,0)\}$no two queens in row $j$$n \cdot \binom{n}{2}$
Columns$\forall i, j, k:\ (X_{ij},\, X_{ik}) \in \{(0,0),(0,1),(1,0)\}$no two queens in column $i$$n \cdot \binom{n}{2}$
Diagonal ↘$\forall i, j, k:\ (X_{ij},\, X_{i+k,\,j+k}) \in \{(0,0),(0,1),(1,0)\}$no two on a down-right diagonal$O(n^3)$
Diagonal ↗$\forall i, j, k:\ (X_{ij},\, X_{i+k,\,j-k}) \in \{(0,0),(0,1),(1,0)\}$no two on an up-right diagonal$O(n^3)$
Count$\displaystyle\sum_{i,j} X_{ij} = n$exactly $n$ queens are placed1 (but it touches all $n^2$ variables)

Indices stay on the board ($1 \le i+k \le n$ and so on). The first four families are binary, so they fit the constraint-graph picture of §5; the fifth does not — the count constraint mentions all $n^2$ variables at once. That global constraint is the ugly part of this model.

Common mistake

Forgetting the count constraint. Without it, $X_{ij} = 0$ for every square satisfies every attack constraint — an empty board is a perfectly legal "solution". The attack constraints only say not too many queens; something has to say enough queens. This is a general lesson: a CSP whose constraints are all "forbid" constraints usually has a trivial degenerate solution, and you must add the constraint that rules it out.

Size of the search space. There are $n^2$ variables each with $2$ values, so the number of complete assignments is

$$|{\text{assignments}}| = 2^{n^2}.$$

For $n = 8$ that is $2^{64} \approx 1.845 \times 10^{19}$. If you could test a billion assignments per second you would need about 585 years.

Formulation 2 — one variable per column

Use a modelling trick that appears everywhere in CSPs: build a constraint into the variables themselves. Exactly one queen goes in each column (two would share the column; if some column had none then with $n$ queens in $n$ columns another has two). So instead of "is there a queen here?", ask "which row holds column $k$'s queen?".

Definition

Variables: $Q_1, Q_2, \dots, Q_n$ — one per column. $n$ variables.

Domains: $Q_k \in \{1, 2, \dots, n\}$, the row index of the queen in column $k$. So $d = n$.

Constraints, implicit form: $$\forall\, i \neq j: \quad Q_i \neq Q_j \quad\text{(no shared row)} \qquad\text{and}\qquad |Q_i - Q_j| \neq |i - j| \quad\text{(no shared diagonal)}.$$

Constraints, explicit form: for each pair $(i,j)$, the list of allowed value-pairs. For $n = 4$, for instance, $$(Q_1, Q_2) \in \{(1,3),\,(1,4),\,(2,4),\,(3,1),\,(4,1),\,(4,2)\}.$$

The diagonal condition is the one people mis-derive. Two queens sit at $(i, Q_i)$ and $(j, Q_j)$ in (column, row) coordinates, and they share a diagonal exactly when the horizontal distance equals the vertical distance, $|i - j| = |Q_i - Q_j|$. Forbidding that gives $|Q_i - Q_j| \neq |i-j|$. One absolute-value equation covers both diagonal directions, which is why formulation 2 needs one condition where formulation 1 needed two families.

Verify the explicit list for $n=4$, which the course writes with a "…" and moves on. We need $Q_1 \neq Q_2$ and $|Q_1 - Q_2| \neq |1-2| = 1$ — the two rows must differ by at least $2$:

$Q_1 \backslash Q_2$1234
1✗ same row✗ diagonal✓✓
2✗ diagonal✗ same row✗ diagonal✓
3✓✗ diagonal✗ same row✗ diagonal
4✓✓✗ diagonal✗ same row

Six allowed pairs out of sixteen: $(1,3), (1,4), (2,4), (3,1), (4,1), (4,2)$ — exactly the set above. That was one of the $\binom{4}{2} = 6$ constraints; for $n = 8$ you would fill $\binom{8}{2} = 28$ tables of $64$ cells. The implicit form says the same thing in two lines of algebra and costs two instructions to test instead of a table lookup.

Key idea

Implicit constraint: a formula or piece of code that returns true/false for a tuple of values. Compact, but opaque — the solver cannot look inside it.

Explicit constraint: an enumerated table of allowed tuples. Bulky, but completely transparent — a solver can index it, intersect it, and precompute supports. Real solvers convert small implicit constraints into explicit tables for exactly that reason.

Why formulation 2 wins

Count complete assignments again. Formulation 2 has $n$ variables with $n$ values each:

$$|\text{assignments}| = n^n \qquad\text{versus}\qquad 2^{n^2}.$$
$n$Formulation 1: $2^{n^2}$Formulation 2: $n^n$Ratio
4$2^{16} = 65{,}536$$4^4 = 256$$256\times$
8$2^{64} \approx 1.845 \times 10^{19}$$8^8 = 16{,}777{,}216$$\approx 1.1 \times 10^{12}\times$
16$2^{256} \approx 1.2 \times 10^{77}$$16^{16} \approx 1.8 \times 10^{19}$$\approx 6 \times 10^{57}\times$

For $n = 8$: 16.8 million assignments instead of 18.4 quintillion. Three further things improve, and they matter more than the raw count:

  1. Two constraint families vanish. "One queen per column" and "exactly $n$ queens" are now impossible to violate, and a constraint you cannot violate costs nothing to enforce. The best way to satisfy a constraint is to choose variables that cannot express its violation.
  2. The CSP becomes purely binary. The $n^2$-ary count constraint is gone, so the constraint graph is a clean $K_n$ and every algorithm here applies directly.
  3. Every assignment is closer to a solution. Formulation 1's assignments mostly have the wrong number of queens entirely; in formulation 2 every assignment is a legal-looking board and $40{,}320$ of the $16{,}777{,}216$ are row-distinct permutations. A space dense in near-solutions is what makes filtering effective.
Figure 2 — the same 4-queens solution under both encodings
0010 1000 0001 0100 i=1i=2i=3i=4 j=1j=2j=3j=4 Formulation 1 — 16 Boolean variables X(3,1)=X(1,2)=X(4,3)=X(2,4)=1, rest 0 2^16 = 65,536 assignments to check Q₁=2Q₂=4Q₃=1Q₄=3 1234 Formulation 2 — 4 variables, domain {1,2,3,4} one queen per column is built into the model 4⁴ = 256 assignments to check
What to notice: both pictures show the identical board. On the left the board is described by 16 answers to "is there a queen here?"; on the right by 4 answers to "which row is this column's queen in?". The right-hand description cannot even express a board with two queens in a column, which is why an entire constraint family disappears. Check the solution: rows 2, 4, 1, 3 are distinct ✓, and $|Q_1-Q_3| = 1 \neq 2$, $|Q_1-Q_4|=1 \neq 3$, $|Q_2-Q_4|=1\neq 2$, $|Q_1-Q_2|=2\neq 1$, $|Q_2-Q_3|=3\neq 1$, $|Q_3-Q_4|=2\neq 1$ ✓.
In formulation 2, why is there no constraint saying "exactly $n$ queens are placed"?
Why: a complete assignment in a CSP gives every variable exactly one value. With one variable per column, that is "one queen per column", hence $n$ queens. The constraint is not implicit — it is gone, absorbed into the choice of variables. That absorption is the whole reason formulation 2 is better, and it is a move worth reaching for in every modelling problem you meet.

7. Sudoku as a CSP

Sudoku is the canonical CSP because it is the one you have already solved by hand, using — whether you knew it or not — exactly the algorithms in this chapter.

Definition

Variables: each open (blank) square. The given digits are not variables; they are fixed. A typical puzzle has around 50–60 open squares out of 81.

Domains: $\{1,2,\dots,9\}$, so $d = 9$.

Constraints: a 9-way alldiff for each row, each column, and each $3\times 3$ region — 27 constraints in total. ("Region" is the course's word for what puzzle books call a box or block.)

alldiff$(V_1,\dots,V_9)$ holds exactly when all nine values are pairwise distinct. Each mentions nine variables, so this is a higher-order CSP and the constraint-graph machinery of §5 does not literally apply.

Decomposing into binary constraints

The course's alternative: replace each 9-way alldiff by all $\binom{9}{2} = 36$ pairwise $\neq$ constraints. The CSP is then binary and every algorithm below runs unmodified.

How many? Naively $27 \times 36 = 972$, but that double-counts — two squares in the same row and region generate the same constraint twice. Count by squares: each has $8$ row-peers, $8$ column-peers and $8$ region-peers of which $4$ are already row- or column-peers, so $8+8+4 = 20$ distinct peers, and

$$\frac{81 \times 20}{2} = 810$$

distinct constraints — divide by two because each is shared by the two squares it relates. So $810$ edges over $81$ nodes.

Key idea — the higher-order/binary tradeoff

Higher-order form (27 alldiffs): fewer, larger constraints. Each one can be propagated by a specialised, strong algorithm that sees all nine variables at once.

Binary form (810 inequalities): more, smaller constraints. Generic algorithms (AC-3, forward checking) apply immediately, but propagation is weaker: nine pairwise views of a constraint can miss what the 9-way view catches.

Every higher-order constraint over finite domains can be converted to binary constraints (add auxiliary variables if necessary), so generality is never lost. What is lost is inference strength.

Example — where the binary decomposition is blind

Suppose that after some filtering, three squares in one row have domains

$$D_A = \{1,2\}, \qquad D_B = \{1,2\}, \qquad D_C = \{1,2\}.$$

Under the binary reading, every constraint is satisfiable pairwise: $A=1, B=2$ works; $A=1, C=2$ works; $B=1, C=2$ works. Every arc is consistent (see §13), so filtering reports "no problem" and search continues.

Under the 9-way alldiff reading, three variables need three distinct values but only two values exist between them. By the pigeonhole principle the puzzle is already dead. A dedicated alldiff propagator (Régin's algorithm, via bipartite matching) detects exactly this and fails immediately. beyond syllabus The general phenomenon — that local pairwise consistency misses global infeasibility — is the subject of §15.

One observation for §17: when a human solves Sudoku, "this square can only be a 7, so write 7" is precisely minimum remaining values, and "cross 7 off every peer of this square" is precisely forward checking. You already run these algorithms; this chapter names them.

8. Varieties of CSPs

Everything so far assumed discrete variables with finite domains. With $n$ variables and domain size $d$ there are $d^{\,n}$ complete assignments, so brute-force enumeration is $O(d^{\,n})$ — and it stays exponential no matter how clever we are, because:

Definition

A Boolean CSP is a finite-domain CSP with $d = 2$. The most famous one is Boolean satisfiability (SAT): variables are propositional atoms with domain $\{\text{true},\text{false}\}$, and each constraint is a clause such as $(x_1 \vee \neg x_2 \vee x_3)$, which forbids exactly one of the eight assignments to those three variables.

SAT is NP-complete (Cook–Levin, 1971). Since SAT is a special case of finite-domain CSP, deciding whether a CSP has a solution is NP-hard in general.

So no algorithm below changes the worst-case complexity. Forward checking, AC-3, MRV and LCV are heuristics and pruning rules: they shrink the tree enormously on realistic instances and change nothing at all in the worst case.

The formalism is wider than finite domains, and the slides hand the rest to 07-380. Domains can be discrete but infinite — job scheduling with integer start times — where an explicit table of allowed tuples is not a data structure and you need a constraint language instead, writing things like $\text{Start}_{J_1} + 5 \le \text{Start}_{J_3}$; linear integer constraints are solvable, nonlinear ones are undecidable (Matiyasevich 1970, Hilbert's tenth problem). Or variables can be continuous — Hubble observation windows — where with linear constraints feasibility is solvable in polynomial time by linear programming.

Key idea

The landscape: continuous + linear is polynomial, discrete + finite is NP-hard, discrete + infinite + nonlinear is undecidable. Discreteness, not size, is what makes CSPs hard. Only the finite-domain case is examinable here.

9. Varieties of constraints

Constraints vary along a second axis: arity, how many variables one constraint mentions.

Unary constraints

One variable: "South Australia dislikes green", $\text{SA} \neq \text{green}$. These are free, because a statement about which values one variable may take is a domain. Delete the forbidden values once, before search, and forget the constraint existed — the course's "equivalent to reducing domains". So $D_{\text{SA}} = \{\text{red},\text{green},\text{blue}\}$ becomes $\{\text{red},\text{blue}\}$, in $O(d)$, once, forever.

Binary constraints

Exactly two variables: $\text{SA} \neq \text{NSW}$. The focus of the lecture, because these are exactly the constraints that give a constraint graph, and a graph is a thing algorithms can walk.

Higher-order constraints

Three or more: the Sudoku alldiff of §7, or a cryptarithmetic column (two digits, a carry in and a carry out — four variables). A $k$-ary constraint is a hyperedge on $k$ nodes, giving a hypergraph; the standard move is to decompose into binary constraints.

Soft constraints (preferences)

Everything so far is hard: an assignment satisfies the constraints or it is not a solution. Real problems also have preferences — the course's example is "red is better than green".

Definition

A soft constraint or preference is often represented by attaching a cost to each variable assignment. The cost of a complete assignment is the sum of the costs of its parts, and we seek the cheapest assignment that satisfies all the hard constraints:

$$\text{minimise} \quad \sum_{i=1}^{n} \text{cost}_i(X_i = v_i) \quad\text{subject to all hard constraints being satisfied.}$$

This turns a satisfaction problem into a constrained optimisation problem.

Common mistake

Thinking preferences are a small extension. They are not: they break the defining property of identification problems from §1. Once assignments have costs, the first solution you find is no longer good enough — you must keep searching to prove no cheaper one exists, or use branch-and-bound. Every algorithm below stops at the first solution, and that is only correct because all solutions are equally good.

Concretely: suppose every region costs $0$ for red, $1$ for green and $3$ for blue. The solution from §4 — WA red, NT green, SA blue, Q red, NSW green, V red, T green — costs $0+1+3+0+1+0+1 = 6$. Recolouring T from green to red is still legal (T has no neighbours) and costs $5$, so that solution was valid but not optimal. Under hard constraints only, both are equally correct answers; under preferences, only one is.

Before inventing anything new, check that the search machinery of Chapter 2 already applies. It does, verbatim.

Definition — standard search formulation of a CSP

Three consequences follow immediately:

  1. Solutions appear only at depth $n$: each step assigns one variable, and the goal test demands all $n$.
  2. There are no infinite paths and no cycles — every step strictly increases the number of assigned variables.
  3. The branching factor is $(n-\ell)\,d$ at depth $\ell$: any of the $n - \ell$ unassigned variables, times any of its $d$ values.

That third point is a disaster. Multiplying the branching factors down the tree,

$$\#\text{leaves} \;=\; (n d)\big((n-1)d\big)\cdots(1\cdot d) \;=\; n!\, d^{\,n},$$

and for Australia ($n = 7$, $d = 3$),

$$7! \cdot 3^7 = 5040 \times 2187 = 11{,}022{,}480 \ \text{leaves},$$

while there are only $3^7 = 2187$ distinct complete assignments. Every assignment is reached $5040$ times, once per ordering of the seven variables. Fixing that is Idea 1 of §11.

The naive algorithm: DFS with the goal test at the bottom

The course's starting point is deliberately dumb: assign a value at each node, and check the constraints only when the assignment is complete — the goal test as the black box of general search. So we build $\text{WA}=\text{red},\ \text{NT}=\text{red}$, already illegal, and extend it through five more variables, generating $3^5 = 243$ complete assignments under a prefix that was dead on arrival and rejecting all $243$ one at a time.

Poll 1 — should we use BFS or DFS?

Answer: DFS — and it is not close.

1. BFS's advantage is unavailable. BFS is preferred when the shallowest goal is the cheapest. Every solution here sits at depth $n$, so BFS's optimality guarantee buys nothing.

2. DFS's disadvantage is unavailable too. DFS is incomplete only because it can dive down an infinite path; this tree has depth exactly $n$, so DFS is complete here for free.

3. Memory — the decisive argument. BFS holds a whole level of the frontier, on the order of $d^{\,n}$ nodes (for Australia at least $2187$ partial assignments). DFS stores one root-to-leaf path: $O(n)$ nodes, or $O(nd)$ if it also remembers which values remain at each level. 7 nodes versus thousands, and the gap is exponential in $n$.

4. Time to first solution. BFS reaches its first complete assignment only after enumerating every incomplete one; DFS gets there in $n$ expansions. Any solution is as good as any other, so reaching the bottom fast is exactly what we want.

So: depth-first search, with improvements bolted on.

Why is DFS complete for a CSP even though DFS is incomplete for search problems in general?
Why: DFS fails to be complete only when it can descend forever. Here the state is a partial assignment and every step strictly grows it, so after $n$ steps there is nothing left to assign. A finite tree makes DFS complete. Option A confuses the constraint graph with the search tree (see the warning in §5) — the constraint graph is full of cycles, and it does not matter.

11. Backtracking search

Key idea

Backtracking search is the basic uninformed algorithm for solving CSPs, and $$\textbf{backtracking search} \;=\; \textbf{DFS} \;+\; \textbf{two improvements}.$$

Idea 1 — one variable at a time

Variable assignments are commutative:

$$[\,\text{WA}=\text{red} \text{ then } \text{NT}=\text{green}\,] \quad\text{is the same state as}\quad [\,\text{NT}=\text{green} \text{ then } \text{WA}=\text{red}\,].$$

Both are the set $\{\text{WA}=\text{red},\ \text{NT}=\text{green}\}$, and a set has no order, so a search exploring both orderings explores each state $\ell!$ times at depth $\ell$. The fix is one line of code: at each node, fix one variable and branch only on its values. Which variable is up to you; what matters is that you pick one and do not consider the others at this node.

Why this is sound

At the root we now generate $3$ children instead of $21$. Could we lose a solution? No. Let $S$ be any solution, a complete assignment, and suppose we branch on $\text{WA}$. $S$ gives $\text{WA}$ some value $v$, and the child $\text{WA}=v$ exists because we generate a child for every value in $D_{\text{WA}}$ — so $S$ lies in its subtree. Recurse: at every node, whichever variable we branch on, $S$ assigns it some value and that branch exists. By induction $S$ is reachable.

What we discarded were alternative orderings of the same assignments, never alternative assignments, so completeness is preserved exactly. The proof also shows that any variable-selection rule is sound — which is what licenses MRV in §17. $\square$

Quantifying the saving. Branching factor drops from $nd$ to $d$; the leaf count drops from $n!\,d^{\,n}$ to $d^{\,n}$; that is a factor of $n!$.

InstanceNaive leaves $n!d^n$One-variable-at-a-time $d^n$Saving $n!$
Australia, $n=7, d=3$$11{,}022{,}480$$2{,}187$$5{,}040\times$
8-queens, $n=8, d=8$$\approx 6.8\times 10^{11}$$16{,}777{,}216$$40{,}320\times$
Sudoku, $n=55, d=9$$\approx 10^{126}$$\approx 10^{52}$$\approx 10^{73}\times$

Idea 2 — check constraints as you go

Consider only values that do not conflict with previous assignments. Testing each candidate against the already-assigned neighbours costs a little computation, but nothing compared with the subtree avoided. The course calls this the incremental goal test: a small test at every node instead of one big test at depth $n$. Concretely, with $\text{WA}=\text{red}$ assigned, we simply never generate the child $\text{NT}=\text{red}$ — which naive DFS would have grown $3^5 = 243$ complete assignments beneath before noticing.

Figure 3 — naive DFS versus backtracking, top two levels
Naive: branch on every (variable, value) Backtracking: branch on one variable's values WA=rWA=gWA=bNT=r NT=gNT=bQ=r… 21 children at the root; 21 ways to write the same two-variable state at depth 2. n! · dⁿ = 11,022,480 leaves WA=redWA=greenWA=blue NT=greenNT=blue NT=red never generated 3 children at the root; each state written once. dⁿ = 2,187 leaves, minus everything pruned
What to notice: Idea 1 removes the horizontal redundancy (21 children collapse to 3 because ordering does not matter), and Idea 2 removes the vertical waste (the crossed-out child would have grown a subtree of 243 doomed leaves). The two improvements attack different kinds of waste, which is why you want both.

The algorithm in full

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


function RECURSIVE-BACKTRACKING(assignment, csp) returns a solution, or failure

    if assignment is complete then
        return assignment                          # every variable has a value, and
                                                   # every value was checked on the way in

    var  <- SELECT-UNASSIGNED-VARIABLE(VARIABLES[csp], assignment, csp)     # decision point 1

    for each value in ORDER-DOMAIN-VALUES(var, assignment, csp) do          # decision point 2

        if value is consistent with assignment given CONSTRAINTS[csp] then  # decision point 3
            add {var = value} to assignment
            result <- RECURSIVE-BACKTRACKING(assignment, csp)
            if result != failure then
                return result
            remove {var = value} from assignment   # <- this line is the "backtrack"

    return failure                                 # no value for var works; tell the caller

Three lines are worth reading twice.

Say it in one line

Backtracking = DFS + variable ordering + fail-on-violation. And the decision points — the three places where a smarter policy can be dropped in without touching correctness — are: (1) which variable next (§17, MRV), (2) which value first (§17, LCV), and (3) how hard to look for trouble at each node (§12 forward checking, §14 AC-3). A fourth, beyond this course, is (4) how far to jump back on failure — plain backtracking undoes one assignment; backjumping undoes several at once. beyond syllabus

With just these two improvements, backtracking search solves n-queens for $n \approx 25$. That is the honest number from the course: not thousands, twenty-five. Hold onto it — by §17 the same basic algorithm will handle $n = 1000$.

12. Filtering, part 1: forward checking

Backtracking still wastes effort in a specific, diagnosable way. The state $\{\text{WA}=\text{red},\ \text{Q}=\text{green},\ \text{V}=\text{blue}\}$ is perfectly consistent — no two of WA, Q, V even share a border — but it is already dead: SA borders all three, so SA can be neither red nor green nor blue. Plain backtracking will not discover this until it actually gets to SA, after building every combination of NT and NSW above it.

Key idea — filtering

Filtering: keep track of the domains of the unassigned variables, and cross off bad options as you go. Failure is detected the moment some variable has no values remaining — which can be long before that variable is reached by the search.

Definition — forward checking

The simplest filtering rule. After a variable is assigned a value, check the constraints it participates in and cross off the values of the unassigned variables that violate them. For map colouring: after $X = c$, delete $c$ from the domain of every unassigned neighbour of $X$. If any domain becomes empty, fail and backtrack immediately.

Analogy

Forward checking is the pencil marks in a Sudoku grid. You do not just write a 7 into a square; you immediately rub the little 7 out of every square in that row, column and box. The instant a square has no pencil marks left, you know you went wrong earlier — without having to reach that square in your solving order.

The Australia trace

Assign $\text{WA}=\text{red}$, then $\text{Q}=\text{green}$, then $\text{V}=\text{blue}$, maintaining domains. Every domain starts $\{\text{R},\text{G},\text{B}\}$. Borders: WA–NT, WA–SA, NT–SA, NT–Q, SA–Q, SA–NSW, SA–V, Q–NSW, NSW–V.

Figure 4 — the forward-checking domain table, row by row
VariableInitialafter WA = redafter Q = greenafter V = blue
WAR G BRRR
NTR G BR G BR G BR G B
QR G BR G BGG
NSWR G BR G BR G BR G B
VR G BR G BR G BB
SAR G BR G BR G BR G B  ∅ FAIL
TR G BR G BR G BR G B
What to notice: two things. (1) The failure is detected at SA without SA ever being assigned — that is the whole value of filtering. (2) Look at the "after Q = green" column: NT and SA are both down to $\{$B$\}$, and NT and SA share a border, so the CSP was already unsolvable at step 2. Forward checking cannot see this, because it only ever compares an unassigned variable against an assigned one, never two unassigned variables against each other. Fixing that blind spot is exactly what arc consistency does in §13.
Common mistake

Forgetting to restore the crossed-off values when you backtrack. The deletions made after assigning $\text{V}=\text{blue}$ are valid only while that assignment stands. When the search undoes $\text{V}=\text{blue}$ and tries $\text{V}=\text{green}$, SA must get its B back. In code, either save a copy of the domains at each node, or keep a trail of (variable, value) deletions to undo. A filtering bug of this kind does not crash — it silently reports "no solution" for solvable problems, which is much worse.

Guided problem 1Run forward checking to the first failure

Same Australia CSP, same three colours, but a different assignment order. Apply forward checking after each of these assignments, in order, and report the first failure:

NT = red,   NSW = green,   V = blue

Borders again: WA–NT, WA–SA, NT–SA, NT–Q, SA–Q, SA–NSW, SA–V, Q–NSW, NSW–V.

Step 1 — Which domains does NT = red touch?

NT's neighbours are WA, SA and Q. Delete red from each of them.

WA: $\{$G, B$\}$  ·  SA: $\{$G, B$\}$  ·  Q: $\{$G, B$\}$. Everything else still $\{$R, G, B$\}$. No empty domain, continue.

Step 2 — Now NSW = green

NSW's neighbours are SA, Q and V. Delete green from each.

SA: $\{$G, B$\} \to \{$B$\}$  ·  Q: $\{$G, B$\} \to \{$B$\}$  ·  V: $\{$R, G, B$\} \to \{$R, B$\}$.

Still no empty domain. But pause and look: SA and Q are both down to $\{$B$\}$, and SA–Q is a border. The problem is already dead — and forward checking has no idea, for exactly the reason flagged in Figure 4.

Step 3 — Now V = blue

Check it is allowed first: V's assigned neighbours are NSW (green). Blue is in $D_V = \{$R, B$\}$, so the assignment is consistent and we make it.

V's unassigned neighbour is SA. Delete blue from SA: $\{$B$\} \to \{\,\}$.

Empty domain at SA. FAIL. Backtrack on V.

Answer & the takeaway

Final domain table before the failure is announced:

WANTQNSWVSAT
domainG BRBGB∅R G B

The first failure is at SA, after the third assignment.

Takeaway: the failure was detectable after the second assignment (SA and Q both forced to blue across a border), but forward checking only ever propagates assigned → unassigned. It took a third assignment to make the contradiction visible to it. One wasted level of search does not sound like much; multiply by $d$ at every node above and it is a large constant factor. That is the gap arc consistency closes.

13. Consistency of a single arc

Forward checking is a special case of something more general, and naming the general thing lets us strengthen it. The general thing is arc consistency.

Definition

Let $X$ and $Y$ be two variables joined by a constraint, and consider the directed arc $X \to Y$. The arc has a tail ($X$) and a head ($Y$).

The arc $X \to Y$ is consistent iff for every value $x$ in the tail's domain $D_X$, there is some value $y$ in the head's domain $D_Y$ such that the pair $(x,y)$ does not violate the constraint between $X$ and $Y$.

$x$ ranges over the values left in $D_X$, $y$ over those left in $D_Y$, and "does not violate" means the pair is allowed by the constraint (for map colouring, $x \neq y$). A $y$ that works for a given $x$ is a support for $x$. So: the arc $X \to Y$ is consistent iff every value in the tail has at least one support in the head.

ENFORCING AN ARC DELETES FROM THE TAIL

To enforce arc consistency on $X \to Y$: remove from $D_X$ every value that has no support in $D_Y$.

The head's domain is not touched. Not shrunk, not grown, not read-and-modified — only read.

The lecture prints "Remember: delete from the tail!" at the bottom of fifteen consecutive slides. That is not decoration; it is the single most common place to go wrong by hand. Arc $X \to Y$ ⟹ values die in $X$.

Figure 5 — one REVISE call: SA → WA
SA  (tail) WA  (head) redgreenblue red no support SA → WA constraint: SA ≠ WA red in SA: needs some y ≠ red in D(WA) = {red}. None exists → delete red from SA. green in SA: y = red works ✓   ·   blue in SA: y = red works ✓  → both survive. DELETE FROM THE TAIL — WA's domain is untouched.
What to notice: the arrow points at WA, but the damage happens at SA. A useful mnemonic: the arrow is a question the tail asks the head — "given what you can still be, can I still be this?" — and only the asker changes its mind.

Forward checking, re-read as arc consistency

The course writes forward checking as

$$\text{enforce } X \to Y \quad\text{for all } (X,Y) \in E \text{ with } Y \text{ newly assigned.}$$

Unpack it. $Y$ was just assigned, so $D_Y$ is a singleton $\{c\}$, and $X$ ranges over $Y$'s neighbours. Enforcing $X \to Y$ deletes from $D_X$ every value with no support in $\{c\}$ — and for a $\neq$ constraint the only value without support is $c$ itself. So those arcs delete exactly $c$ from each neighbour's domain: forward checking, verbatim.

Key idea

Forward checking = enforce arc consistency on the arcs pointing at the newly assigned variable, once.

Full arc consistency = enforce it on every arc, repeatedly, until nothing changes.

Full arc consistency is strictly stronger: it makes every deletion forward checking makes, and possibly more, for two separate reasons worth keeping apart.

  1. More arcs. Forward checking only examines arcs $X \to Y$ with $Y$ just assigned, never an arc between two unassigned variables. Arc consistency examines all of them — which is exactly how it catches "NT and SA are both forced to blue and share a border".
  2. Propagation. When forward checking removes a value from $D_X$ it stops. Arc consistency asks whether shrinking $D_X$ destroyed the support of some value in a third variable $W$ neighbouring $X$; if so that value dies too, which may destroy support elsewhere, and so on. Deletions cascade. Forward checking has no cascade — it is one layer deep.
Why "strictly stronger" is literally true

Let $D^{\text{FC}}$ be the domains after forward checking on the assignment of $Y$, and $D^{\text{AC}}$ the domains after enforcing full arc consistency from the same starting point. Claim: $D^{\text{AC}}_X \subseteq D^{\text{FC}}_X$ for every $X$.

The arcs forward checking enforces are a subset of the arcs arc consistency enforces, and enforcing an arc only ever removes values. A value removed by enforcing some arc is removed by any procedure that enforces that arc and reaches a fixed point. Hence every value FC removes, AC also removes; AC may additionally remove values via arcs FC never looked at. $\square$

"Strictly": the Australia trace of §14 exhibits a case where AC empties a domain and FC does not. Corollary: arc consistency detects failure at least as early as forward checking, and never later.

You enforce arc consistency on the arc $\text{NSW} \to \text{SA}$, where $D_{\text{NSW}} = \{\text{R},\text{G},\text{B}\}$ and $D_{\text{SA}} = \{\text{B}\}$. What changes?
Why: NSW is the tail, so NSW is where values die. Blue in NSW has no support, because the only value left in the head SA is blue and $\text{B} = \text{B}$ violates the constraint. Red and green both find the support $y = \text{B}$, so they stay. SA is the head and is only read. If you want SA to lose values you must enforce the other arc, $\text{SA} \to \text{NSW}$ — arcs are directed and both directions of every edge must eventually be processed.

14. AC-3: enforcing arc consistency on the whole CSP

Knowing how to enforce one arc, we need every arc consistent at once. The difficulty is that the arcs interact: enforcing $X \to Y$ shrinks $D_X$, which can break an arc $W \to X$ that was consistent a moment ago. One pass is not enough.

The simplistic algorithm

repeat
    changed <- false
    for each ordered pair (X, Y) of variables joined by a constraint do
        if enforcing X -> Y removes any value then changed <- true
until changed = false

Correct — it stops only at a fixed point, and a fixed point is by definition arc-consistent — and wasteful: one full cycle costs $O(|E| d^2)$ even if the only thing that changed was a single value, and you may need many cycles.

AC-3

Definition

AC-3 (Arc Consistency algorithm #3, Mackworth 1977) is the same idea with a queue of arcs still to check, so that it ignores constraints that have not been modified since they were last analysed. An arc goes back on the queue only when something it depends on has actually changed.

function AC-3(csp, queue) returns false if an inconsistency is found, true otherwise
    # queue defaults to ALL directed arcs (Xi, Xj) of the constraint graph --
    # both directions of every edge

    while queue is not empty do
        (Xi, Xj) <- REMOVE-FIRST(queue)

        if REVISE(csp, Xi, Xj) then                 # Di actually shrank
            if size of Di = 0 then
                return false                        # wipeout: this CSP branch is dead

            for each Xk in NEIGHBORS(Xi) \ {Xj} do
                add (Xk, Xi) to queue               # Di shrank, so everyone who
                                                    # depends on Di must be re-checked
    return true


function REVISE(csp, Xi, Xj) returns true iff we removed something from Di
    revised <- false
    for each x in Di do
        if no value y in Dj satisfies the constraint between Xi and Xj given x then
            delete x from Di                        # DELETE FROM THE TAIL
            revised <- true
    return revised
The enqueue rule — the only subtle line

When a value is deleted from $D_X$, re-enqueue the arc $W \to X$ for every neighbour $W$ of $X$.

Why into $X$ and not out of it? Because $D_X$ is the head of the arcs $W \to X$, and a head's domain is what supplies supports, so shrinking $D_X$ can destroy the support of some value in $D_W$. Arcs out of $X$ are unaffected: a smaller tail cannot create new unsupported values in some other variable.

The lecture enqueues all neighbours $W$, including the head $X_j$ just compared against; the textbook writes $\text{NEIGHBORS}(X_i) \setminus \{X_j\}$. Both are correct — see below — and skipping is a small optimisation. The trace follows the lecture.

Why it is safe to skip the arc back to the head

Suppose REVISE$(X_i, X_j)$ deleted the value $x$ from $D_i$. Could that deletion cost some $y \in D_j$ its support, making $X_j \to X_i$ newly inconsistent? Only if $y$'s only support in $D_i$ had been $x$, i.e. $(y,x)$ satisfies the constraint. But $x$ was deleted precisely because no value of $D_j$ supports it: for every $y' \in D_j$, $(x,y')$ violates the constraint. Taking $y' = y$ gives that $(x,y)$ violates it, contradicting $(y,x)$ satisfying it — for binary constraints $(x,y)$ is allowed iff $(y,x)$ is, each read in the right order.

So no value of $D_j$ ever depended on a value we deleted from $D_i$, and $X_j \to X_i$ cannot have been broken. $\square$

The lecture's Australia trace, compressed

We are inside backtracking search, running AC-3 after each assignment, with WA = red and then Q = green.

Phase 1 — AC-3 after WA = red. $D_{\text{WA}} = \{$R$\}$, everything else $\{$R,G,B$\}$. The queue is seeded with the arcs pointing at the newly assigned variable, $\text{SA}\to\text{WA}$ and $\text{NT}\to\text{WA}$. Each deletes R from its tail — $D_{\text{SA}} = D_{\text{NT}} = \{$G,B$\}$ — and each enqueues every arc into the variable that shrank (WA→SA, NT→SA, Q→SA, NSW→SA, V→SA; then WA→NT, SA→NT, Q→NT). All of those pass: WA→SA finds R supported by G $\in D_{\text{SA}}$, NT→SA compares $\{$G,B$\}$ with $\{$G,B$\}$ and finds G supported by B and B by G, and so on. Nothing more is deleted, so nothing more is enqueued and the queue drains. The CSP is arc-consistent and search continues, leaving $D_{\text{WA}} = \{$R$\}$, $D_{\text{NT}} = D_{\text{SA}} = \{$G,B$\}$ and $D_{\text{Q}} = D_{\text{NSW}} = D_{\text{V}} = D_{\text{T}} = \{$R,G,B$\}$.

Phase 2 — AC-3 after Q = green. Set $D_{\text{Q}} = \{$G$\}$ and seed the queue with the arcs into Q: $\text{NT}\to\text{Q}$, $\text{SA}\to\text{Q}$, $\text{NSW}\to\text{Q}$.

Arc poppedREVISE resultEnqueued
NT → QG has no support in $\{$G$\}$ → delete G from NT. $D_{\text{NT}} = \{$B$\}$.every arc into NT: WA→NT, SA→NT, Q→NT
SA → QG unsupported → delete G from SA. $D_{\text{SA}} = \{$B$\}$.every arc into SA
NSW → QG unsupported → delete G from NSW. $D_{\text{NSW}} = \{$R,B$\}$.every arc into NSW
WA → NT$\{$R$\}$ against $\{$B$\}$: R is supported by B. No change.—
SA → NT$D_{\text{SA}} = \{$B$\}$, $D_{\text{NT}} = \{$B$\}$. Is B supported? The only $y \in D_{\text{NT}}$ is B, and B = B violates SA ≠ NT. Delete B from SA. $D_{\text{SA}} = \varnothing$  !!!AC-3 returns false — wipeout.

Note the mechanism, because it is the whole point: the deletion at NT in the first step put SA→NT onto the queue, and processing that re-queued arc is what found the contradiction. That cascade is what forward checking does not have.

Conclusion of the trace

Backtrack on the assignment of Q. AC-3 has proved that no completion of $\{\text{WA}=\text{red},\ \text{Q}=\text{green}\}$ exists, so the search must undo $\text{Q}=\text{green}$ and try $\text{Q}=\text{blue}$ instead — without ever assigning NT, SA, NSW, V or T.

Compare with §12: forward checking, given the same two assignments, would have produced $D_{\text{NT}} = \{$B$\}$, $D_{\text{SA}} = \{$B$\}$, $D_{\text{NSW}} = \{$R,B$\}$ — no empty domain — and continued happily. It needed a third assignment before it noticed. Arc consistency detects failure earlier than forward checking.

Where AC-3 fits, and what it costs

AC-3 runs either as a preprocessor — once on the raw CSP before any search, cheaply shrinking every domain permanently, and occasionally proving the whole problem infeasible before you begin — or after each assignment inside the recursion, seeded with the arcs into the just-assigned variable, with its effects undone on backtrack.

Answer — what is the downside of enforcing arc consistency?

Cost. One call to AC-3 is $O(n^2 d^3)$ (derived in §16), and backtracking search calls it at every node. Forward checking, by contrast, costs $O(\text{deg}(X) \cdot d) $ — a handful of deletions. You are buying pruned subtrees with CPU time, and the exchange rate is not always favourable.

Three specific ways the trade can go badly:

There is also a second, subtler downside, and it is the whole of the next section: arc consistency can succeed and still be wrong. An arc-consistent CSP may have no solution at all, so passing the AC-3 test proves nothing.

Guided problem 2Hand-run AC-3 on a three-variable CSP

Variables $A, B, C$ with domains $D_A = D_B = D_C = \{1,2,3\}$ and two constraints:

$A \lt B$   and   $B \lt C$.

The constraint graph is the path $A - B - C$. Run AC-3 with the queue seeded with all four directed arcs, in the order $A{\to}B$, $B{\to}A$, $B{\to}C$, $C{\to}B$. Report the final domains.

Step 1 — Arc $A \to B$

Tail $A$, head $B = \{1,2,3\}$. For each $a \in D_A$ we need some $b \in D_B$ with $a \lt b$.

  • $a=1$: $b=2$ works ✓
  • $a=2$: $b=3$ works ✓
  • $a=3$: need $b \gt 3$, none exists ✗ → delete 3

$D_A = \{1,2\}$. Something changed, so enqueue the arcs into $A$: only $B \to A$ (which is already queued; a duplicate is harmless).

Queue: $B{\to}A$, $B{\to}C$, $C{\to}B$, $(B{\to}A)$.

Step 2 — Arc $B \to A$

Careful with direction: the constraint is $A \lt B$, so from $B$'s side each $b$ needs some $a \in D_A = \{1,2\}$ with $a \lt b$.

  • $b=1$: need $a \lt 1$, none ✗ → delete 1
  • $b=2$: $a=1$ ✓
  • $b=3$: $a=1$ ✓

$D_B = \{2,3\}$. Enqueue arcs into $B$: $A \to B$ and $C \to B$.

Step 3 — Arc $B \to C$

Constraint $B \lt C$; tail $B = \{2,3\}$, head $C = \{1,2,3\}$.

  • $b=2$: $c=3$ ✓
  • $b=3$: need $c \gt 3$, none ✗ → delete 3

$D_B = \{2\}$. Enqueue arcs into $B$ again: $A \to B$, $C \to B$.

Step 4 — Arc $C \to B$, then the re-queued arcs

$C \to B$: each $c$ needs some $b \in D_B = \{2\}$ with $b \lt c$.

  • $c=1$: $2 \lt 1$? no ✗ → delete 1
  • $c=2$: $2 \lt 2$? no ✗ → delete 2
  • $c=3$: $2 \lt 3$ ✓

$D_C = \{3\}$. Enqueue arcs into $C$: $B \to C$.

Now $A \to B$ with $D_A = \{1,2\}$, $D_B = \{2\}$: $a=1$ needs $1 \lt 2$ ✓; $a=2$ needs $2 \lt 2$ ✗ → delete 2. $D_A = \{1\}$. Enqueue $B \to A$.

Remaining arcs ($B \to C$, $B \to A$, and any duplicates) now find everything supported: $D_A=\{1\}, D_B=\{2\}, D_C=\{3\}$ with $1 \lt 2 \lt 3$. Nothing changes, queue drains.

Answer & the takeaway

Final domains: $D_A = \{1\}$, $D_B = \{2\}$, $D_C = \{3\}$. Every domain is a singleton, so arc consistency alone has solved the CSP — zero backtracking was required.

Takeaway: that was not luck. This constraint graph is a tree (a path is a tree), and for tree-structured CSPs, enforcing arc consistency in the right order guarantees a backtrack-free solve. That is the structure result flagged in §18. Notice also how the deletion at $B$ in step 3 propagated back to $A$ in step 4 — the cascade forward checking does not have.

15. The limitations of arc consistency

AC-3 returning true feels like a certificate. It is not one. After enforcing arc consistency you may be in any of three situations:

Case 1

One solution left. Every domain is a singleton and they are mutually consistent — as in Guided problem 2. Read the answer off the domains; you are done.

Case 2

Many solutions left. Domains have shrunk but several still hold multiple values. You have a smaller CSP, not an answer. Keep searching.

Case 3

No solutions left — and you do not know it. Every arc is consistent, every domain is non-empty, and yet no complete assignment satisfies all constraints. AC-3 reports success on an unsolvable problem.

Case 3 is the one to internalise, and it is what the lecture's "What went wrong here?" slide is pointing at.

Example — arc-consistent with no solution

Three variables $X$, $Y$, $Z$. Domains $D_X = D_Y = D_Z = \{\text{red}, \text{green}\}$. Constraints: $X \neq Y$, $Y \neq Z$, $X \neq Z$ — a triangle.

Every arc is consistent. Check one and the rest follow by symmetry. Arc $X \to Y$: does red in $D_X$ have a support? Yes — green in $D_Y$, and red ≠ green. Does green in $D_X$ have a support? Yes — red in $D_Y$. So nothing is deleted. The same holds for all six directed arcs. AC-3 drains its queue without a single deletion and returns true.

There is no solution. The three variables must take pairwise distinct values, so we need three distinct values; we have two. By the pigeonhole principle, two of $X, Y, Z$ share a value, and those two are constrained to differ. Contradiction. (Equivalently: a triangle is a 3-clique and needs 3 colours; we offered 2.)

What went wrong: nothing went wrong locally. Every pair is individually satisfiable. The contradiction lives in a three-variable interaction, and arc consistency — by construction — only ever looks at two variables at a time.

Figure 6 — fully arc-consistent, and unsolvable
XYZ ≠≠≠ domains {red, green}; all three pairs must differ Every arc passes REVISE: red in X → supported by green in Y ✓ green in X → supported by red in Y ✓ … and identically for all six arcs. Yet every assignment fails: RRR ✗   RRG ✗   RGR ✗   RGG ✗ GRR ✗   GRG ✗   GGR ✗   GGG ✗ 3 variables, pairwise distinct, 2 values. Pigeonhole: impossible. AC-3 cannot see it.
What to notice: all 8 complete assignments are listed and every one fails, yet no local check can detect it. Arc consistency is a 2-variable property; this contradiction is a 3-variable one. Strengthening to path consistency (3-consistency) would catch this triangle — and would itself be blind to a 4-variable version. There is no finite level of local consistency that decides every CSP; that would contradict NP-hardness. beyond syllabus
Three things to remember
AC-3 finishes on a CSP with every domain non-empty. What may you conclude?
Why: the triangle above is arc-consistent with non-empty domains and has zero solutions, which kills option A. Option B is also wrong: singleton domains that happen to violate a constraint are possible in general — the reliable statement is only that arc consistency has removed every value that fails a pairwise support test. Any conclusion about solvability must come from the search, not from the filter.

16. The cost of one run of AC-3

The lecture derives the bound in four steps, each one line.

Derivation

Step 1 — an arc is enqueued only after a value is removed. Look at the pseudocode: add (Xk, Xi) to queue sits inside if REVISE(...) then, and REVISE returns true only when it actually deleted something. (The one exception is the initial queue, handled in step 3.) So "how many arcs get processed?" reduces to "how many deletions can happen?".

Step 2 — there are at most $O(nd)$ removals in the whole run. There are $n$ variables, each with a domain of at most $d$ values, so there are at most $nd$ values in existence. Every removal destroys one value permanently — AC-3 never puts a value back — so there can be at most $nd$ removals ever. This is the crucial monotonicity argument: domains only shrink, which is what makes the algorithm terminate at all.

Step 3 — each removal enqueues at most $n$ arcs, so $O(n^2 d)$ arc insertions. When a value dies in $D_{X_i}$, we enqueue one arc per neighbour of $X_i$. A variable has fewer than $n$ neighbours (at most $n-1$), so each removal adds $\le n$ arcs. Multiply by the $O(nd)$ removals: $O(n^2 d)$ insertions. The initial queue contributes $2|E| \le n(n-1) = O(n^2)$ arcs, which is dominated by $O(n^2d)$ whenever $d \ge 1$. Each insertion is processed at most once, so the while loop runs $O(n^2d)$ times.

Step 4 — processing one arc costs $O(d^2)$. REVISE$(X_i, X_j)$ loops over the $\le d$ values $x \in D_i$ and, for each, scans the $\le d$ values $y \in D_j$ looking for a support. That is $d \times d$ constraint checks in the worst case, each $O(1)$.

Multiply. $$\underbrace{O(n^2 d)}_{\text{arcs processed}} \times \underbrace{O(d^2)}_{\text{cost per arc}} \;=\; \boxed{O(n^2 d^3)}$$ for a single run of AC-3.

Sanity-check the factors against Australia: $n = 7$, $d = 3$, giving $n^2 d^3 = 49 \times 27 = 1323$ constraint checks as an upper bound for one call. The trace in §14 used about 15 arcs and a few dozen checks — the bound is loose, as worst-case bounds are, but it is the right shape.

A common way to write the same bound

Textbooks often state AC-3 as $O(e\, d^3)$ where $e = |E|$ is the number of edges (constraints) rather than $O(n^2d^3)$. The two agree: a binary constraint graph on $n$ nodes has $e \le \binom{n}{2} = O(n^2)$ edges, and the derivation above used "$\le n$ neighbours" exactly where a finer count would use the degree. For sparse graphs — a road map, a schedule — $e$ is closer to $O(n)$ and AC-3 is much faster than the $n^2$ form suggests.

The tightened bound. beyond syllabus Zhang and Yap (2001) showed that the true worst case of AC-3 is $O(n^2 d^2)$, a factor of $d$ better, by not repeating the $d^2$ support scan from scratch on every visit to an arc. The lecture marks this "not required"; the classroom answer is $O(n^2d^3)$.

Remember what this cost multiplies

This is the cost of one run. The whole backtracking algorithm with AC-3 calls AC-3 many times — once per node of the search tree, and the tree can have $O(d^n)$ nodes. The total is therefore $O(d^n \cdot n^2 d^3)$ in the worst case, which is worse than plain backtracking's worst case. AC-3 is a bet that the pruning saves more nodes than the filtering costs. On structured problems it wins enormously; on loose ones it loses. See the downside box in §14.

Where do you run AC-3? — the lecture's question, answered

Answer: in two places.

1. Once as a preprocessor, before search starts, with the queue seeded with all directed arcs. This is pure profit: one $O(n^2d^3)$ call, and every value it deletes stays deleted at every node, forever. If it returns false, the CSP is unsatisfiable and you never search at all.

2. After each assignment, inside the recursion, seeded only with the arcs $W \to X$ for the neighbours $W$ of the just-assigned $X$. Seeding all arcs would be correct but wasteful: the only domain that just changed is $D_X$, so the only arcs that can newly fail are those whose head is $X$ — everything else follows by propagation.

function BACKTRACKING-WITH-AC3(csp) returns a solution, or failure
    if AC-3(csp, queue = ALL-ARCS(csp)) = false then
        return failure                                  # preprocessing pass: whole CSP is dead
    return RECURSE({}, csp)


function RECURSE(assignment, csp) returns a solution, or failure
    if assignment is complete then return assignment

    var <- SELECT-UNASSIGNED-VARIABLE(...)              # MRV (see section 17)

    for each value in ORDER-DOMAIN-VALUES(var, ...) do  # LCV (see section 17)

        if value is consistent with assignment then
            add {var = value} to assignment
            saved <- copy of all current domains        # so we can undo the filtering
            D[var] <- {value}

            queue <- { (W, var) : W in NEIGHBORS(var) } # only the arcs into var
            if AC-3(csp, queue) then                    # no domain wiped out
                result <- RECURSE(assignment, csp)
                if result != failure then return result

            restore all domains from saved              # undo filtering, then
            remove {var = value} from assignment        # undo the assignment

    return failure

Two details are easy to get wrong and both are fatal:

17. Ordering: MRV and LCV

Filtering answered "can we detect inevitable failure early?". Ordering answers the lecture's other two questions about improving backtracking: which variable should be assigned next (the SELECT-UNASSIGNED-VARIABLE hook) and in what order should its values be tried (the ORDER-DOMAIN-VALUES hook). Both are pure speed — correctness does not depend on them, since the soundness proof in §11 allowed any choice. And, the part students consistently get backwards, the two questions have opposite answers.

Variable ordering: minimum remaining values

Definition

Minimum remaining values (MRV): choose the unassigned variable with the fewest legal values left in its filtered domain. Also called the most constrained variable heuristic, or "fail-fast" ordering.

MRV needs filtering to be running — "values left in its domain" is only meaningful if someone is crossing values off. In practice MRV is always paired with forward checking or AC-3.

Answer — why min rather than max?

Because you must satisfy every variable, so the only question is when you find out you cannot. Ordering never changes whether a solution exists; it changes the depth at which failure becomes visible. Put the variable most likely to fail as close to the root as possible.

Reason 1 — the pruned subtree is exponentially bigger near the top. Discovering a doomed variable at depth $k$ discards about $d^{\,n-k}$ leaves: $d^{\,n-1}$ at depth 1, only $d$ at depth $n-1$. If SA is unsatisfiable under the current filtering, assigning it first fails once; assigning it last re-discovers the same failure at the bottom of every branch above it — up to $3^5 = 243$ separate times, each after five wasted assignments.

Reason 2 — the branching factor at this node is smallest. The node has exactly $|D_X|$ children, so the smallest $|D_X|$ makes the tree narrowest at the top. Compare $2 \times 9$ with $9 \times 2$: the same 18 leaves, reached through 2 internal nodes instead of 9.

Reason 3 — the $|D_X| = 0$ case is free. MRV selects an empty domain (0 is the minimum), the for loop over its values runs zero times, and we return failure immediately. MRV subsumes the wipeout check.

What max remaining values would do: put the easy, flexible variables at the top and the bottleneck at the bottom — the worst possible shape. You would branch widely on variables that were never going to cause trouble, then hit the same contradiction over and over at the leaves.

Tie-breaking. Several variables often tie on domain size — at the start of Australia all seven have three values. The standard tie-breaker is the degree heuristic of §5: among the tied variables, pick the one with the most constraints on unassigned variables. In Australia that is SA (degree 5), precisely the variable we want at the root.

Value ordering: least constraining value

Definition

Least constraining value (LCV): given a choice of variable, try its values in order of how few options each rules out for the remaining variables — the least constraining value first.

Note that it may take some computation to determine this: to know how many options a value rules out you effectively have to re-run the filtering for each candidate value and count the deletions.

Answer — why least rather than most?

Because you only need ONE value to work. A variable node in the search tree is a disjunction: the subtree succeeds if any child succeeds. So try the value most likely to work first — and the value that leaves its neighbours the most options is the one under which solutions are most likely to survive.

What happens if you get it backwards. The most constraining value maximises the chance that the first child fails. But that failure ends nothing — you still have to go back and try the other values. You have simply guaranteed that the first attempt is wasted.

Caveat, stated honestly. LCV only helps when you are looking for one solution and a solution exists. If the subproblem has no solution at all, or you are enumerating all solutions, you must try every value regardless and the order is irrelevant. LCV is a bet on success; MRV is a bet on failure. Both bets pay off on realistic instances, which is why they are used together.

THE ASYMMETRY — read this twice

These two heuristics point in opposite directions, and that is not an inconsistency. It follows from the logical structure of the search tree.

Choosing a variableChoosing a value
Logical formAND — every variable must get a valueOR — some value must work
Node typeyou will visit all of them eventuallyyou stop at the first child that succeeds
What you wantfail fastsucceed fast
So pick themost constrained (fewest values)least constraining (kills fewest options)
HeuristicMRVLCV
Payoff whenthe branch is doomed — you learn it at the topthe branch is solvable — you land on it first

One sentence to memorise: you must satisfy every variable, so look for trouble; you only need one value, so look for the easiest one. If you ever find yourself writing "choose the most constraining value", you have mixed up the AND with the OR.

Example — LCV computed by hand

Australia, with $\text{WA} = \text{red}$ and $\text{Q} = \text{green}$ already assigned and forward checking applied, so:

NT {B}   SA {B}   NSW {R,B}   V {R,G,B}   T {R,G,B}

Say we are choosing a value for NSW, whose domain is $\{$R, B$\}$. Count the deletions each choice would cause among unassigned neighbours (SA and V):

LCV therefore orders red before blue, and red is indeed the only one that can possibly lead anywhere. Note the cost: producing this ranking required simulating the filtering twice, once per candidate value — that is the "some computation" the slides warn about.

The payoff. The lecture's headline: these ordering ideas make 1000-queens feasible, against the $n \approx 25$ plain backtracking managed in §11. Same algorithm, same exponential worst case — but filtering and ordering together keep realistic instances out of the exponential region entirely.

Figure 7 — interactive: four solvers on the same map
WA NT Q SA NSW V T
What to notice: press Auto-run on each strategy in turn and watch the counters. With 3 colours Australia is easy, so the interesting number is values tried — plain backtracking tries values it has to reject, while forward checking has already crossed them off and never considers them. Then switch to 2 colours, which is unsolvable (the cycle WA–NT–SA–WA is an odd cycle), and watch the four strategies prove it impossible at wildly different costs: backtracking grinds through the whole tree, forward checking cuts it down, AC-3 fails several levels earlier still, and MRV goes straight for SA. The domain table on the right shows exactly which values each strategy has crossed off at each moment.
Guided problem 3Apply MRV and LCV, and justify every choice

Australia, three colours, backtracking with forward checking, MRV (degree tie-break) and LCV. Nothing is assigned yet; all seven domains are $\{$R, G, B$\}$.

Work out: (a) which variable is assigned first and why, (b) which value LCV picks for it, (c) which variable comes second, (d) which value LCV picks for that.

Step 1 — (a) The first variable

MRV compares domain sizes: all seven are $3$. A seven-way tie, so MRV alone cannot decide and the degree heuristic breaks it — count constraints on unassigned variables:

VariableWANTQNSWVSAT
degree2333250

SA wins with degree 5. Assigning SA first immediately filters five other domains — the largest possible amount of information from one move — and SA is the variable most likely to become impossible later, so we want its failure (if any) at the root.

Step 2 — (b) LCV for SA

All three colours are symmetric right now: every neighbour has the full domain $\{$R,G,B$\}$, so whichever colour we choose deletes exactly one value from each of the five neighbours — 5 deletions either way.

A genuine three-way tie, so LCV is indifferent; take red by convention. This is worth saying out loud rather than inventing a reason: in a symmetric problem the value heuristic has nothing to work with, and pretending otherwise is a good way to lose marks. (Formally, the three colours are interchangeable under a relabelling symmetry, so no value-ordering heuristic that depends only on domain sizes can distinguish them.)

Forward checking after SA = red: WA, NT, Q, NSW, V all become $\{$G, B$\}$. T stays $\{$R, G, B$\}$.

Step 3 — (c) The second variable

Domain sizes now: WA 2, NT 2, Q 2, NSW 2, V 2, T 3. MRV eliminates T immediately — it is the most flexible variable, so it goes last, which is correct: T is an independent subproblem and assigning it early tells us nothing.

Five-way tie at size 2, broken by degree among unassigned variables (SA no longer counts, since it is assigned):

VariableWANTQNSWV
remaining degree1 (NT)2 (WA, Q)2 (NT, NSW)2 (Q, V)1 (NSW)

NT, Q and NSW tie at remaining degree 2; take NT (any of the three is a defensible answer, and a good solution says so).

Step 4 — (d) LCV for NT

$D_{\text{NT}} = \{$G, B$\}$. NT's unassigned neighbours are WA $\{$G,B$\}$ and Q $\{$G,B$\}$ (SA is assigned, so it does not count).

  • NT = green: deletes G from WA (1) and G from Q (1) → 2 deletions.
  • NT = blue: deletes B from WA (1) and B from Q (1) → 2 deletions.

Tied again, for the same symmetry reason. Take green.

Answer & the takeaway

(a) SA, by MRV with the degree tie-break (degree 5). (b) Any colour — LCV ties; say red. (c) NT, Q or NSW, all tied at domain size 2 and remaining degree 2; say NT. (d) Green or blue, tied; say green.

Takeaways. First: MRV did real work — it identified SA, the variable whose failure would otherwise be discovered 243 times over. Second: on a symmetric instance LCV frequently ties, and a correct answer says it ties rather than manufacturing a preference. LCV earns its keep once filtering has made the domains uneven, as in the worked example above where it separated red from blue for NSW. Third: notice that MRV and the degree heuristic pushed T to the very end — exactly right, since T is independent of everything.

Your classmate proposes "most constraining value": always try the value that eliminates the most options for the neighbours, arguing that it prunes the most. What is wrong with the argument?
Why: the classmate has applied the variable-ordering logic to a value choice. Variables are conjunctive — every one must be assigned — so making a doomed variable fail early genuinely prunes. Values are disjunctive — success on any one value suffices — so pushing the first child towards failure prunes nothing; it just delays the child that would have worked. Cost is a real issue for LCV too (option C is true of both heuristics), but it is not what makes "most constraining value" wrong.

18. Summary

The chapter in five lines:

Key idea
TechniqueWhat it doesCost per nodeDetects failure
Naive DFSchecks constraints only at depth $n$$O(1)$at the very bottom
Backtrackingchecks each new value against the assigned variables$O(\deg \cdot 1)$when the conflicting variable is reached
+ Forward checkingcrosses the value off unassigned neighbours$O(\deg \cdot d)$when a neighbour's domain empties
+ AC-3makes every arc consistent, with propagation$O(n^2d^3)$strictly earlier than forward checking
+ MRV / LCVreorders the branching$O(n)$ / $O(\deg \cdot d)$as high in the tree as possible

Structure, and what is left for later

The lecture lists structure as the third family of speed-ups and puts it in parentheses: mentioned, not covered. beyond syllabus Independent subproblems — solve each connected component separately. Tree-structured CSPs — $\mathbf{O(n d^2)}$ with no backtracking: root the tree, REVISE each parent→child arc from the leaves up, then assign forwards, every value guaranteed to extend (Guided problem 2 was an instance, which is why arc consistency alone solved it). Cutset conditioning — enumerate all $d^{|S|}$ assignments to a cycle cutset $S$ whose removal leaves a tree and solve that tree in $O(nd^2)$ each time, total $O(d^{|S|} \cdot n d^2)$; for Australia removing SA alone leaves a path plus isolated T, so $|S| = 1$ — the formal reason SA kept showing up as the bottleneck. Tree decomposition — $O(n d^{\,w+1})$ for treewidth $w$.

Also deferred: local search for CSPs (min-conflicts hill climbing — start from a complete, broken assignment and repair it; this is what really solves million-queens), continuous and infinite-domain CSPs (§8, handed to 07-380), constrained optimisation for the soft constraints of §9, which needs branch-and-bound rather than "stop at the first solution", and specialised propagators such as the alldiff algorithm §7's pigeonhole example calls for.

Next, in Chapter 5, the problem changes shape again: another agent gets to choose some of the values, and it is not on your side.