Click here to Skip to main content
15,901,373 members
Articles / Programming Languages / C#
Alternative
Article

Backtracking on Eight Queens Puzzle

Rate me:
Please Sign up or sign in to vote.
3.93/5 (6 votes)
8 Sep 2015CPOL8 min read 19.3K   309   5   9
Explains Backtracking (and some more) with the Example "Eight-Queens-Puzzle"

Abstract

Backtracking is a standard-method to find solutions for particular kind of problems, known as "Constraint-Satisfaction"-Problems. These Problems define a set of Constraints to validate any tryal. The most primitive way to solve them is "Brute-Force" - that means: Simply try everything out ;-)

Now Backtracking is applicable (and is an exorbitant improvement), if you can build the tryals stepwise. Then you can check the rules each step, and on failure you can skip all further steps in that direction.

Note: Eg on Path-Finding-Problems Backtracking is possible, but not efficient. Better use a FloodFill-Derivate, Dijkstra or something else.

Even the Knights-Tour-Puzzle, or the Frog-Hopping can be done very much better by respecting additional heuristics.

The eight Queens Problem

is a nice sample-puzzle, where Backtracking is applicable and efficient - it is defined as:

"Find all ways, to place eigth queens on a chessboard, each occupying 1) her own row 2) her own column 3) her own ascending diagonale 4) her own descending diagonale"

My QueenProblems Problem

To me the most difficult was, to change my way of viewing a chessboard: In a backtrackings point of view it's not a 2-dimensioned matrix, but is a tree-structure with depth of eight, and each node has eight childnodes.

Or let me try in other words: On 8-Q-Problem the Backtracking uses no nested x-/y-iteration. Instead of that it steps forward the rows one by one, and on each row it examines, with which of the eight columns it can continue. It steps (row-)backwards, if there is no valid column in that row, (where the queen could be placed).

This way of view is crucial from the very beginning: The first of the four constraints mentioned above can be removed, since the stepping from row to row implies that a row-conflict never can occur.

A "mathematical discovery" ;-)

while thinking about an efficiant way to check the remaining three rules, i've got the idea of 3 hashsets, which stores kind of Identifiers about the already occupied 1) colum 2) asc diagonale 3) desc diagonale.
1) is trivial, since columns are numbered.
2) also wellknown (to me): In a two-dimensional matrix on descending diagonales the x-y-Difference is constant.
3) (you can't imagine, how hard i thought about it, to figure out that simple truth): on ascending diagonales the x-y-Sum is constant.

To make clear what i mean, i created an Image (red numbs: x-y, blue numbs: x+y):

1027506/Matrix2D.png

Code

Constraint-Validation-Mechanism

You see: simpliest Maths gives way to derive from any chess-field the "constraint-Id" in each of the 3 (remaining) "Dimensions": 1) column 2) ascending diagonale 3) descending diagonale.
A Hashset<int> is the perfect storage for those Constraint-Ids, since its Add()-Method rejects adding an Id twice. Moreover on rejecting the Method returns false.

C#
// 3 constraints: column, ascending diagonale, descending diagonale
private static HashSet<int>[] _Occupieds = Enumerable.Range(0, 3).Select(i => new HashSet<int>()).ToArray();
private static Func<int, int, int>[] _GetOccupyIds = new Func<int, int, int>[] { 
         (x, y) => x, 
         (x, y) => x + y, 
         (x, y) => x - y };

private static bool Occupy(int col, int row) {
   int[] tempIds = new int[3];  // to store Ids for possibly rollback
   for (var i = 3; i-- > 0; ) {
      tempIds[i] = _GetOccupyIds[i](col, row);
      if (!_Occupieds[i].Add(tempIds[i])) {
         while (++i < 3) _Occupieds[i].Remove(tempIds[i]);  // rollback
         return false;
      }
   }
   return true;
}
private static void DeOccupy(int col, int row) {
   for (var i = 3; i-- > 0; ) _Occupieds[i].Remove(_GetOccupyIds[i](col, row));
}

There are 3 HashSets, and 3 Func<int, int> - Delegates.
When trying to "put a queen on chessboard" the method Occupy() uses the Delegates to calculate the "Id" of each "dimension" (column, asc diagonale, desc diagonale).
Then try to add it to the corresponding Hashset. If that fails remove the possibly previous added Ids of the other "dimensions", before exit and return false.
The other method - DeOccupy() is needed to remove previous stored Constraint-Ids when Backtracking steps back ("remove a Queen from board").

Search-Algorithm

C#
public static List<int[]> FindAll() {
   var queens = new Stack<int>();
   var output = new List<int[]>();
   Action recurse = null;
   recurse = () => {
      var row = queens.Count;
      if (row == _BoardSize) {
         output.Add(queens.ToArray());
         return;
      }
      for (var col = 0; col < _BoardSize; col++) {
         if (Occupy(col, row)) {
            queens.Push(col);
            recurse();
            queens.Pop();
            DeOccupy(col, row);
         }
      }
   };
   recurse();
   return output;
}

As mentioned in the Abstract, Backtracking builds its tryals step by step. That means to the 8-Q-Problem: row by row. And each step occupies a specific column (in which the queen is placed - for that row).
Long story short: The integers in the queens-Stack are the complete Representation of the current tryal and its state of progress.

Note one of my favourites - the local recursion:
The whole algorithm finds place in the shown method, and the recursive part is done by an anonymous method in a local variable, namely named "recurse" ;-)
So i can hold the queens-Stack, and the output-List as local variables - there is neither a need to design them as class-var, nor to pass them as parameter through all the recursive calls, which will occur.

Lets examine more deeply, what the anonyme recursive method does:
var row = queens.Count;
As said the stack-count is identical to the current tryals progress-state, and also is identical to the row in examination.
Accordingly the next 3 lines check if and react when the tryals state is "complete".

Otherwise check each column, and if you find a position which you can occupy successfully, note it on Stack and recurse.
After recursing comes the backtracking of the Backtracking-Algorithm: Denote the tryal-step from stack and also  de-occupy its constraint-Ids.

You see: An absolutely usually recursive method.

Output

As seen, FindAll() returns the solutions as List<int[]>. Lets see, how to translate an int[] to a string a chess-player would understand, namely to the "A1" - Format:

C#
static void Main(string[] args) {
   Func<int[], string> getA1addresses = columnList => string.Concat(columnList.Select((col, i) => " " + (char)('a' + col) + (i + 1)));
   List<int[]> columnLists = Find8Queens.FindAll();
   Console.WriteLine(string.Join("\n", columnLists.Select(getA1addresses)));
   Console.WriteLine(columnLists.Count.ToString() + " Results");
   Console.Read();
}

The first line does the job:

C#
Func<int[], string> getA1addresses =
   columnList => string.Concat(columnList.Select((col, i) => " " + (char)('a' + col) + (i + 1)));

Get the Letter by adding the column col to 'a'
The list-index i is the row, but zero-based. So add 1 to it to get the digit according to the "A1"-format.
Append the above to " " and you have the "A1"-formatted Display of one entry of the columnList.
Concatenize all the eight entries, and the result-string tells, what the chess-player needs to know: where to place the queens ;-)

A second one: doing the same, but very different

Credits to codeproject-member djmarcus - he posted a comment and gave a completely different concept of validating the 8-Queens-Rules - including the code to achieve it.
If you like, refer to his comment in the discussion-section of this article.

Validation-concept II

djmarcus pointed out, that all squares of a chessboard can be represented simply as bits within a single uint64. This gives the opportunity to create an "attack-Vector" for each position a queen can occupy - as said - whithin one number. Now with simple Bit-Operation And you can check wether a position "is under attack". Moreover with simple Inclusive Or-Operation you can cumulative add several attackVectors as well as you can cumulate several queen-positions, (when several queens succesfully are placed on the board).

Code II

I only show the recursive solve()-function - since it's the main-part, and most convincing, while the attackVectors preparation is more line-expensive:

C#
private bool solve(int row, ulong queens, ulong attacks) {
   if (row == 8) { displayVector("Solution:", queens); return foundSolution; }
   for (var col = 0; col < 8; col++) {
      ulong positionVector = _positionVectors[row * 8 + col];
      if ((attacks & positionVector) == 0) {
         if (solve(row + 1, queens | positionVector, attacks | _attackVectors[row * 8 + col])) 
            return foundSolution;
      }
   }
   return false;   //-- no solution on this row; backup
}

if (row == 8) { displayVector("Solution:", queens); return foundSolution; }
Again at first is checked, whether the row-maxSize is reached, that indicates a found solution and leads to output and exit function.

ulong positionVector = _positionVectors[row * 8 + col];
(not yet mentioned: ) also a "positionVectors"-Array is prepared, so that to each row/column - defined position its represantation as ulong is available.

if ((attacks & positionVector) == 0) {
This one is the check, whether the current position conflicts with any attack(-bit).

if (solve(row + 1, queens | positionVector, attacks | _attackVectors[row * 8 + col])) return foundSolution;
On no conflict (which was checked the line before) the recursion takes a step forward. The tryal-progress of this step-forward is represented by the three modifications of the parameters, passed to the self-call:

  1. increment the row: row + 1
  2. cumulative add the successfull new position: queens | positionVector
  3. cumulative add the associated attackVector: attacks | _attackVectors[row * 8 + col]

This models exactly, what happens, when a chess-player puts the next Queen on an available column of the next row: 1) he enters the next row, 2) one more queen is present, 3) one more group of attacked squares is to respect.

As said: For brevity i do not present the Preparation-Code of attack- and position-Vectors. But nevertheless even that preparations are cruicial for the performance: On each tryal there is nothing to compute - instead of that the prepared result can simply be taken from an Array.
Keep this in mind: Storing prepared results, instead of computing them each time is a highly effective technique to optimize algorithms for speed.

Output II

Even the Output is very different. While i compose a line with "A1"-formatted Positions, the output of djmarcus creates a kind of "Ascii-Art" - compare:

a1 e2 h3 f4 c5 g6 b7 d8
Q  *  *  *  *  *  *  *
*  *  *  *  Q  *  *  *
*  *  *  *  *  *  *  Q
*  *  *  *  *  Q  *  *
*  *  Q  *  *  *  *  *
*  *  *  *  *  *  Q  *
*  Q  *  *  *  *  *  *
*  *  *  Q  *  *  *  *

djmarcus' output-code, using two nested for-loops:

C#
for(int row = 0; row < 8; row++) {
   Console.WriteLine();
   for (int col = 0; col < 8; col++) Console.Write((
      queens & _positionVectors[row * 8 + col]) != 0 ? " Q " : " * ");
}

Points of Interest

To me the "mathematical discovery" was the most important thing i learned. Eg now i see alternative ways to define the two Dimensions of a 2-D-matrix (and even to address elements) than columns and rows.
I mean: the field (2, 4) - addressed by asc/desc Diagonales it would come up as: (6, -2)
(check it, against the picture above :-) )

Maybe to you the "local-recursion"-trick is the fanciest - since it is quite unknown, although its advantages in many cases.

Lately I am very happy to be able to present as second, very different approach, and both do the same, namely solve the 8-Q-Problem by Backtracking.
Note, that on my approach, i have explicit step-back-Actions: pop the failing queen from the tryal-stack, remove her "occupy-ids" from hashsets - these things djmarcus is rid off, since he can pass the complete tryal-state - all queens, all attacked squares - within 2 single numbers to the recursive call. So the return from recursion is step-back enough, and he can continue straight on with the state, which was present before entering the recursion.

There could be said much more, comparing both approaches and search pros and cons. My point is, also to look at the commons, which makes the main-principle emerge very clearly, i think.

Conclusion

We saw Backtracking as method to solve "Constraint-Satisfaction-Problems", and applicable, when a tryal can be developed step by step. The Principle is, to discard a tryal as soon as it becomes invalid, and return to a previous state, where the tryal was less developed, but still valid, and continue developing it another way.
EG on eight-Queens-Problem all further tryals can be skipped, when already in the first two rows two queens attack each other.

Mostly this stepping back to a previous state is achieved by Recursion, and so was here, in both samples.
In Fact Backtracking also can be seen as Organizing the problem as decision-tree and performe a depth-first-traversal through it, skipping all those branches, who are not worth-while.
(Note: Like any depth-first-traversal Backtracking can also be done by non-recursive implementations, (but in this article is not.))

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralYou can also check out the following version in C: Pin
Baruch2315-Sep-15 6:40
professionalBaruch2315-Sep-15 6:40 
Generalare you on topic? Pin
Mr.PoorEnglish15-Sep-15 6:49
Mr.PoorEnglish15-Sep-15 6:49 
GeneralRe: are you on topic? Pin
Baruch2315-Sep-15 6:57
professionalBaruch2315-Sep-15 6:57 
QuestionErrata... Pin
djmarcus9-Sep-15 10:21
djmarcus9-Sep-15 10:21 
AnswerRe: Errata... Pin
Mr.PoorEnglish9-Sep-15 13:26
Mr.PoorEnglish9-Sep-15 13:26 
SuggestionMany ways to solve this problem... Pin
djmarcus7-Sep-15 10:55
djmarcus7-Sep-15 10:55 
There are many ways to solve the 8-Queens problem. Consequently, one should define the criteria by which the solutions alternatives are rated.

Several criteria come to mind:

[1] Execution time
[2] Compactness of code
[3] Recursion versus in-line looping
[4] Elegance (the hardest to evaluate)

From your article, I did not get a clear sense which criteria, if any, applies to your solution.

Here's an approach that focuses on execution time.

It comes down to data representation.

The 8 Queens problem lends itself to some cleverness of utilizing the modern CPU hardware.

The key is that there are 64 squares on a chess board and that there are 64 bits in an 'unsigned long'.

This strongly suggests that each bit of a 64-bit unsigned long be used to represent the chessboard, where a '1' bit represents a queen (or a square under attack) while a '0' bit represents an empty square (or a safe square).

With this in mind, a chessboard can be represented with 8 groups of 8 bits with each group of 8 bits representing a row (or column if your thinking/visualization is flexible enough -- mine is not).

Thus, a queen placed on top left square would be represented as (position vector):
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

in hex: 80000000 00000000

Furthermore, all the squares attacked by this queen are (attack vector):
11111111
11000000
10100000
10010000
10001000
10000100
10000010
10000001

or, when assembled into a 64-bit unsigned long:
11111111 11000000 10100000 10010000 10001000 10000100 10000010 10000001

in hex: FFC0A090 88848281

Similarly for queens placed on any other square.

The elegance of this approach is that given a board's cumulative attack vector (OR of all the existing queens' attack vectors), the check for placing another queen at any position is a simple AND of the proposed queen's position vector against the board's cumulative attack vectors. The 'AND' of the bits will leave a non-zero bit if the queen's position overlaps any of the attacked squares. The entire test can be done in the hardware in one simple statement.

There is no need for hash tables, complicated checking for squares under attack (diagonally, vertically and horizontally), and most importantly, there is no need for bounds checking!

If the result of the AND is zero, then it is safe to add the queen to the proposed position, otherwise it is not. Once placed, the board's cumulative attack vectors is just an OR of the new queen's attack vector with the previous cumulative board's attack vector.

That's it.

As an example, the algorithm below tries to put one queen on every row, subject to it being on a non-attacked square.

To detect if a square is safe, we AND the queen's position vector with the current board's cumulative attack vector.

Invoking the algorithm becomes:

C#
solve(row: 0, col: 0, queens: 0, attacks: 0);


The solution runs in less than 1 millisecond on my system.

Here's the heart of the code:

C#
/// <summary>
/// Recurse through the  board positions
/// </summary>
/// <param name="row">Row where we are trying to place queen one (0..7)</param>
/// <param name="col">Column for trying queen placement (0..7)</param>
/// <param name="queens">Position vectors for the queens already placed (1 bit per queen)</param>
/// <param name="attacks">Attack vector (1 bit for every square under attack)</param>
/// <returns>True if solution found; false otherwise</returns>
private bool solve(int row, int col, ulong queens, ulong attacks)
{
    //-- set this value to 'false' if you want to see all solutions
    const bool foundSolution = true;

    while (col < 8)
    {
        //-- position vector for where we are trying to put queen on
        ulong positionVector = _positionVectors[row * 8 + col];

        //-- check if square is safe
        if ((attacks & positionVector) == 0)
        {
            //-- square is safe (not under attack)
            if (row == 7)
            {
                //-- we reached bottom row and therefore have a solution
                displayVector("Solution:", queens | positionVector);
                return foundSolution;
            }

            //-- we can add queen at [row, col]; try adding another queen at next row
            //   using updated queen positions and attack vectors
            if (solve(row + 1, 0, queens | positionVector, attacks | _attackVectors[row * 8 + col]))
                return foundSolution;
        }

        //-- position under attack; try next column on same row
        col++;
    }

    //-- no solution on this row; backup
    return false;
}


The only coding remaining is to set up the static vectors

C#
/// <summary>
/// Populate up all the possible queen's position vectors and attack vectors
/// </summary>
private void setupQueens()
{
    ulong position = 0x8000000000000000;

    //-- define a position of a queen for each possible square on the board
    for (int row = 0; row < 8; row++)
    {
        for (int col = 0; col < 8; col++)
        {
            _positionVectors[row * 8 + col] = position;
            position >>= 1;
        }
    }

    //-- now compute the attack vector for a queen placed on each position on the board
    for (int row = 0; row < 8; row++)
    {
        //-- mark each square in the same row as 'attacked'
        for (int col = 0; col < 8; col++)
        {
            //-- compute attack vector for queen placed at square: [row, col]
            ulong attackVector = 0;

            for (int col2 = 0; col2 < 8; col2++)
                attackVector |= _positionVectors[row * 8 + col2];

            //-- mark each square in the same column as 'attacked'
            for (int row2 = 0; row2 < 8; row2++)
                attackVector |= _positionVectors[row2 * 8 + col];

            //-- mark the diagonals
            for (int i = 1; i <= 7; i++)
            {
                //-- do diagonals going to lower row numbers (top row is row 0)
                if (row >= i)
                {
                    int row1 = row - i;

                    if (col >= i)
                    {
                        int col1 = col - i;
                        attackVector |= _positionVectors[row1 * 8 + col1];
                    }
                    if (col + i < 8)
                    {
                        int col1 = col + i;
                        attackVector |= _positionVectors[row1 * 8 + col1];
                    }
                }

                //-- do diagonals going to higher row number (last row is row 7)
                if (row + i < 8)
                {
                    int row1 = row + i;

                    if (col >= i)
                    {
                        int col1 = col - i;
                        attackVector |= _positionVectors[row1 * 8 + col1];
                    }
                    if (col + i < 8)
                    {
                        int col1 = col + i;
                        attackVector |= _positionVectors[row1 * 8 + col1];
                    }
                }
            }

            //-- save the attack vector for [row, col]
            _attackVectors[row * 8 + col] = attackVector;
        }
    }
}


... and a trivial chessboard vector display method:

C#
/// <summary>
/// Display a chessboard vector on the console
/// </summary>
/// <param name="header"></param>
/// <param name="queens"></param>
private void displayVector(string header, ulong queens)
{
    Console.WriteLine();
    Console.WriteLine(header);

    for (int row = 0; row < 8; row++)
    {
        Console.WriteLine();

        for (int col = 0; col < 8; col++)
            Console.Write((queens & _positionVectors[row * 8 + col]) != 0 ? " Q " : " * ");
    }
    Console.WriteLine();
    Console.WriteLine();
}


On my system, I get the result:

Elapsed time: 1 millisecond

Solution:

Q  *  *  *  *  *  *  *
*  *  *  *  Q  *  *  *
*  *  *  *  *  *  *  Q
*  *  *  *  *  Q  *  *
*  *  Q  *  *  *  *  *
*  *  *  *  *  *  Q  *
*  Q  *  *  *  *  *  *
*  *  *  Q  *  *  *  *

Generalthank you very much Pin
Mr.PoorEnglish7-Sep-15 19:08
Mr.PoorEnglish7-Sep-15 19:08 
GeneralRe: thank you very much Pin
djmarcus8-Sep-15 3:58
djmarcus8-Sep-15 3:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.