**Backtracking** # Backtracking algorithms - One approach for solving a real-life problem is to make a sequence of decisions. Each choice leads you along some path that requires more decision making along the way. + If you make a correct set of choices, you end up at a solution. + If you reach a dead end or discover you have made some incorrect choice along the way, you backtrack to a previous decision point and try a different possibility. - Such an algorithm is called _backtracking algorithm_. - A backtracking algorithm can be conceptualized either iteratively or recursively. The recursive view can be easier to understand. - The recursive view is: _a backtracking problem has a solution if and only if at least one of the smaller subproblems that results from making each possible initial choice has a solution_. # Maze solving - Imagine you are inside a maze and have to find your way out. What strategy can you use? One strategy you can use is the following ``` put your right hand on the wall while you are still inside the maze do move forward while keeping your hand on the wall ``` - Unfortunately this strategy works correctly on some mazes only. E.g., it fails on this simple maze: ![](simpleMaze.png) # ASCII representation of mazes - A maze can be represented textually. For example, the previous maze is represented like this ``` +-+-------+---+ | | | | | + +---+ +-+ | | | | | | | + | + | + + | | | | | | | | | +-+-+ | +-+ + | S| | | + +-+ +-+ +-+ | | | | +-+---+-------+ ``` Your starting location is at S. The symbol `+` indicates a corner, and the symbols `|` or `-` indicate a vertical or a horizontal wall, respectively. # Maze solving, continued - A strategy that works correctly all the time must _consider all possible paths in a systematic manner_. (It can decide not to follow a path to its very end if that path is known to be fruitless!) One such strategy is called _depth-first search_, a topic you're going to study later in an Algorithms Design course. - In this note we will use backtracking to solve the maze problem. This can take a long time if the maze contains many cycles. However, it works fast on acyclic mazes. # A worked example - Consider the following maze: ``` +-+-------+---+ | | | | | + +---+ +-+ | | | | | | | + | + +-+ + | | | | | | | | | +-+-+ | +-+ | | | S| | | | | +-+ +-+ | | | | | | | | +-+ +---+ + | | | | | | + +---+ +---+ | | | | +-+---+ +-----+ ``` - There are three choices for your initial move: go north, go west, or go south. It turns out that the first two choices lead to dead ends and going south is the only correct choice. You can see this because you have the whole picture of the maze and can imagine following some possibilities to the very end. However, the algorithm can make use of local information only. # Maze solving by recursive backtracking - The algorithm will explore possible paths by moving along some path starting from S until it finds itself outside the maze, or gets into a dead end. - If it gets outside the maze, it returns success. If it gets into a dead end, it returns failure. In case of failure, the algorithm backtracks to a previous decision point to try another choice. - It keeps track of the partial path under exploration by marking it with the `x` characters. - The pseudocode for the algorithm is as follows: ``` solveMaze(start) { if the current square (start) is outside the maze then return success if the current square (start) is marked or is a wall then return failure mark this square (start) for each neighbor of start square do if solveMaze(neighbor) then return success unmark this square (start) return failure } ``` - The function `solveMaze` should be called with `start` set to the position of `S` in the maze. # Maze example solution - If the `solveMaze` function works correctly, we should get this output: ``` +-+-------+---+ | |xxxxxxx| | | +x+---+x+-+ | |xxx| |xxx| | |x+ | + +-+x+ | |x| | | | |xxx| |x+-+-+ | +-+x| |x| x| |x| |x| +-+x+-+ |x| |x| |xxxxx|x| |x+-+ +---+x+x| |xxx| |xxx| | +x+---+ +---+ | |xxxxx| | +-+---+x+-----+ ``` # The n-queens puzzle - The $n$ queens puzzle was first proposed in the 19th century. It asks you to place $n$ queens on an $n \times n$ chessboard in such a way that no two queens attack one another, i.e., no two queens are in the same row, column, or diagonal. - It's obvious that in any solution to the $n$ queens puzzle, each row contains exactly one queen. (In fact, each column and each diagonal also contains exactly one queen.) - Let's number the rows and columns using nonnegative integers from 0 to $n-1$. - We'll store the answer in an array `pi`, where for each row `i`, the value `pi[i]` is the column in which the queen in row `i` should be placed. E.g., the solution to the 4-queens puzzle on the left is represented by the array `pi` on the right. ********************************************************** * column * * 0 1 2 3 * * +---+---+---+---+ * * 0 | | Q | | | * * +---+---+---+---+ 0 1 2 3 * * 1 | | | | Q | +-+-+-+-+ * * row +---+---+---+---+ pi |1|3|0|2| * * 2 | Q | | | | +-+-+-+-+ * * +---+---+---+---+ * * 3 | | | Q | | * * +---+---+---+---+ * ********************************************************** # n-queens recursive backtracking - The base case of the algorithm occurs when all `n` queens have already been placed successfully on the board. The algorithm declares success, prints a solution, and stops. - In the recursive case, the algorithm tries placing one more queen in the lowest-numbered row that does not yet have a queen, making sure the placement position is compatible with the positions of all queens that have already been placed in lower rows. For each compatible placement position in row `i`, it recursively calls itself on row `i+1`. If no compatible placement positions in row `i` leads to a success, the algorithm returns failure and backtracks to row `i-1` to try a next compatible placement position in row `i-1`. # n-queens pseudocode - The pseudocode for the algorithm is as follows: ``` solveQueen(i) { if row i = n then { print solution in pi return success } else { for each column j from 0 to n-1 do { if the queen at square (i, j) is compatible with all queens at squares (0, pi[0]), ..., (i-1, pi[i-1]) then { set pi[i] := j if solveQueen(i+1) = success then return success } } return failure } } ``` - The function `solveQueen` should be called with `i` set to 0. _---San Skulrattanakulchai_