4

I have a difficult time understanding recursion and backtracking, albeit I have done some simple exercises (e.g. Fibonacci). So allow me to present my "brain flow" here:

  1. I read the textbook and know that you can use backtracking to remove the previous queen if its current position eliminates the possibility of placing the next queen in the next column. So this seems to be easy and all I need to do is to remove it and let the program decide the next possible place.

  2. After a while I found that the program stalled at the 6th queen so I figured out that if I simply removed the 5th queen the program simply put it back to its current position (i.e. given the first four queens the 5th queen always falls into the same place, which is not surprising). So I thought that I need to track the position of the last queen.

  3. This is when I got puzzled. If I were to track the position of the last queen (so that when I backtrack the program is NOT allowed to put the queen into the same place), there are two potential difficulties:

a) Say that I remove the 5th queen, and I have to write code deciding its next possible position. This can be solved by ignoring its current position (before been removed) and continues to look for possible places in the following rows.

b) Should I track all the previous queens? It seems to be so. Let's say that actually I have to remove not one queen, but two queens (or even more), I surely need to track all of their current positions. But this is much more complex than what it seems to be.

  1. So I started to look for answers in the textbook. Sadly it does not have the backtracking code but only have the recursion code. Then I found the code here:

http://www.geeksforgeeks.org/backtracking-set-3-n-queen-problem/

It greatly amazed me because this is so simple and yet it works! The only backtracking part is to remove the last queen! So the question is: How does the following code make sure that when given the position of 4 queens the 5th one does not always fall into the same place again and again? I think what I do not understand is how can you backtrack recursively (say that you need to remove more queens). I have no problem when moving forward recursively, but how can I move backward recursively?

/* A recursive utility function to solve N Queen problem */
bool solveNQUtil(int board[N][N], int col)
{
/* base case: If all queens are placed then return true */
if (col >= N)
    return true;

/* Consider this column and try placing this queen in all rows
   one by one */
for (int i = 0; i < N; i++)
{
    /* Check if queen can be placed on board[i][col] */
    if ( isSafe(board, i, col) )
    {
        /* Place this queen in board[i][col] */
        board[i][col] = 1;

        /* recur to place rest of the queens */
        if ( solveNQUtil(board, col + 1) == true )
            return true;

        /* If placing queen in board[i][col] doesn't lead to a solution
           then remove queen from board[i][col] */
        board[i][col] = 0; // BACKTRACK
    }
}

 /* If queen can not be place in any row in this colum col
    then return false */
return false;
}

OK. Now I have some code that DOES work but I mostly modified my own codes according to the above ones so I'm quite shaky:

bool EightQueen(int& numQueen)  {   

if (numQueen == 8)  {
    return true;
}
if (numQueen == 0)  {
    PlaceQueen(0, 0);
    numQueen ++;
    EightQueen(numQueen);
}

int row = 0;

for (row = 0; row <= 7; row ++) {
    if (CheckThis(row, numQueen))   {   //Check if next queen can be  put
        PlaceQueen(row, numQueen);  //Place next queen
        numQueen ++;
        DisplayQueen();
        cout << endl;
        if (EightQueen(numQueen))   {   //Try next queen
            return true;
        }
        ClearQueen(numQueen - 1);
        numQueen --;
    }
}
return false;
}

Say that numQueen is 5, so in the for loop we are going to check if the 6th queen can be placed. As we know this is impossible for all rows, so the function returns false. I assume that it then "shrinks" back to where it was called, that is when numQueen is 4. So ClearQueen(4) is called and the last queen (the 5th) is removed. Apparently the for loop has not finished so we will try the next row to see if it allows further development. i.e. We check if the 5th Queen can be placed on the next row and if it does we will further check if the sixth queen can be placed and so on.

Yeah it seems to work, well, eh, yeah.

Nicholas Humphrey
  • 1,220
  • 1
  • 16
  • 33

3 Answers3

8

Consider your initial board:

+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+

When you first call your function, the algorithm places a queen at column 0 and line 0 because you call it with col = 0 and because the for (int i = 0; i < N; i++) starts at 0. Your board becomes

+---+---+---+---+---+---+---+---+
| Q |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+

Then, you call the function recursively, with col = 1, so you'll attempt to place a queen at col=1 and line=0. You get an impossible placement because the queens could take each other, so you continue the for (int i = 0; i < N; i++) loop and eventually succeed in placing a queen at col=1 and line=2, you get this board:

+---+---+---+---+---+---+---+---+
| Q |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   | Q |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+

Now you keep doing this, incrementing col every time. Eventually, you'll get to this board:

+---+---+---+---+---+---+---+---+
| Q |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   | Q |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   | Q |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   | Q |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   | Q |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   | Q |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   | Q |   |
+---+---+---+---+---+---+---+---+

You have a problem here, because this board doesn't admit a queen position in column 7. You'll have to backtrack. What happens is that the recursive function only reaches return false; after all positions in a column were attempted and no position was found. When the function returns false, the previous function call will resume execution on the line

if ( solveNQUtil(board, col + 1) == true )

Since the call returned true, the rest of the for loop's body will be executed, i will be incremented and the algorithm will keep trying positions. Think of this as a gigantic set of nested for loop:

for(int i = 0; i < 8; ++i) {
  for(int j = 0; j < 8; ++j) {

    //Snip 6 other fors

    board[0, i] = 1;
    board[1, j] = 1;

    //Snip

    if(isValid(board)) return board;

    //Snip clean up
  }
}

That you replace with recursive function calls. This illustrates that "backtracking" is really just letting the previous recursive level iterate to its next attempt. In this case, it means trying a new position while in other applications it would be to attempt the next generated move.

I think what you need to understand is that the state of the previous recursive call is not lost when you call the same function again. When you reach the line

if ( solveNQUtil(board, col + 1) == true )

The state of the current function is still on the stack and a new stack frame is created for the new call to solveNQUtil. When that function returns, the previous one can continue its execution and, in this case, increment which line it's attempting to place the queen in.

Hope this helps. The best way to wrap your head around this stuff is to reduce your problem to a smaller domain (say a smaller amount of queens) and step through the execution using a debugger.

anthonyvd
  • 7,329
  • 4
  • 30
  • 51
  • Thanks a lot for the detailed answer! I managed to figure out where the problem lays: my brain continues to think that it needs to memorize the positions of previous queens to backtrack, while in fact it does not need to do so as a simple for loop inside of the recursion takes care of the problem already ---- the brain only needs to order my hands to remove the previous queen and put it into the next row and check if it's feasible. But of course I'm sure that for the next recursion-backtracking problem I need to re-convince my brain. – Nicholas Humphrey Jul 29 '13 at 15:06
  • 1
    @FanZhang Glad I could help! Recursion is one of those things you just "get" at some point and it seems this is the "Ohhhhh" moment for you ;) – anthonyvd Jul 29 '13 at 15:10
  • 1
    Thanks pwny I managed to modify my codes according to the above code and it works despite my skepticism. I'll try more recursion exercises to get my feet wet. – Nicholas Humphrey Jul 29 '13 at 17:56
2

The direct answer to your question is simple: you position and remove the queen in a loop. The next time through the loop, you will try the next position.

Which brings me to the next point: you say that the text book doesn't have the backtracking code, but only the recursion code. The recursion code is the backtracking code. When recursing, each instance of the function has its own full set of variables. So in this case, when solveNQUtil is called, the problem has already been solved for the first col - 1 columns. The function iterates through the rows, each time testing whether it can place a queen, and if so, placing it, and recursing. The iteration ensures that all possible places will be examined (if necessary---your code terminates as soon as we find a solution).

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Thanks I'm beginning to see the problem but I still have trouble figuring out how does the program iterate through the rows automatically. I believe the for loop does the trick as it iterates through i (which is the row). I'll read the code more carefully and come back in a few mins. – Nicholas Humphrey Jul 29 '13 at 14:22
  • OK thanks I now figure out the problem: the algorithm works like a tree (haven't read the chapter but has some idea), each queen is treated as a node, and given a few nodes you loop through the possible position for the next node, and if no possible solution is achieved you simply remove the last node. And the program automatically move the last node down the row because when the last recursion ended the program goes back into the previous for loop which takes care of the position of the last node. I know it's messy but I guess the problem is that I do not truly understand recursion. – Nicholas Humphrey Jul 29 '13 at 14:31
  • 1
    @FanZhang Yes. Backtracking is sort of a tree. But a tree that never actually gets built. And the iterating through the rows isn't automatic; it is very explicitly done by means of a `for` loop. – James Kanze Jul 29 '13 at 14:36
  • Thanks, now I figure out where my problem lays: When I read about recursion the textbook told me that recursion could be more intuitive for some problems, which is probably true. i.e. When I play the 8 queen problem by hand, my brain does not actually "save" the position of the previous queens into a "stack", but simply look at the previous queen and order my hand to move it downwards to the next row (and see if there is no attacking from other queens). I'm sure I need to work out many more exercises to be comfortable about recursion (no more stacks!). – Nicholas Humphrey Jul 29 '13 at 14:54
2

You have to remember that there are two reasons for moving a queen down the board :

  • It isn't in a safe position
  • It IS in a safe position, but when you tried placing the next queen, the recursive call returned false, meaning you've exhausted all possibilities in the next column.

Your program was stalling at Queen 5 because it failed to account for the second condition. There is no need for tracking positions, as James said, each recursive call has implicit tracking for the queen it has to place.

Try to imagine the call stack (actually, you could modify your program to generate the same kind of output) :

Queen 1 is safe on row 1
    Queen 2 is safe on row 3
        Queen 3 is safe on row 5
            Queen 4 is safe on row 2
                Queen 5 is safe on row 4
                    No more rows to try for Queen 6. Backtracking...
                Queen 5 is safe on row 8
                    No more rows to try for Queen 6. Backtracking...
                No more rows to try for Queen 5. Backtracking...
            Queen 4 is safe on row 7
                Queen 5 is safe on row 2
                    Queen 6 is safe on row 4
                        Queen 7 is safe on row 6
                           No more rows to try for Queen 8. Backtracking...

Each time you're backtracking, realize that you get back to the previous function call, in the same state you left it. So when you reach Queen 6 and there are no possibilities, the function returns false, which means you've completed the solveNQUtil(board, col + 1) call for Queen 5. You are back in the for loop for Queen 5, and the next thing that's going to happen is that i gets incremented and you try to put Queen 5 on row 5 and so on...

I suggest you to play around with this demo (try the Placement Control : "Manual With Help" option), our brains are way better at understanding things visually. Code is too abstract.

Miklos Aubert
  • 4,405
  • 2
  • 24
  • 33
  • Thanks Miklos Aubert. I did play the 4-queen problem by hand and finally I figured out that my brain stubbornly believes that I need to keep a "stack" for the previous queens' positions. I also found out that although I KNOW that when the recursion ends it returns to the for loop, I do not UNDERSTAND it when programming. This I'm afraid can only be achieved by flood-filling my brain with all sorts of recursion problems for a few days and make it accept the concept. – Nicholas Humphrey Jul 29 '13 at 15:16
  • I understand your struggle. It's hard to look at a *single* function and imagine that it's going to be executed at different levels and always return where it should. Somehow it's easier for the brain to understand the concept of a call stack when a function calls a _different_ function, which then calls yet _another_ function, although in practice, it's the same damn thing with recursive calls! You have to see that a recursive call is just like any other function call, the fact that it's the same function you're in doesn't mean you're "staying in place". You're really _moving_ to a new state. – Miklos Aubert Jul 29 '13 at 15:35
  • After a few mins of consideration, I gathered that I'm also struggling with that for loop. It's like looking into the mirrors and convince yourself that sooner or later you can get to the core of the problem and find a solution. I'm not sure how to put it, but, well, guess more exercises help. – Nicholas Humphrey Jul 29 '13 at 16:16