Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Introduction to Backtracking
Backtracking
Portion B
Backtracking is a technique used to solve problems with a large search space, by systematically trying and eliminating possibilities.
Portion A
Backtracking
At this point in time you know that Portion A will NOT lead you out of the maze,
Portion A
If you get stuck before you find your way out, then you "backtrack" to the junction.
Portion B
Backtracking
Clearly, at a single junction you could have even more than 2 choices. The backtracking strategy says to try each choice, one after the other,
if you ever get stuck, "backtrack" to the junction and try the next choice.
C
B
Find an arrangement of 8 queens on a single chess board such that no two queens are attacking one another. In chess, queens can move all the way down any row, column or diagonal (so long as no pieces are in the way).
Due to the first two restrictions, it's clear that each row and column of the board will have exactly one queen.
1) Place a queen on the first available square in row 1. 2) Move onto the next row, placing a queen on the first available square there (that doesn't conflict with the previously placed queens). 3) Continue in this fashion until either:
a)
b)
Q
Q Q Q Q
Continue
When we carry out backtracking, an easy way to visualize what is going on is a tree that shows all the different possibilities that have been tried.
The neat thing about coding up backtracking, is that it can be done recursively, without having to do all the bookkeeping at once.
Instead, the stack or recursive calls does most of the bookkeeping (ie, keeping track of which queens we've placed, and which combinations we've tried so far, etc.)
perm[] - stores a valid permutation of queens from index 0 to location-1. location the column we are placing the next queen usedList[] keeps track of the rows in which the queens have already been placed.
void solveItRec(int perm[], int location, struct onesquare usedList[]) { if (location == SIZE) { printSol(perm); } for (int i=0; i<SIZE; i++) { if (usedList[i] == false) {
Found a solution to the problem, so print it! Loop through possible rows to place this queen. Only try this row if it hasnt been used
Check if this position conflicts with any previous queens on the diagonal
1) mark the queen in this row 2) mark the row as used 3) solve the next column location recursively 4) un-mark the row as used, so we can get ALL possible valid solutions.
Another possible brute-force algorithm is generate the permutations of the numbers 1 through 8 (of which there are 8! = 40,320),
and uses the elements of each permutation as indices to place a queen on each row. Then it rejects those boards with diagonal attacking positions.
A further improvement which examines only 5,508 possible queen placements is to combine the permutation based method with the early pruning method:
The permutations are generated depth-first, and the search space is pruned if the partial permutation produces a diagonal attack
A 3x3 magic number square has 9 numbers {1,2, 9} that must be arranged in such a way that every row, column, and diagonal has the same sum.
For a 3x3 magic number square the sum is 3*(3*3+1)/2 = 15.
15
15
Say we want to create a class MagicSquare that takes in an int n and returns all valid Magic Number Squares of size nxn.
Example, n =439returns 8 magic squares: 2 7 6 2 8 1 6
9 5 1 4 3 8 2 9 4 7 5 3 6 1 8 4 3 8 9 5 1 2 7 6 3 5 7 8 1 6 6 1 8 7 5 3 2 9 4 6 7 2 1 5 9 8 3 4 3 5 7 4 9 2 8 3 4 1 5 9 6 7 2
The plan like we did with Eight Queens is just to try a number in the first position.
Then we can recursively solve the board at the next position given that the number has been placed. Then we systematically throw out boards that do not meet our constraints.
public class MagicSquare { private int[][] square; private boolean[] possible; private int totalSqs; private int sum; private int numsquares;
square[][] will hold the numbers we place. possible[] will hold the numbers that have still not bee used yet. sum will store what value the rows/columns/diagonals must sum to.
// Creates an empty MagicSquare object. public MagicSquare(int n) { // Fill in an empty square; 0 represents unfilled. square = new int[n][n]; for (int i=0; i<n; i++) for (int j=0; j<n; j++) square[i][j] = 0; // Start with all numbers being possible. totalSqs = n*n; possible = new boolean[totalSqs]; for (int i=0; i<totalSqs; i++) possible[i] = true; sum = n*(n*n+1)/2; numsquares = 0;
There are n*n total numbers, so possible[] is size n*n. At first all of the numbers can be used so the entire array is set to true.
// Recursive method which fills in the square at (row,col). public void fill(int row, int col) {
// Try each possible number for this square. for (int i=0; i<totalSqs; i++) {
if (possible[i]) {
square[row][col] = i+1; possible[i] = false; // Go to the next square.
all possible values for this spot if (!checkRows() || Try !checkCols() || !checkDiags())
return; // Filled everything, so print !!
Find { the new row and col, if (row == square.length) so we can recursively fill the next spot. System.out.println(this);
numsquares++; return;
Base Cases: Either we broke the constraints, Recursively call fill() on our new spot. so we throw that board out. OR Were DONE!! Undo this square for future recursive calls, so that we can try ALL valid boards
}
// Recursively fill. fill(newrow, newcol); // Undo this square. square[row][col] = 0; possible[i] = true;
// Recursive method which fills in the square at (row,col). public void fill(int row, int col) {
// Try each possible number for this square. for (int i=0; i<totalSqs; i++) {
if (possible[i]) {
square[row][col] = i+1; possible[i] = false; // Go to the next square.
// Returns true iff all the rows are okay. public boolean checkRows() { // Try each row. for (int i=0; i<square.length; i++) { int test = 0; boolean unFilled = false; // Add up the values in this row. for (int j=0; j<square[i].length; j++) { test += square[i][j]; if (square[i][j] == 0) unFilled = true; }
// If the row is filled and adds up to the wrong number, // this is an invalid square. If the row is filled and adds if (!unFilled && test != sum) up to the wrong number return false; then our row constraint is violated, } // Never found proof of an invalid row. return true; }
and we return false. At this point we have checked all rows, and none of them violated our constraint.
In dealing with a maze, to make sure you don't try too many possibilities,
one should mark which locations in the maze have been visited already so that no location in the maze gets visited twice. (If a place has already been visited, there is no point in trying to reach the end of the maze from
References
Slides adapted from Arup Guhas Computer Science II Lecture notes: http://www.cs.ucf.edu/~dmarino/ucf/cop350 3/lectures/ Additional material from the textbook:
Data Structures and Algorithm Analysis in Java (Second Edition) by Mark Allen Weiss
Additional images:
www.wikipedia.com xkcd.com