Recursive Backtracking
Recursive Backtracking
If we ever solve the problem, great, we’re done. Otherwise, we need to keep exploring all
possible paths by making choices and, when they prove to have been wrong, backtracking to the
most recent choice point. What’s really interesting about backtracking is that we only back up in
the recursion as far as we need to go to reach a previously unexplored choice point. Eventually,
more and more of these choice points will have been explored, and we will backtrack further and
further. If we happen to backtrack to our initial position and find ourselves with no more choices
from that initial position, the particular problem at hand is unsolvable.
Morphing
Write a function Morph which recursively attempts to act end
morph the start word into the destination word by a act
series of transformations. A transformation is changing ant
one letter from the start word into the letter at the and
corresponding position in the destination word. Each end
word formed along the way must be a valid word (as
read boot
reported by the dictionary from the Boggle assignment
read
that went out on Wednesday). Assume the lexicon has
bead
been opened and is ready for use. Remember the
beat
member function to check if a word is in the lexicon is:
boat
boot
bool Lexicon::containsWord(string word);
The two parameters to Morph are the start word and the target word. Your function
should return true if and only if a morph could be found (i.e. you do not have to keep
track of the steps, just report whether you found a morph or not).
2
return false;
}
Playing Dominoes
The game of dominoes is played with rectangular pieces composed of two connected squares,
each of which is marked with a certain number of dots. For example, each of the following five
rectangles represents a domino:
Dominoes are connected end-to-end to form chains, subject to the condition that two dominoes
can be linked together only if the numbers match, although it is legal to rotate dominoes 180˚ so
that the numbers are reversed. For example, you could connect the first, third, and fifth
dominoes in the above collection to form the following chain:
Note that the 3-5 domino had to be rotated so that it matched up correctly with the 4-5 domino.
Given a set of dominoes, an interesting question to ask is whether it is possible to form a chain
starting at one number and ending with another. For example, the example chain shown earlier
makes it clear that you can use the original set of five dominoes to build a chain starting with a 1
and ending with a 3. Similarly, if you wanted to build a chain starting with a 6 and ending with a
2, you could do so using only one domino:
On the other hand, there is no way—using just these five dominoes—to build a chain starting
with a 1 and ending with a 6.
3
Dominoes can, of course, be represented in C++ very easily as a pair of integers. Assuming the
type domino is defined as
struct domino {
int first;
int second;
bool used;
};
that returns true if it possible to build a chain from start to finish using any subset of the n
dominoes in the dominoes simplify. To simplify the problem, assume that ChainExists
always returns true if start is equal to finish because you can connect any number to itself
with a chain of zero dominoes.
For example, if dominoes is the domino set illustrated, calling ChainExists would give the
following results:
ChainExists(dominoes, 1, 3) → true
ChainExists(dominoes, 5, 5) → true
ChainExists(dominoes, 1, 6) → false
Answer
bool ChainExists(Vector<domino>& dominoes, int start, int end)
{
if (start == end) return true;
return false;
}
4
d o r i s s u l l i v a n
We want to recursively dissect a string (which may or may not be a legitimate word) and to
return the first subsequence of the specified length that happens to be a word in the specified
lexicon (or the empty string, if no such subsequence can be found). Here’s the code:
/**
* Function: FindMeaningfulSubsequence
* Usage: FindMeaningfulSubsequence("colleenmichellecrandall", 8, lex);
* --------------------------------------------------------------------
* Recursively searches for a subsequence of the specified length that just happens
* to be a word in the lexicon. Once such a subsequence is found, the search
* unwinds and returns the result immediately. If no such subsequence can
* be found, then the empty string is returned to express failure.
*
* Examples: FindMeaningfulSubsequence("abcdefghijklmnopqrstuvwxyz", 6, lex);
* returns "abhors"
* FindMeaningfulSubsequence("antidisestablishmenttarianism", 10, lex);
* returns "testaments"
* FindMeaningfulSubsequence("geraldrichardcainjunior", 7, lex);
* returns "gliadin"
*/
string found =
FindMeaningfulSubsequence(candidate + letters[0], letters.substr(1), len - 1, lex);
if (found != "") return found;
return FindMeaningfulSubsequence(candidate, letters.substr(1), len, lex);
}
The Goal: To assign eight queens to eight positions on a 8 by 8 chessboard so that no queen,
according to the rules governing normal chess play, can attack any other queen on the board.
/**
* File: queens.cpp
* -----------------------------------------------------
* This program implements a graphical search for a solution
* to the N queens problem, utilizing a recursive backtracking approach.
* See documentation for Solve function for more details on the
* algorithm.
*/
#include "chessGraphics.h"
static const int NumQueens = 8;
int main()
{
Grid<bool> board(NumQueens, NumQueens);
InitGraphics();
ClearBoard(board); // Set all board positions to false
DrawChessboard(NumQueens); // Draw empty chessboard of right size
Solve(board,0); // Attempts to solve the puzzle
return 0;
}
/**
* Function: Solve
* -----------------------------
* This function is the main entry in solving the N queens problem.
* It takes the partially filled in board and the column we are trying
* to place a queen in. It will return a Boolean value which indicates
* whether or not we found a successful arrangement starting
* from this configuration.
*
* Base case: if there are no more queens to place,
* then we have successfully solved the problem!
*
* Otherwise, we find a safe row in this column, place a queen at
* (row,col) of the board and recursively call Solve starting
* at the next column using this new board configuration.
* If that Solve call fails, we will remove that queen from (row,col)
* and try again with the next safe row within the column.
* If we have tried all the rows in this column and have
* not found a solution, then we return true from this invocation of
* Solve, which will force backtracking out of this
* unsolvable configuration.
*
* The starting call to Solve should begin with an empty board and
* placing a queen in column 0:
* Solve(board, 0);
*/
7
return true;
}
/**
* Function: PlaceQueen
* --------------------
* Places a queen in (row,col) of the board by setting value in
* the grid to true and drawing a 'Q' in that square on the
* displayed chessboard.
*/
/**
* Function: RemoveQueen
* ---------------------
* Removes a queen from (row,col) of the board by setting value in the grid to
* false and erasing the 'Q' from that square on the displayed chessboard.
*/
/**
* Function: IsSafe
* -----------------------------
* Given a partially filled board and (row,col), returns boolean value
* which indicates whether that position is safe (i.e. not threatened by
* another queen already on the board.)
*/
SEND
+ MORE
MONEY
The goal here is to assign each letter a digit from 0 to 9 so that the arithmetic works out correctly.
The rules are that all occurrences of a letter must be assigned the same digit, and no digit can be
assigned to more than one letter. First, I will show you a workable, but not very efficient
strategy and then improve on it.
First, create a list of all the characters that need assigning to pass to Solve
If all characters are assigned, return true if puzzle is solved, false otherwise
Otherwise, consider the first unassigned character
for (every possible choice among the digits not in use)
make that choice and then recursively try to assign the rest of the characters
if recursion successful, return true
if !successful, unmake assignment and try another digit
if all digits have been tried and nothing worked, return false to trigger backtracking
And here is the code at the heart of the recursive program (other code was excluded for clarity):
/**
* ExhaustiveSolve
* ---------------
* This is the "not-very-smart" version of cryptarithmetic solver. It takes
* the puzzle itself (with the 3 strings for the two addends and sum) and a
* string of letters as yet unassigned. If there are no more letters to assign
* then we've hit a base-case, if the current letter-to-digit mapping solves
* the puzzle, we're done, otherwise we return false to trigger backtracking
* If we have letters to assign, we take the first letter from that list, and
* try assigning it the digits from 0 to 9 and then recursively working
* through solving puzzle from here. If we manage to make a good assignment
* that works, we've succeeded, else we need to unassign that choice and try
* another digit. This version is easy to write, since it uses a simple
* approach (quite similar to permutations if you think about it) but it is
* not so smart because it doesn't take into account the structure of the
* puzzle constraints (for example, once the two digits for the addends have
* been assigned, there is no reason to try anything other than the correct
* digit for the sum) yet it tries a lot of useless combos regardless.
*/
9
The algorithm above actually has a lot in common with the permutations algorithm, it pretty
much just creates all arrangements of the mapping from characters to digits and tries each until
one works or all have been unsuccessfully tried. For a large puzzle, this could take a while.
A smarter algorithm could take into account the structure of the puzzle and avoid going down
dead-end paths. For example, if we assign the characters starting from the ones place and
moving to the left, at each stage, we can verify the correctness of what we have so far before we
continue. This definitely complicates the code but leads to a tremendous improvement in
efficiency, making it much more feasible to solve large puzzles.
Our pseudo-code in this case has more special cases, but the same general design:
Start by examining the rightmost digit of the topmost row, with a carry of 0
If we are beyond the leftmost digit of the puzzle, return true if no carry, false otherwise
/**
* SmarterSolve
* -------------
* This is the more clever version of cryptarithmetic solver.
* It takes the puzzle itself (with the 3 strings for the two addends
* and the sum), the row we are currently trying to assign and the
* sum so far for that column. We are always assumed to be working on
* the last column of the puzzle (when we finish a column, we remove
* that column and solve the smaller version of the puzzle consisting
* of the remaining columns). (One special thing to note is that
* the strings have been reversed to make the processing slightly
* easier for this version). The base case for this version is
* when we have assigned all columns and thus the strings that
* remain are empty. As long as we didn't come in with any carry,
* we must have successfully assigned the letters.
* If we aren't at a base case, the two recursive steps are
* separated into helper functions, one for assigning an addend,
* the other for handling the sum.
* This version is more complex to write, since it tries to take
* into account the nature of addition to avoid trying a lot
* of useless combinations unnecessarily.
*/
/**
* SolveForAddend
* --------------
* Helper for SmarterSolver to assign one digit in an addend. If there
* are no letters remaining in that addend or the last letter is
* already assigned, we just recur on the rest of the puzzle from here.
* If the letter isn't assigned, we try all possibilities one by one
* until we are able to find one that works, or return false to trigger.
* backtracking on our earlier decisions.
*/
char ch = puzzle.rows[whichRow][0];
puzzle.rows[whichRow]++;
if (LetterUsed(ch)) { // already assigned, just go from here
return SmarterSolve(puzzle, whichRow + 1, LetterToDigit(ch) + rowSumSoFar);
} else { // not yet assigned, try all digits
for (int digit = 0; digit <= 9; digit++) {
if (AssignLetterToDigit(ch, digit)) {
if (SmarterSolve(puzzle, whichRow+1, rowSumSoFar + digit))
return true;
UnassignLetterFromDigit(ch, digit);
}
}
11
return false;
}
}
/**
* SolveForSum
* -----------
* Helper for SmarterSolver to assign one digit in the sum. If the
* letter is already assigned and it matches the required sum as computed
* from the digits in the addends, we're fine and just recur on the rest
* of the puzzle. If the letter is assigned and it doesn’t match, we fail
* and trigger backtracking. If the letter isn’t assigned, we assign it the
* necessary digit and recur.
*/
puzzle.rows[Sum]++;
if (LetterUsed(ch)) { // already assigned
return (LetterToDigit(ch) == sumDigit)
&& SmarterSolve(puzzle, Addend1, rowSum/10);
} else {
if (AssignLetterToDigit(ch, sumDigit)) { // only try matching digit
if (SmarterSolve(puzzle, Addend1, rowSum/10))
return true;
UnassignLetterFromDigit(ch, sumDigit);
}
return false;
}
}