Recursion Review and
Methodology to Remove Recursion
Recursion Review:
· Recursion is a problem solving tool
as well as a feature incorporated into programming languages
·
Recursive problems have two features
1.
Solutions are easy to give for
special cases – called “base cases” (or stopping states)
2.
For a given case (not a base case)
there are clear rules for how to proceed to a new case. This new case is either a) a base case or
b)leads eventually to a base case
·
Understanding what goes on in memory is
crucial to understanding recursion.
With each recursive call to a function a local environment (or stack
frame) is stacked which includes enough storage for:
1.
Local function variables
2.
Value parameters
3.
References (i.e. pointers) back to
call-by-reference parameters
4.
A memory cell for passing back the
value of the function (for non-void functions)
5.
The address to return to after
completing the execution of the current recursive call
Recursion is a
Divide and Conquer Problem Solving Strategy
E.g. The
Towers of Hanoi is covered in your Textbook on Pages 163-8.
Here is the
code for a C++ recursive version of the Towers of Hanoi. The goal of the code is to move a number of
disks, say “count” disks, from peg “start” to peg “finish” using peg “temp”
for temporary storage:
void move(int count, int start, int finish, int temp)
{
If (count > 0){
move(count-1, start, temp, finish);
cout << “Move disk” <<
count << “from” << start << “to” << finish <<
“.”<< endl;
move(count-1, temp, finish, start);
}
}
Assume that
the start peg has the number 1, the finish peg has the number 3 and the temp
peg has the number 2 assigned to it. Then
a call to the move function that would move 3 disks from peg 1 to peg 3 using
peg 2 for intermediate “temporary” storge would look
like:
move(3, 1,
3, 2);
We will use the above recursive code for our
case study. Some older languages such as
Fortran and Cobol do not allow for the use of
recursion. These languages are not so
much in vogue, but it still behooves the programmer to thoroughly understand
what is going on in the background when a recursive algorithm executes.
GENERAL
METHOD FOR REMOVING RECURSION:
The following is adapted from Kruse’s
earlier book, ‘Data Structures and Program Design in C’.
Each call in
a program to a subprogram (recursive or not) requires the subprogram have a
storage area where it can keep its
local variables, its calling parameters, and its return address (that is, the
location of the statement following the one that made the call). In a recursive
program, the storage areas for subprograms are kept in a stack. Without recursion, one permanent storage area
is often reserved for each subprogram, so that an attempt to make a recursive
call would change the values in the storage area, thereby destroying the
ability of the outer call to complete its work and return properly. To simulate recursion we must therefore
eschew use of the local storage area reserved for the subprogram, and instead
set up a stack, in which we shall keep all the local variables, calling
parameters, and the return address for the function value.
Preliminary assumptions:
1.
Direct recursion: We’ll assume the recursion we are
going to remove is direct. That means that the subprogram directly calls
itself. This is the
case with the insert and delete routines of the AVL tree code.
2. Parameter types: You should be
familiar with two kinds of parameters for functions: call by value and call by
reference (address). Parameters called
by value are copied into local variables within the function that are discarded
when the function returns. Parameters
called by reference exist (i.e. have a memory cell allocated) in the calling
program, so that the function refers to them there (i.e. changes to the
parameter occur back in the call programs memory cell). This is how we change things “outside” of a
subprogram, without the use of “global variables”.
Pseudocode
for the Recursion Removal (Simulation) Method:The pseudocode
breaks the method into three steps:
1. Initialization
2.
Recursive call simulation
3.
Return simulation
Below
we assume we are removing recursion from function P -
Initialization:
1.
Declare a stack
that will hold all local variables, parameters called by value, and an indicator
of the return address within P for a particular recursive call (assuming P can
call itself from more than one spot inside its code). Be sure the stack is initialized to being empty. You are to use the STL’s stack in your
implementation.
2.
To enable each recursive call to
start at the beginning of the original function P, the first executable
statement of the original P should have a label attached to it.
Recursive
call: The following steps should be done at each place inside P where P calls
itself.
3.
Make a new statement label Li (if
this is the ith place where P is called recursively)
and attach the label to the first statement after the call to P (so that a
return can be made to this label).
4.
Push the integer i
onto the stack. This will convey on return
that P was called from the ith place.
5.
Push all the local variables values
and parameters called by value onto the stack.
Push the references (address of their memory locations, i.e. pointers)
for call-by-reference parameters.
6.
Set the dummy parameters called by
value to the values given in the new call to P.
7.
Replace the call to P with a goto to the statement label at the start of P.
Return
(from recursive call): At the end of P (or wherever P returns to its calling
program), the following steps should be done.
8.
If the stack is empty then the
recursion is finished; make a normal return.
9.
Otherwise, pop the stack to restore
the values of all local variables and parameters called by value.
10.
Use the integer i
popped from the stack and use this to go to the statement labeled Li. We can accomplish this with a switch
statement.
11.
Note: the above 10 steps work fine
for a function that returns void, for a function that returns a value, you need
to have a memory cell on the stack to hold the value the function returns and
the first statement after recursive return will be and assignment statement to
assign the value calculated to the variable that receives the value of the
function in the recursive version. Thus,
in this case, put your label signifying recursive return BEFORE this assignment
statement.
Next we apply this method to our Towers
of Hanoi Recursive Funtion: By applying this recursion removal method, we
get a straightforward application of the 10 steps listed above:
/*
move: moves count disks from start to finish using temp as
intermediate/temporary storage. */
/*version
with recursion removed/simulated */
void move(int count, int start, int finish, int temp)
{
stack_type stack; /*stack declaration: be sure the stack is initialized to being empty
here! */
int return_address; /* selects place in code to return to after
“recursion” */
L0: /* marks the start of the original
recursive function */
if (count >0)
{
push(count, start, finish, temp, 1); /*save current environment
on the stack */
count --; /* this line and next set up new
parameters */
swap(&finish, &temp);
goto L0; /* simulate the 1st recursive call now w/
new parameters */
L1:
/* marks the return from the first
recursive call */
cout << “Move disk” << count << “from”
<< start << “to” << finish << “.”<< endl;
push(count, start, finish, temp, 2); /*save current
environment on the stack */
count --; /* this line and next set up new
parameters */
swap(&start, &temp);
goto L0; /* simulate the 2nd recursive call now w/
new parameters */
}
L2: /* marks the return from the second
recursive call */
if not stackempty(stack) /*as long as stack is not empty, there are recursive calls
to execute to completion, must return to
appropriate line of code*/
{ pop(&count,
&start, &finish, &temp, &return_address);
/*must “load” local memory of
this
nonrecursive version with the values you just popped,
thus the “call by reference”. You will figure out how to use
STL stack class to do this*/
switch (return_address)
{
case 1: goto L1; break;
case 2: goto L2; break;
}
}
}
NOTE: YOU WILL NEED TO USE THE STACK
CLASS OF THE STL TO ACCOMPLISH WHAT YOU NEED TO ACCOMPLISH!
HERE IS THE AUXILIARY FUNCTION, SWAP:
void swap( int *x, int *y)
{
int tmp;
tmp = *x;
*x
= *y;
*y
= tmp;
}
NOTE: THE ABOVE CODE WOULD BE CONSIDERED
VERY POOR STYLE, HOWEVER THAT IS NOT THE POINT OF THIS
ASSIGNMENT! THE POINT IS THAT YOU
UNDERSTAND EXACTLY HOW
RECURSIVE CODE WILL BE TRANSLATED BY THE COMPILER AND THEN EXECUTED ON THE
MACHINE. KNOWING THIS, YOU WILL BE ABLE
TO WRITE RECURSIVE CODE AND UNDERSTAND HOW IT WILL BE EXECUTED, MAKING YOU MORE
EFFECTIVE AT WRITING CORRECT RECURSIVE PROGRAMS!
The key to
being an effective programmer is understanding exactly
what goes on in memory!