Backtracking
Backtracking is a great tool to have especially if you are working on a game that requires re-tracing steps and trying something different. I wanted to refresh myself on some of working mechanics of this algorithm so I did a bit of digging and noticed there is tons more information on the recursive version and not much on the iterative version.
The iterative version definitely frowned upon during in a homework or during an interview however I believe it more clearly lays out the mechanisms at play to solve a problem.
Problem Statement
Write a program that can find a path from an arbitrary entry point in a maze to an exit.
+--+--+--+
|0 1 2 |
+ +--+--+
|3 4 5 |
+--+ +--+
|6 7 8 |
+--+--+--+
We will make a class
that will initialize necessary variables and then use a method called solve_maze
to begin solving the maze.
Initialize
We initialize the class with the following variables that will enable us to keep track of the activities that the class will perform. This is an important concept here. It is important to keep close track of what is going us as the program runs.
Here is what our initialize looks like
def initialize
@path = []
@visited = []
@current = nil
...
end
@path
an array that will keep track of our our live solution. We will be editing this path as the application tries various solutions and keeps only the ones that worked well.
@visited
is an array that holds ALL the solutions the algorithm has tried. It will be used as a reference to ensure that the algorithm does not repeat a solution.
@current
is simply a pointer to where the algorithm is within the maze at any given time.
Build the Maze
For this entire thing to work we will need to build a maze. The code for that appears here:
def build_maze
@maze = {}
@maze[0] = [1, 2]
@maze[1] = [0, 3]
@maze[2] = [0]
@maze[3] = [1, 4]
@maze[4] = [3 ,5, 7]
@maze[5] = [4]
@maze[6] = [7]
@maze[7] = [ 6, 8]
@maze[8] = [ 7 ]
end
As shown above we will be representing the maze as a hash. The key will be a point in the maze and the value holds all the points that the point in the key touches.
Solving the maze ( iterative solution )
while (@current != finish)
maze[@current].each do |node|
if @visited.include?(node)
if node == maze[@current].last
@path.pop
@current = @path.last
break
end
next
else
@path << node
@visited << node
@current = node
break
end
end
end
Before we start this loop we need to set a few more variables. finish
is the point we will set as the maze exit. The most tricky part of getting the algorithm correct for me was the order in which the arrays were appended to the @path
and @visited
arrays. Once I had the concept figured out the remaining the work flowed.
START
You can set a start point. In my code I simply set @current
we could have a less hard-coded solution for sure but I was more focused on getting the code working. The arrays at this point are all empty . We jump to a point in the maze and grab all the connected points and begin traversing. Here what is important is we start tracking our steps buy updating the various variables we setup.
FOUND
Given we land on a node we have already seen before we simply skip to the next node in the current path that we are on.
BLOCKED
Given we find that the current path has already by discovered and we have reached the very end of the path. Then we have the do the the most intesting part of the algorithm: We have have to remove that node from out @path
variable as it no longer qualifies as a solution to our problem. Here we need to set @current
again and we abandon the current path. Given we are not finished yet the out loop will be take control and move to the next node and another path will be explored.
Finished
As we exhaust the various nodes we will eventually reach the point where @current
will be equal to finish
and here the loop terminates and we have solve the entire maze.