DMarlon Studio

Lesson 19 · 19 of 20

Recursion and backtracking · C

Solve the N-queens problem in C with recursion and backtracking: try an option, carry on, and undo it when it leads nowhere.

C · Recursion and backtracking in DMarlon Studio: the example's code and its output in the terminal

The N-queens problem is to place N queens on an N×N board so that none attacks another: not in the same row, column or diagonal. Since each row holds exactly one queen, it is enough to store in col[r] the column of the queen in row r.

safe checks whether a queen can go in column c of row row by looking only at the rows above. There is an attack if they share a column (col[r] == c) or a diagonal: on a diagonal, the distance between columns equals the distance between rows, abs(col[r] - c) == row - r.

place is recursive and uses backtracking: it tries each column of the current row; if it is safe, it places the queen and calls itself for the next row. When that call returns, whether it found something or not, the loop moves on to the next column and col[row] is overwritten: that undoes the choice with no extra code.

The base case is row == N: every row has a queen, so it is a solution. All of them are counted and only the first is drawn. With 6 queens there are 4 solutions; with 8, the chessboard, there are 92.

Backtracking works for any "choose, check and step back" problem: sudokus, mazes, timetables or permutations. Branches that are not safe are abandoned early, which is why it is far faster than trying every combination.

Code · main.c

#include <stdio.h>#include <stdlib.h>#define N 6int col[N], solutions = 0;  // col[r]: the column of the queen in row r// Can a queen go at (row, c) without attacking the rows above?int safe(int row, int c) {    for (int r = 0; r < row; r++)        if (col[r] == c || abs(col[r] - c) == row - r) return 0;  // column or diagonal    return 1;}void place(int row) {    if (row == N) {                       // all rows filled: a solution        if (++solutions == 1)             // draw only the first one            for (int r = 0; r < N; r++) {                for (int c = 0; c < N; c++) printf(c == col[r] ? " Q" : " .");                printf("\n");            }        return;    }    for (int c = 0; c < N; c++)        if (safe(row, c)) {            col[row] = c;                 // try this column...            place(row + 1);               // ...solve the rest recursively...        }                                 // ...and backtrack: the loop tries the next one}int main(void) {    place(0);    printf("%d queens on a %dx%d board: %d solutions\n", N, N, N, solutions);    return 0;}

Output · cc main.c -o main && ./main

 . Q . . . .
 . . . Q . .
 . . . . . Q
 Q . . . . .
 . . Q . . .
 . . . . Q .
6 queens on a 6x6 board: 4 solutions

Key points

  • Recursion: a function that calls itself on a smaller problem.
  • There must always be a base case that stops it.
  • Backtracking: choose, go on, undo the choice.
  • Dropping impossible branches early saves almost all the work.

Exercise

Set N to 8 and check that you get 92 solutions; then also count how many times safe is called and compare it with the 8⁸ ways of putting one queen in each row.