Python Program to Solve the N-Queens Problem
Advanced Backtracking Time O(n!) Space O(n)
Places N queens on an N×N chessboard so that no two attack each other, trying each column row by row and backing up from dead ends.
A chess queen attacks every square along its row, its column and both of its diagonals, so in the N-Queens puzzle no two queens may share any of them. A 4×4 board has 2 solutions; the classic 8×8 version, the eight queens puzzle, has 92.
It's the classic backtracking problem: put a queen on the first safe square of a row, move on to the next row, and when a row has no safe square left, go back and move the queen in the row above.
Program
def is_safe(queens, row, col):
"""True if no queen in queens attacks the square (row, col)."""
for r, c in enumerate(queens):
# Same column, or on a diagonal: as far across as down
if c == col or abs(c - col) == row - r:
return False
return True
def n_queens(n):
"""Every safe placement of n queens, as lists of columns."""
solutions = []
queens = [] # queens[r] is the column of the queen in row r
def place(row):
if row == n:
solutions.append(queens.copy())
return
for col in range(n):
if is_safe(queens, row, col):
queens.append(col) # choose this square
place(row + 1) # fill the rows below
queens.pop() # undo, to try the next column
place(0)
return solutions
size = 4
solutions = n_queens(size)
print(f"{len(solutions)} solutions for {size} queens. The first:")
for col in solutions[0]:
print(" ".join("Q" if c == col else "." for c in range(size)))
Open in the editor Download n_queens.py
Output
2 solutions for 4 queens. The first:
. Q . .
. . . Q
Q . . .
. . Q .
How it works
Step through the program line by line: each step below shows up as its line runs.
-
is_safechecks a square against every queen already placed, one in each row above it. Two queens share a diagonal when they are as many columns apart as rows apart, whichabs(c - col) == row - rtests. Rows need no check, because each row gets only one queen. -
solutionscollects the finished boards.queensis the board being built, as one number per row: the column of that row's queen.[1, 3, 0, 2]means row 0 has its queen in column 1, row 1 in column 3, and so on. -
place(row)fillsrowand the rows below it. It's a function insiden_queens, so it can usen,solutionsandqueens(scope). Whenrowreachesn, every row has a queen, and a copy of the board goes intosolutions. - Otherwise it tries each column of the row in turn, and puts a queen on each safe square it finds. That's the choice.
-
place(row + 1)fills the rows below, finding every solution that starts this way. - Then it takes the queen back, to try the next column. When no column of a row is safe, the loop ends without placing anything and the call returns to the row above, which moves its queen on: that's the backtracking.
- The search starts at row 0 with an empty board, and returns every solution it finds.
- The program solves the 4×4 board and prints how many solutions there are.
- It draws the first solution a row at a time:
Qin the queen's column and.in the others, with spaces between them.
The search on the 4×4 board, up to the first solution:
| Step | queens | Row | What happens |
|---|---|---|---|
| 1 | [] | 0 | Column 0 is safe: place a queen |
| 2 | [0] | 1 | Columns 0 and 1 are attacked, 2 is safe: place |
| 3 | [0, 2] | 2 | All four columns are attacked: back up |
| 4 | [0] | 1 | Column 3 is safe: move row 1's queen there |
| 5 | [0, 3] | 2 | Column 0 is attacked, 1 is safe: place |
| 6 | [0, 3, 1] | 3 | All four columns are attacked: back up |
| 7 | [0, 3] | 2 | Columns 2 and 3 are attacked: back up |
| 8 | [0] | 1 | Column 3 was the last: back up |
| 9 | [] | 0 | Column 1 is safe: move row 0's queen there |
| 10 | [1] | 1 | Columns 0 to 2 are attacked, 3 is safe: place |
| 11 | [1, 3] | 2 | Column 0 is safe: place |
| 12 | [1, 3, 0] | 3 | Columns 0 and 1 are attacked, 2 is safe: place |
| 13 | [1, 3, 0, 2] | 4 | Every row has a queen: save the solution |
The search then carries on and finds the second solution, [2, 0, 3, 1], the first one's mirror image. In all it
places a queen 16 times and checks 60 squares.
Time and space complexity
| Time | O(n!) |
|---|---|
| Extra space | O(n) |
The first queen has n columns to choose from, the second at most n − 1 outside the first one's column, the third
at most n − 2, and so on: at most n! ways to fill the rows, which is why the time is given as O(n!). The diagonals
cut that down a great deal: for 8 queens the program places a queen 2,056 times to find all 92 solutions, where 8! is
40,320. Each is_safe call also reads up to n queens; the version below checks a square in one step. The space is
O(n): queens and the chain of place calls are never more than n long, not counting the solutions it keeps.
Other ways to write it
With sets for the columns and diagonals
Squares on the same diagonal going down to the right share row - col, and squares on the same diagonal going up to the
right share row + col. Keeping the used columns and diagonals in sets makes each check one lookup
instead of a loop. This version only counts the solutions.
def count_n_queens(n):
cols, diagonals, anti_diagonals = set(), set(), set()
def place(row):
if row == n:
return 1
count = 0
for col in range(n):
if (col in cols or row - col in diagonals
or row + col in anti_diagonals):
continue
cols.add(col) # choose
diagonals.add(row - col)
anti_diagonals.add(row + col)
count += place(row + 1) # explore
cols.remove(col) # undo
diagonals.remove(row - col)
anti_diagonals.remove(row + col)
return count
return place(0)
print(count_n_queens(4), count_n_queens(8))Try it yourself
- Change
sizeto 5, 6 and 8: they have 10, 4 and 92 solutions. - Print every solution as a list of columns, not only the first as a board.
- Try a size of 2 or 3: there are no solutions, so
solutions[0]fails with anIndexError. Check that the list isn't empty before drawing the board. - Count how many times
placeputs a queen down, and compare it with n!: 16 against 24 for 4 queens.