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!