Purpose: The data structure (Knuth, “DLX”) that makes Algorithm X fast: a sparse 0/1 matrix stored as a toroidal doubly linked list, in which removing and restoring a node are both .
The Core Trick
In a doubly linked list, removing node x:
x->left->right = x->right;
x->right->left = x->left;But x itself still points at its old neighbours. So restoring it is just:
x->left->right = x;
x->right->left = x;No allocation, no stack of saved values, no copying. Backtracking becomes free. Knuth called the effect “dancing” because the pointers seem to leap out and back.
Structure
- Every 1 in the matrix becomes a node linked to its neighbours in all four directions (
L,R,U,D), with wraparound. - Each column has a header node carrying a size counter; headers are linked in a horizontal ring rooted at a special node
h. - Rows are horizontal rings; there is no row header needed, only a stored row identifier.
Cover and Uncover
void cover(Node* c) {
c->R->L = c->L; c->L->R = c->R; // unlink the column header
for (Node* i = c->D; i != c; i = i->D)
for (Node* j = i->R; j != i; j = j->R) {
j->D->U = j->U; j->U->D = j->D; // unlink each node of the row
j->col->size--;
}
}
void uncover(Node* c) {
for (Node* i = c->U; i != c; i = i->U)
for (Node* j = i->L; j != i; j = j->L) {
j->col->size++;
j->D->U = j; j->U->D = j; // relink, exact reverse order
}
c->R->L = c; c->L->R = c;
}Order matters
uncovermust undo in exactly the reverse order ofcover— reverse the direction of both loops (D→U,R→L). Getting this wrong produces a structure that looks fine for a few steps and then corrupts silently.
Search
void search(int depth) {
if (h->R == h) { report(solution, depth); return; }
Node* c = nullptr; int best = INT_MAX; // S-heuristic
for (Node* j = h->R; j != h; j = j->R)
if (j->size < best) { best = j->size; c = j; }
if (best == 0) return; // dead end
cover(c);
for (Node* r = c->D; r != c; r = r->D) {
solution[depth] = r->rowId;
for (Node* j = r->R; j != r; j = j->R) cover(j->col);
search(depth + 1);
for (Node* j = r->L; j != r; j = j->L) uncover(j->col);
}
uncover(c);
}Paradigm
Backtracking with an -undo data structure. The algorithmic idea is Algorithm X; DLX is pure engineering — but engineering that buys two orders of magnitude.
Complexity
cover/uncover: , with no hidden constant- Overall: exponential (exact cover is NP-complete), but the constant factor is tiny and the S-heuristic keeps the tree small
- Space: nodes, allocated once up front
Array-based DLX
Instead of pointers, use six parallel
intarrays (L,R,U,D,col,row) indexed by node id. Same algorithm, far better cache behaviour, and no allocator pressure — this is what competitive implementations do.
Variants / Use Cases
- Sudoku solvers — the canonical demo; solves the hardest known puzzles in microseconds
- Polyomino / pentomino tiling, N-Queens, Latin squares, Langford pairs
- Exact Cover — the modelling page
- XCC (exact cover with colours) — Knuth’s extension in TAOCP Volume 4B, for puzzles with compatibility constraints
- Any backtracking with heavy undo — the dancing-links idea generalises to rollback DSU, persistent structures, and undo-able segment trees