Hints — Module 09: Backtracking and Dancing Links
Stage 1: Basic backtrack: n queens
-
Reach for Algorithm 7.2.2B: the whole search is a tree where level
lfixes the queen in rowl, and the only property you test at step B3 is "does this queen attack an earlier one?" Cost is measured in tree nodes, so a good pruning test at B3 abandons whole subtrees near the root. Remember the convention:n = 0has exactly one (empty) solution. -
Recurse row by row with a mutable array
awherea[r]is the column chosen in rowr. At each row try every column0..n, keep only the ones passing the attack test against rows already placed, descend, and let the loop's overwrite ofa[row]serve as the implicit B5 backtrack — no explicit undo needed. Thread avisitclosure so the same recursion serves both counting and returning the first solution. -
The property test is the load-bearing line: two queens attack iff same column or same diagonal, i.e.
a[r] == col || (row - r) == (col.max(c) - col.min(c))wherec = a[r]. Structure it asfn queens_ok(a, row, col) -> boolloopingr in 0..row, and in the recursion doif row == n { count += 1; visit(a); return; }before the column loop.
Stage 2: Backtracking with bitwise state
-
This is Walker's method (§7.2.2): the same tree as stage 1, but the three attack constraints — columns used, "down" diagonals (constant r+c), "up" diagonals (constant r−c) — are each a set of forbidden columns, and a set fits in a machine word. The free columns become a one-instruction computation instead of a loop.
-
Carry
cols,diag_down,diag_upasu32masks down the recursion (n ≤ 31). The legal columns in this row arefree = all & !(cols | diag_down | diag_up). When you place a queen and move to the next row the diagonals slide: down-diagonals shift left by one, up-diagonals shift right by one. Terminate whencols == all(every column filled) by returning 1. -
Iterate the set bits of
freewith the ruler-function trickbit = free & free.wrapping_neg()thenfree ^= bit, and recurse withgo(all, cols | bit, (diag_down | bit) << 1, (diag_up | bit) >> 1). The<< 1on down and>> 1on up is the crux — get them backwards and the counts are wrong; check against stage 1 for n ≤ 12.
Stage 3: Exact cover via dancing links
-
This is Algorithm 7.2.2.1X. The heart of it is the couplet: removing node
xfrom a doubly linked list is two assignments that leavex's own links untouched, soxstill remembers where it belongs and restoring it is two more assignments needing no extra memory.cover/uncovermust be exact inverses, which forces uncover to walk everything in the reverse order that cover walked it. -
Use parallel
Vec<usize>arraysleft, right, up, down, col, plussizeper column header andnode_optionper node. Node 0 is the rooth; nodes1..=num_itemsare column headers linked in a circular horizontal list.add_optionpushes one new node per item, splices each into the bottom of its column (just above the header) and into a circular horizontal row. Branch on the column of minimumsize(MRV) so the tree stays small. -
cover(c)first unlinks headerchorizontally (right[left[c]] = right[c]; left[right[c]] = left[c]), then for each nodeidown columncand eachjto its right, splicesjout vertically and decrementssize[col[j]].uncover(c)reverses it: walkiup fromup[c],jleft fromleft[i], re-increment size and relinkdown[up[j]] = j; up[down[j]] = j, then relink the header last. Insearch: ifright[root] == rootrecordpartial(mapped throughnode_option, sorted); else cover the chosen column, iterate its options coveringcol[j]for eachjrightward, recurse, then uncover leftward.
Stage 4: Sudoku as an exact-cover problem
-
A filled Sudoku is an exact cover of 324 items: 81 cell, 81 row-digit, 81 column-digit, 81 box-digit constraints, each covered exactly once. Every placement (r, c, d) is an option touching exactly four items. The whole point is reuse — feed it to the identical
ExactCoverengine from stage 3 and decode the answer back. -
Lay out the item indices in four contiguous blocks so each placement maps to four fixed formulas, and handle givens by offering only the single clued digit for a filled cell (all nine digits for an empty one). Keep a side table mapping each option index back to its
(r, c, d)so you can decode the solution. Take the first solution only viasolve_first, and returnNonewhen there is none. -
Use
cell(r,c) = 9*r + c,row(r,d) = 81 + 9*r + (d-1),col(c,d) = 162 + 9*c + (d-1),box(b,d) = 243 + 9*b + (d-1)withb = (r/3)*3 + c/3. For each cell add optionsec.add_option(&[9*r+c, 81+9*r+(d-1), 162+9*c+(d-1), 243+9*b+(d-1)])(pushing(r,c,d)to your side table), thenlet sol = ec.solve_first()?;and writeout[r][c] = dfor each decoded option.