360 likes | 743 Views
Recursion: Backtracking. Dr. Gang Qian Department of Computer Science University of Central Oklahoma. Objectives (Chapter 5). Understand backtracking algorithms and use them to solve problems Use recursive functions to implement backtracking algorithms
E N D
Recursion: Backtracking Dr. Gang Qian Department of Computer Science University of Central Oklahoma
Objectives (Chapter 5) • Understand backtracking algorithms and use them to solve problems • Use recursive functions to implement backtracking algorithms • See how the choice of data structures can affect the efficiency of a program
The Eight-Queens Puzzle • How to place eight queens on a chess board so that no queen can take another • Remember that in chess, a queen can take another piece that lies on the same row, the same column or the same diagonal (either direction) • There seems to be no analytical solutions
Solutions do exists • Requires luck coupled with trial and error • Or much exhaustive computation
Solve the Puzzle • How will you solve the problem? • Put the queens on the board one by one in some systematic/logical order • Make sure that the queen placed will not be taken by another already on the board • If you are lucky to put eight queens on the board, one solution is found • Otherwise, some queens need to be removed and placed elsewhere to continue the search for a solution
Why put the queens in a systematic order? • Make sure that you will not consider the same situation again • The whole process is suitable for recursive programming • A large problem can be divided into small subproblems with a similar nature
An Outline of the Recursive Function • Recursive function: solve_from • Given a configuration of queens on the chessboard, search for all solutions • Class Queens • Represent a particular configuration of queens on the chessboard • Code sketch solve_from (Queens configuration) { if configuration already contains eight queens Print configuration else for every square p that is unguarded by configuration { add a queen on square p to configuration; solve_from(configuration); remove the queen from square p of configuration; } } Stop Condition Sub-problems. Need to try all possibilities
How to find the next square to try? • There must be a queen, exactly one queen in each row • There can never be more than one queen in each row • Therefore, we can place the queens one row at a time in order • What we have decided is actually a systematic way to solve the problem
Example: Four Queens • X: Guarded squares • ?: Other squares that have not been tried
Backtracking • Backtracking algorithms • Search for a solution by • constructing partial solutions that remain consistent with the requirements of the problem, and then • extending a partial solution toward completion • When inconsistency occurs, the algorithms backs up (backtracks) by • removing the most recently constructed part of the solution, and then • trying another possibility
Suitable for implementations using • Recursion, or • Stacks • Only the most recent part is used • Useful for situations where many possibilities may first appear, but few survive further tests • Scheduling problems • Arranging sports tournaments • Designing a compiler • Parsing
Main program int main( ) /* Pre: The user enters a valid board size. Post: All solutions to the n-queens puzzle for the selected board size are printed. Uses: The class Queens and the recursive function solve_from. */ { int board_size; print_information( ); cout << "What is the size of the board? " << flush; cin >> board_size; if (board size < 0 || board size > max_board) cout << "The number must be between 0 and “ << max_board << endl; else { Queens configuration(board_size); // Initialize empty configuration. solve_from(configuration); // Find all solutions extending configuration. } }
The configuration: Queens class • Constructor • set the user-selected board size and initialize the empty Queens object • print • print the solutions • unguarded bool Queens :: unguarded(int col) const; Post: Returns true or false according as the square in the first unoccupied row (row count) and column col is not guarded by any queen. • Why no row number?
insert void Queens :: insert(int col); Pre: The square in the first unoccupied row (row count) and column col is not guarded by any queen. Post: A queen has been inserted into the square at row count and column col; row count has been incremented by 1. • remove void Queens :: remove(int col); Pre: There is a queen in the square in row count - 1 and column col. Post: The above queen has been removed; count has been decremented by 1. • is_solved bool Queens :: is_solved( ) const; Post: The function returns true if the number of queens already placed equals board size; otherwise, it returns false.
The backtracking function: solve_from void solve_from(Queens &configuration) /* Pre: The Queens configuration represents a partially completed arrangement of non-attacking queens on a chessboard. Post: All n-queens solutions that extend the given configuration are printed. The configuration is restored to its initial state. Uses: The class Queens and the function solve_from , recursively. */ { if (configuration.is_solved( )) configuration.print( ); else for (int col = 0; col < configuration.board_size; col++) if (configuration.unguarded(col)) { configuration.insert(col); solve_from(configuration); // Recursively continue to add queens. configuration.remove(col); } }
Implementation of the Queens Class • Store the chessboard as a 2-dimensional array with entries indicating the locations of the queens • Queens class const int max_board = 30; class Queens { public: Queens(int size); bool is_solved( ) const; void print( ) const; bool unguarded(int col) const; void insert(int col); void remove(int col); int board_size; // dimension of board = maximum number of queens private: int count; // current number of queens = first unoccupied row bool queen_square[max_board][max_board]; };
Constructor Queens :: Queens(int size) /* Post: The Queens object is set up as an empty configuration on a chessboard with size squares in each row and column. */ { board_size = size; count = 0; for (int row = 0; row < board_size; row++) for (int col = 0; col < board_size; col++) queen_square[row][col] = false; } • insert void Queens :: insert(int col) /* Pre: The square in the first unoccupied row (row count ) and column col is not guarded by any queen. Post: A queen has been inserted into the square at row count and column col ; count has been incremented by 1. */ { queen_square[count++][col] = true; } • is_solved, remove and print are also trivial
unguarded bool Queens :: unguarded(int col) const /* Post: Returns true or false according as the square in the first unoccupied row (row count ) and column col is not guarded by any queen. */ { int i; bool ok = true; // turns false if there is a queen in column or diagonal for (i = 0; ok && i < count; i++) // Check upper part of column ok = !queen_square[i][col]; for (i = 1; ok && count - i >= 0 && col - i >= 0; i++) // Check upper-left diagonal ok = !queen_square[count - i][col - i]; for (i = 1; ok && count - i >= 0 && col + i < board size; i++) // Check upper-right diagonal ok = !queen_square[count - i][col + i]; return ok; }
Exercise • Describe a rectangular maze by indicating its paths and walls within an array. Write a backtracking program to find a way through the maze • You are a tournament director and need to arrange a round robin tournament among N = 2k players. In this tournament, everyone plays exactly one game each day; after N - 1 days, a match occurred between every pair of players. Write a backtracking program to do this.
Review and Refinement • The time increases rapidly with the board size • First refinement • Use the 2-dimensional array to keep track of all the squares that are guarded by queens • For each square, keep a count of the number of queens guarding the square • Faster, but we still need the loops to update the guard counts for each square
Second refinement • Objective • Eliminate all loops • Key idea • Each row, column and diagonal can contain at most one queen • Keep track of unguarded squares by using three bool arrays: col_free, upward_free and downward_free • Diagonals from the lower left to the upper right are called upward diagonals • Diagonals from the upper left to the lower right are called downward diagonals • An integer array is used to record the column number for the queens in each row
Eliminate loops • It is trivial to identify each column • How to identify each diagonal • For upward diagonals, the row and column indices have a constant sum • The sum ranges from 0 to 2 board_size – 2 • The sum can be used to identify each upward diagonals • The square in row i and column j is in upward diagonal number i + j • For downward diagonals, the difference of the row and column indices is constant • The difference ranges from -board_size+1 to board_size–1 • The downward diagonal can be numbered using the difference • The square in row i and column j is in downward diagonal number i - j + board_size - 1
Refined Implementation • Revised Queens class class Queens { public: Queens(int size); bool is_solved( ) const; void print( ) const; bool unguarded(int col) const; void insert(int col); void remove(int col); int board_size; private: int count; bool col_free[max_board]; bool upward_free[2 * max_board - 1]; bool downward_free[2 * max_board - 1]; // column number of queen in each row int queen_in_row[max_board]; };
Constructor Queens :: Queens(int size) /* Post: The Queens object is set up as an empty configuration on a chessboard with size squares in each row and column. */ { board_size = size; count = 0; for (int i = 0; i < board_size; i++) col_free[i] = true; for (int j = 0; j < (2 * board size - 1); j++) upward_free[j] = true; for (int k = 0; k < (2 * board size - 1); k++) downward_free[k] = true; }
Insertion void Queens :: insert(int col) /* Pre: The square in the first unoccupied row (row count ) and column col is not guarded by any queen. Post: A queen has been inserted into the square at row count and column col; count has been incremented by 1. */ { queen_in_row[count] = col; col_free[col] = false; upward_free[count + col] = false; downward_free[count - col + board_size - 1] = false; count++; }
Unguarded bool Queens :: unguarded(int col) const /* Post: Returns true or false according as the square in the first unoccupied row (row count ) and column col is not guarded by any queen. */ { return col_free[col] && upward_free[count + col] && downward_free[count - col + board_size - 1]; }
Evaluation New Implementation 2-D array Implementation
Analysis of Backtracking • Effectiveness of Backtracking • Naïve approach • Consider all configurations: put all 8 queens on the board and reject illegal configurations • There can be only one queen in each row • 88 = 16,777,216 • There can be only one queen in each column • 8! = 40,320 • Our program is even better, since it rejects squares in guarded diagonals
Lower Bounds • For n-queens problem, the amount of work done by backtracking problem still grows very fast • To place a queen in each of the first n/4 rows, backtracking algorithm investigates a minimum of n(n - 3)(n - 6) … (n – 3 * n/4) positions • n (n - 3) (n - 6) … (n – 3 * n / 4) > ( n / 4) (n/4) • Exponential growth • Number of solutions • Not bounded by any polynomial in n • Even not bounded by any exponential form kn, where k is a constant • It is proved to be an unsolved problem