RISC JKU

Eight Queens Puzzle

1 Problem Description

The \(n\) Queens Puzzle asks in how many ways \(n\) queens can be placed on a \(n\times n\) chessboard such that they do not threaten each other. The sequence is also listed in the Online Encyclopedia of Integer Sequences. We are going to write a recursive procedure in Maple to do the counting for us.

2 Idea

The main idea is to place one queen at a time somewhere on the board where no other pieces can attack her. If this s possible, then we might find a solution. Else, we need to find a different place for the previous queen.

With this basic method there are \(n^2\) possibilities for placing each queen. We reduce this number (and obtain a more efficient representation of our data) by observing that there can only be one queen in each column of the board. (Two queens on the same column would threaten each other.) With this in mind, we will always place the first queen in the first column, the second queen in the second column and so on.

3 Implementation

We will use a vector \(v\) of length \(n\) to represent our board. If \(v_j = i\), then this means that there is a queen in the \(j\)-th column and \(i\)-th row. We will also keep a counter on how many columns are already successfully filled (ie, how many queens are already successfully placed).

The main procedure is simply called Queens and has as its only input the size \(n\) of the board. It calls the loopQueens procedure which will do the actual work; passing it the parameter \(n\), the information that we need to place a queen in column \(1\), and an empty board.

Queens := n -> loopQueens(n, 1, Vector(n, 1)):

The loopQueens procedure does the actual counting. Its parameters are the size \(n\) of the board, the column in which we currently need to put a queen, and a (partially filled) board. It computes in how many ways we can complete the board; that is, in how many ways we can place queens in the remaining columns \(i,\ldots,n\) without any two threatening each other. (We can assume that the configuration on the partially filled board is valid.)

If \(i > n\), then we must already have filled up all the columns. Thus, we have found exactly one valid configuration, and we simply return that number.

Otherwise, we try to place a queen in the \(i\)-th column. For this, we consider all possible rows \(a = 1,\ldots,n\). If a queen can be placed in the \(i\)-th column and \(a\)-th row, then we might be able to eventually find valid configurations. In that case, we apply the procedure loopQueens recursively with the altered board and asking it to fill the remaining columns. We also increase the solution counter \(s\) by the number of solutions found during the recursive call. Else, if the queen cannot go in row \(a\), then we do not change the solution counter \(s\). Initially, the solution counter is simply set to \(0\).

Instead of writing the method that we just described out as a loop, we are using Maple's foldl function.

loopQueens := proc(n :: posint, i :: posint, board :: Vector(posint)) :: integer:
    if i > n then
        1
    else
        foldl(proc(s,a)
                  if safeAdding(i, a, board) then
                      s + loopQueens(n, i+1, addQueen(i, a, board))
                  else
                      s
                  end if
              end proc,
              0, $1..n)
    end if:
end proc:

In order to complete the implementation, we need to define a few utility functions. The first function simply changes the board by placing a queen at the given spot.

(A short function like this could also be directly embedded into the larger procedure loopQueens; however, it often increases the readability of an implementation to use functions with easily understandable names instead.)

addQueen := proc(i :: posint, j :: posint, board :: Vector(posint))
    :: Vector(posint):
    board[i] := j:
    board
end proc:

The second utility function tests whether a queen can be safely placed at column \(i\) and row \(j\) on a given, partially filled board.

The assumption here is, that the board is already filled in a valid way up to the \((i-1)\)-th column. Thus, we simply need to check whether the new queen makes the configuration invalid or not. We already know, that there cannot be another queen in the same column due to our way of representing the board. We still need to check rows and diagonals.

We consider the columns \(s = 1,\ldots,i-1\) on after the other. Assume that in column \(s\) there is a queen on row \(t\). Then the new queen cannot be in the same row (ie, we need \(j \neq t\)). In order to check the diagonals, we need that \(i - s\) and \(j - t\) as well as \(i - s\) and \(t - j\) are different. (We can check both at the same time using the absolute value.)

Again, instead of explicitly writing out the loop, we use one of Maple's built-in functions. The andmap functions checks whether a predicate (here given by an anonymous procedure) holds for every entry in a given list.

safeAdding := proc(i :: posint, j :: posint, board :: Vector(posint)) :: boolean:
    andmap(proc(s) local t:
               t := board[s]:
               j <> t and abs(i-s) <> abs(j-t)
           end proc, [$1..i-1])
end proc:

Finally, we run our procedure with boards of different sizes.

map(Queens, [$1..10]);

This yields the following table:

\(n\) 1 2 3 4 5 6 7 8 9 10
Solutions 1 0 0 2 10 4 40 92 352 724

his matches the values listed in the Online Encyclopedia of Integer Sequences.