Recursion: Backtracking

advertisement
Recursion: Backtracking
Dr. Jicheng Fu
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


Class Queens


Given a configuration of queens on the chessboard, search for all
solutions
Represent a particular configuration of queens on the chessboard
Code sketch
solve_from (Queens configuration) {
if configuration already contains eight queens
Stop
Print configuration
Condition
else
for every square p that is unguarded by configuration {
Sub-problems.
add a queen on square p to configuration;
Need to try all
solve_from(configuration);
possibilities
remove the queen from square p of configuration;
}
}

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


print


set the user-selected board size and initialize the empty
Queens object
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 col);
void reminsert(int ove(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

 64
   4,426,165,368
8
There can be only one queen in each row


There can be only one queen in each column


88 = 16,777,216
8! = 40,320
Our program is even better, since it rejects squares in
guarded diagonals

Part of the recursion tree for the eight-queens
problem

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
Download