Recursive Backtracking Explanation
Thanks to Lon Ingram for this explanation of recursive backtracking.
Backtracking problems are solved one step at a time. Literally! Here's the
general algorithm:
1) Is where I am a solution?
2) No. Ok, where can I go from here? If I can go somewhere, choose a place to
go.
3) Go there.
5) Was that a solution? If yes, return true!
5) If there are remaining places to go, choose one and goto #3.
6) Out of places to go. Return false.
(*) #3 is the recursive step. At that point you are in a new place, and you
start from #1 again
Imagine that you went to the library looking for a book, but oddly didn't know
how to look one up using the alphabet or the Dewey decimal system. What would
be a good way of systematically finding the book?
Imagine that the stacks look like this:
---> x
[===| |===]
[===| |===]
[===| |===]
[===| |===]
Where [ and ] indicate dead ends, = indicates a shelf of books and | | indicates
a central aisle between the books. I've chosen an arbitrarily small portion of
the library to illustrate the algorithm, but you may realize that's irrelevant
later on.
So, you're at the x. How to proceed?
One way is to step into the aisle and see where you can go from here:
[===|x|===]
[===| |===]
[===| |===]
[===| |===]
You can go right, left or forward. So, let's try right:
[===| |x==]
[===| |===]
[===| |===]
[===| |===]
No luck...
Where can you go from here? Because you just came from backward, you can only
go forward. So, do that. Now, notice that I've marked where we just were with
a '#' because there are no more places to go from there. You might need to do
this in code, you might not. It depends on your algorithm. The meaning of # in
this case is just that there are no unexplored exits from that point in the
library.
[===| |#x=]
[===| |===]
[===| |===]
[===| |===]
No luck...
Where can you go from here? Because you just came from backward, you can only
go forward.
[===| |##x]
[===| |===]
[===| |===]
[===| |===]
Man. No luck here either. On top of that, you ran out of places to go, because
there's a wall in front of you. So, back up until you have another place to go:
[===| |#x#]
[===| |===]
[===| |===]
[===| |===]
Nope...
[===| |x##]
[===| |===]
[===| |===]
[===| |===]
Nope...
[===|x|###]
[===| |===]
[===| |===]
[===| |===]
Ah, ok, from here, you can go right, or forward. Let's go forward. What we
just did was the backtracking portion of the algorithm.
[===| |###]
[===|x|===]
[===| |===]
[===| |===]
From here, you can go left, right or forward. If you repeat this process, you
will examine every shelve in the library. Let's imagine that we know where the
book is:
[===| |###]
[===|x|===]
[=B=| |===]
[===| |===]
If you always chose the same order of directions to try (and that's usually what
you want to do), you will explore these areas before you find the book:
[===| |###]
[===| |###]
[=B#| |###]
[###|#|###]
At that point, you've 'solved' your book finding problem, and should return true
or the book's location or whatever it is that you are trying to determine. Now,
imagine that the book isn't in this part of the library. You will actually
examine every section of the library and eventually return to where you started
from.
[###|x|###]
[###|#|###]
[###|#|###]
[###|#|###]
At this point, you know that you didn't find it, so return false.
Make sense? Here's a *pseudojava* implementation:
boolean findBook(string book, point coords)
{
if (shelves.shelfAt(coords).contains(book))
{
return true;
}
else
{
while (shelves.hasExit())
{
// nextExit returns the coordinates of where the next exit
// leads to and marks that exit as used up
if (findBook(book, shelves.nextExit()))
{
// found it!
return true;
}
}
}
// ran out of places to go. this is a dead end.
return false;
}
So, for further consideration:
floor 1:
[===| |===]
[===| |===]
[===| |===]
[===| |===]
floor 2:
[===| |===]
[===| |===]
[===| |===]
[===| |===]
floor 3:
[===| |===]
[===| |===]
[===| |===]
[===| |===]
1) How would you change the algorithm above to handle moving between multiple
floors in your search?
2) How could the above algorithm be changed to find the number of possible phone
numbers in a particular area code, excepting those that start with some set of
disallowed numbers such as {555, 666, 911, 411, 211, 311}? Pro-hint: what if we
numbered all the shelves in some systematic way and then counted the number of
shelves we examined?