CS - C++ Lesson 19: Recursive Array Programming
INTRODUCTION: Suppose you were trapped in a maze and you needed to find your way out. Your initial attempts are random and due to the same appearance of the white walls, you begin wasting time and energy trying the same routes over and over. After some thought and remembering a computer science algorithm, you take advantage of a tool in your pocket, a marking pen. You begin marking the walls with some notes and a time stamp. At each branch point, you mark the direction you are about to attempt with an arrow. If a dead-end is encountered just around the corner, you retreat to the branch point, mark “dead-end” for that direction, and try another direction. By marking which directions have failed, you avoid trying them again. This 2 dimensional problem and the required backtracking leads us to a recursive matrix problem! After defining the problem, an entire solution will be covered in the outline. Your programming exercise will also be a recursive matrix problem.
The maze searching problem is translated from,
Oh! Pascal!, 2nd edition.[1]
B. Developing the Recursive Solution
C. The Solution to the Maze Problem
D. Declaring enum Data Types
VOCABULARY:
BACKTRACKING
DISCUSSION:
A. Defining the Maze Problem
1.
The maze will be defined as a 2-D grid of
asterisks (marking walls) and blanks (marking potential
paths). The
size of the grid will be 12 x 12 and the starting
location will be at 6,6.
2.
The data structure for this problem will be an apmatrix
of char. The array will be declared as a 13 x
13 grid so that we can use locations 1..12 by 1..12.
Row 0 and column 0 will not be used in the data
structure.
3. The initial grid is stored as a text file and will be loaded into the array.
{Here
is a copy of the mazeData.txt file}
***
********
***
*****
***
** ****
***
* ***
*****
** ***
*****
** ***
*****
******
*****
****
****
* ****
*
** ****
*
* *****
* **********
4.
A potential path out of the maze will be traced
with a !, with moves limited to horizontal or vertical
directions.
5.
We will be at an exit point if we arrive at row
1 or MAXROW,
or at column 1 or MAXCOL.
6. The solution is a backtracking algorithm involving a series of trial and error attempts. If a dead-end is hit, back up and try a different direction.
B.
Developing the Recursive Solution
1.
In developing a recursive solution, consider the
base cases first. What situation(s) cause the
algorithm to stop?
a. We have arrived at a location which is off the data structure. It is important to catch this situation first to avoid an array indexing error.
b. Encountering a '*' character means that we have run into a wall. The algorithm should stop.
c.
Arriving at a location which has a row or column
value equal to 1 or MAXROW or MAXCOL.
We have found an exit point.
2.
The general case of encountering a blank space
requires the following steps:
a. Change the character value from the blank space to the '!' character.
b. Check to see if we are at an exit point. If we are, print the maze with a trail of '!' markers to the exit point.
c. If we are not at an exit point, make 4 recursive calls to check all 4 directions, feeding the new coordinates of the 4 neighboring cells.
3. When a recursive call is made, a value parameter will be used in the formal parameter list of the function. Each recursive call will work with a copy of the array. Why is this? If a reference parameter was used the placement of ! marks would be permanent in the data structure. However, when an exit point is found we want to print only the successful trail of ! marks which leads to an exit.
C.
The Solution to the Maze Problem
// program threadTheMaze
#include <iostream.h>
#include <apmatrix.h>
#include <fstream.h>
const int MAXROW = 12, MAXCOL = 12;
const
char BLANK
= ' ';
void loadTheMaze (apmatrix<char> &maze);
void printMaze (const apmatrix<char> &maze);
void
traceMaze (apmatrix<char>
maze, int row,
int col);
void loadTheMaze (apmatrix<char> &maze)
/* When reading text from disk, you cannot use the extraction operator,
>>, because it skips white space. You need to use the stream member
function get, as used below. You will also need to get rid of the
end-of-line marker, which is a single ASCII code 10 in C++ text file
processing. */
{
ifstream inFile ("Mac HD:ap96:mazedata.txt",ios::in);
char c1;
// to dump end-of-line markers
for (int row=1; row<=MAXROW; row++)
{
for (int col=1; col<=MAXCOL; col++)
inFile.get(maze[row][col]);
inFile.get(c1); // dump eoln marker, ASCII 10's
}
}
void printMaze (const apmatrix<char> &maze)
{
for (int row=1; row<=MAXROW; row++)
{
for (int col=1; col<=MAXCOL; col++)
cout << maze[row][col];
cout << endl;
}
cout << endl << "Hit return to continue ";
cin.get();
}
void traceMaze (apmatrix<char> maze, int row, int col)
/* will attempt to find a path out of the maze. A path will be marked
with the ! marker. The function uses value parameters so that only
the path out will be marked, otherwise extra ! markers will appear in
the answer. The function is recursive. */
{
if (1<=row && row<=MAXROW && 1<=col && col<=MAXCOL)
// boundary check, if false, a base case
{
if (BLANK == maze[row][col]) // if false, base case
{
maze[row][col] = '!';
if (1==row || MAXROW==row || 1==col || MAXCOL==col)
printMaze (maze); // base case
else
{
traceMaze (maze,row-1,col);
traceMaze (maze,row,col+1);
traceMaze (maze,row+1,col);
traceMaze (maze,row,col-1);
}
}
}
}
main ()
{
apmatrix<char>
maze(MAXROW+1,MAXCOL+1);
loadTheMaze (maze);
traceMaze (maze,6,6);
return 0;
}
Here are the two solutions to starting at location (6,6):
***!******** ***!
***** ***!**
**** ***!!!*
*** *****!**
*** *****!**
*** *****
****** *****
**** ****
* **** *
**
**** *
*
***** *
********** |
***
******** ***
***** ***
**
**** *****
** *** *****
** *** *****!**
*** *****!****** *****!!!**** ****
*!**** *!!!**!!**** *!*!!!!***** *!********** |
1.
It is very significant that a blank space be
changed to an '!' mark before the recursive calls are
made. For
example, suppose we begin at location 6,6 and a blank
space is encountered.
If we did not change the blank to an '!' mark,
the recursive call to solve the upward direction
receives the location 5,6.
This recursive call will eventually look back
down at location 6,6 - the location where it came from.
A blank space would still be there and the
recursive calls would go round in circles until the
computer runs out of memory.
2. Changing the blank space to an '!' mark is like leaving a trail of markers. When a recursive call of a neighboring cell looks back at the cell from which it came it will see a '!' mark and not a blank space.
D.
Declaring enum
Data Types
1.
C++ allows for user-defined types which permits
the programmer to better simulate real-world values.
For example the 4 suits which make up a deck of
cards can be defined as follows:
enum suitType {Heart,
Club, Diamond, Spade};
2.
This new data type called suitType
can be used to declare appropriate variables.
The possible values of suitType
have analogous integer values.
The compiler will associate Heart
as integer value 0, Club
as 1, Diamond as 2, and Spade
as a 3.
3.
The following program illustrates some
preliminary usage of enum types.
#include <iostream.h>
enum
suitType {Heart, Club, Diamond, Spade};
main ()
{
suitType
value
= Diamond;
// declare and initialize
for (suitType loop=Heart; loop<=Spade; loop++)
if (loop == value)
cout << "Diamond == Diamond" << endl;
}
4.
The variable loop
is declared as a suitType. It will loop through the 4 values of
the suitType list.
Variables of enum
types can be compared using relational operators.
5. Enum types will be required in the lab exercise of this lesson. A future lesson (Lesson 27) will cover enum types more thoroughly.
SUMMARY/REVIEW: Some of the earlier examples of recursion were applied to linear problems. The factorial or Fibonacci problems were one dimensional, which required only one recursive call within the algorithm. The maze problem is best solved recursively because of the different directions that the problem solving takes. Loosely stated, any problem that involves multiple directions and returning to a branch point is a likely candidate for recursion.
ASSIGNMENT: Lab Exercise L.A.19.1, Eraseobject
[1]Doug Cooper and Michael Clancy, Oh! Pascal! 2nd Edition, W. W. Norton (New York and London), 1985, 362-367.