Recursion: the function call stack

When you call a function or method, the runtime environment must keep track of where you called the function/method from, so that it can continue from that point once it has executed the function. It does this using a stack, the call stack. When a function is called, placeholder data is pushed on the call stack. After the function has completed, the placeholder data is popped off and execution continues from that point.

Exactly how the placeholder data is expressed and stored is not important. Suffice to say, it includes a marker for the location of the function call, and saves the values of any variables local to the function containing the call (including parameter values).

For instance, consider this program:

int main()

{

   cout << "Enter a non-palindromic string: ";

StackCharacters();

cout << endl;

}

void StackCharacters()

{

char ch;

get(cin, ch);

   if (ch != '\n') StackCharacters();

   put(cout, ch);

}

-------------------------------------------------------------------------------- Recursion!

Recursion occurs when a function calls itself. More generally, recursion is when a function appears more than once in the call stack. If function f1 calls function f2, which calls f1, that's also recursion, even though f1 doesn't call itself.

Another example: we know well the definition of the factorial function:

N! = 1 * 2 * . . . * (N - 2) * (N - 1) * N and 0! = 1

And we know how to write code for computing a factorial:

factorial = 1; for (int i = 1; i <= N; i++) factorial *= i;

Note that in the definition, multiplying all terms but the last (N) is the same as computing (N - 1)!. Therefore: N! = (N - 1)! * N and 0! = 1 That is, to compute N!, first compute (N - 1)! then multiply by N.

This is a recursive definition, since we've described the factorial function in terms of itself. The recursive definition translates pretty directly into code:

int factorial(int N)

{

if (N == 0) return 1;

else return factorial(N - 1) * N;

}

-----------------------------------------------------Important rules for recursive functions

The recursion must eventually "bottom out." That is, there must always be one or more base cases, in which no recursive call is made. A recursive call should be used to accomplish a "smaller" part of the problem. At least one of the arguments passed in a recursive call must be different from the arguments passed to the function making the recursive call. Another mathematical example: the Fibonacci sequence

The Fibonacci sequence looks like this: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, . . .

After the first two, each number in the sequence is the sum of the previous two. We'll use Fib(i) to represent the ith number in the Fibonacci sequence. Then we can write the following recursive definition: Base cases: Fib(1) = 0 Fib(2) = 1 Recursive case: Fib(i) = Fib(i - 1) + Fib(i - 2) Note that there are two base cases, and that the recursive case includes two recursive references. The code is, again, a fairly direct translation:

int Fib(int i)

{

   if (i == 1) return 0;

   if (i == 2) return 1;

   return Fib(i - 1) + Fib(i - 2);

}

------------------------------------------------- Recursion isn't always the best method

Notice that the recursive Fibonacci function isn't a very efficient way of computing Fibonacci numbers, since a lot of work gets duplicated in the recursive calls. They're much more efficiently calculated using the loop below. Take this as a lesson: just because something seems most easily defined recursively doesn't mean it's most efficiently coded recursively.

int Fib(int i)

{

   int ct, ult, penult = 1, antepenult = 0;

   if (i == 1) return 0;

   if (i == 2) return 1;

   for (ct = 3; ct <= i; ct++)

   {

   ult = penult + antepenult;

   antepenult = penult;

   penult = ult;

   }

return ult;

}

----------------------------------------- General strategies for writing recursive procedures

Write a precise description of what the function is intended to do. Pretend the function has already been written and is available to be called. You must have faith that when it is called it will do exactly what you described it would in Step 1! Ask how the function might be user to solve a subproblem of the problem it's intended to solve. What changes might be made to the parameter values? Remember that there may be more than one recursive call.

Figure out what must happen to the values returned from recursive calls; they must sometimes be further acted upon (combined, perhaps) to get the final answer. Decide what will serve as base cases. Write the code! Check for base cases first, then deal with recursive cases.

Practice Lab: Translate the following definition of the GCD function into code:

If a = b, GCD(a, b) = a

If a > b, GCD(a, b) = GCD(a - b, b)

If a < b, GCD(a, b) = GCD(a, b - a)

---------------------------------------------------------------- A classic recursion problem

The story has been handed down through the generations: In the great temple at Benares beneath the dome which marks the center of the world, rests a brass plate in which are fixed three diamond needles, each a cubit high and as thick as the body of a bee. On one of these needles, at the creation, God placed sixty-four disks of pure gold, the largest disk resting on the brass plate and the others getting smaller and smaller up to the top one.

This is the Tower of Brahma. Day and night unceasingly, the priests transfer the disks from one diamond needle to another according to the fixed and immutable laws of Brahma, which require that the priest on duty must not move more than one disk at a time and that he must place this disk on a needle so that there is no smaller disk below it.

When all the sixty-four disks shall have been thus transferred from the needle on which at the creation God placed them to one of the other needles, tower, temple and Brahmins alike will crumble into dust, and with a thunderclap the world will vanish. (De Parville, La Nature, translated by W. W. R. Ball) The puzzle is often called the Towers of Hanoi problem. We'll look at a much smaller version: 4 disks instead of 64. Here's our initial picture:

-------------------------------------------------------------------- Problem set-up

The goal: A tower of disks (smallest on top) on peg C.

Rules: Move only one disk at a time. Never put a disk on top of one smaller than itself.

Produce: A sequence of moves of the form "move top disk from peg X to peg Y" that reach the desired goal.

Solution: Use recursion! (big surprise)

Step 1: Write a description of the function:

void Hanoi(int numDisks, char fromPeg, char toPeg, char usingPeg)

// Prints the sequence of actions needed to move numDisks disks

// from fromPeg to toPeg, using usingPeg (if necessary).

The initial function call will be:

Hanoi(4, 'A', 'C', 'B');

And the call should produce the following move sequence:

Move top disk from peg A to peg B

Move top disk from peg B to peg C

Move top disk from peg A to peg C

Move top disk from peg B to peg A

Move top disk from peg B to peg C

Move top disk from peg C to peg A

Move top disk from peg A to peg B

Move top disk from peg B to peg C

Move top disk from peg C to peg A

Move top disk from peg A to peg B

Move top disk from peg C to peg B

Move top disk from peg A to peg C

Move top disk from peg A to peg B

Move top disk from peg B to peg C

Move top disk from peg A to peg C

---------------------------------------------------------- Determining the recursive calls

Steps 2/3: Assume the function is available to be called. How can you use it to solve a subproblem or subproblems of the original problem? Think about which of the function's parameters might be changed in a recursive call. Also keep in mind that at least one parameter value should get "smaller", in some sense of the word. For this problem, the only parameter which could get smaller is the number of disks.

Aha! The function can be used recursively to move a smaller number of disks from any peg to any other peg. To leave us with as little extra work to do as possible, let's try using recursive calls where the number of disks is one less than the number of disks we start with. So if I have 4 disks to start, I assume that I can call the function recursively to find out how to move the top 3 disks on any peg to any other. Given that, what steps should I follow from this initial position to move all 4 disks to peg C? Hint: you will need to make two recursive calls!

------------------------------------------------------------------------Base cases and code

More generally, to move numDisks disks from fromPeg to toPeg, using usingPeg:

move numDisks - 1 disks from fromPeg to usingPeg move one disk from fromPeg to toPeg move numDisks - 1 disks from usingPeg to toPeg On to

Step 4: what is the base case? That is, where does this bottom out? The value of the parameter numDisks is decreasing with each level of recursion. For what value of numDisks do we not need to make a recursive call?

Step 5: write the code!

void Hanoi(int numDisks, char fromPeg, char toPeg, char usingPeg)

{

if (numDisks == 1)

   {

   cout << "Move top disk from " << fromPeg << " to " << toPeg << endl;

   }

   else

   {

   Hanoi(numDisks - 1, fromPeg, usingPeg, toPeg);

   cout << "Move top disk from " << fromPeg << " to " << toPeg << endl; Hanoi(numDisks - 1, usingPeg, toPeg, fromPeg);

   }

}

----------------------------------------------------------------- Recursive array sorting

QuickSort is a recursive array sorting algorithm. Each recursive call sorts a smaller portion of the array. Some rearrangement of the array elements is done before the recursive calls are made, so that after the recursive calls are complete, the whole array is sorted.

QuickSort is O(N2) in the worst case, but O(N log N) in the best case, so it has a slight edge over insertion and selection sort. If I split an array into two parts (not necessarily of equal size): A B then I sort the two parts independently, and I tell you that now the entire array is sorted, then what must have been true of the elements in part A in relation to those in part B before I sorted them? ----------------------------------------------------------------- The QuickSort algorithm

Actually, we're going to split the array into three pieces instead of two, but we'll only have to sort two of those pieces. The process: Pick a value at random from the array Rearrange the array to look like this: smaller than value equal to value larger than value Recursively sort the left and right parts The hope is that the array will be divided approximately in half each time. The depth of the recursion will be approximately log N and the partition operation will take O(N). Now we must determine how to do the partitioning. What O(N) algorithm does it remind you of?

-------------------------------------------------- Practice Lab: QuickSort implementation

Partitioning is equivalent to the Dutch National Flag problem, with elements smaller than the partition value considered red, elements equal to the partition value white, and elements greater than the partition value blue. Use the code from Dutch National Flag to help you complete the code for Quicksort.

void Quicksort(apvector& Array, int left, int right)

// Sorts the elements between positions left and right in Array

{

int lessThan; // aka redMarker

int equalTo; // aka whiteMarker

int greaterThan; // aka blueMarker

int partitionValue;

lessThan = equalTo = left;

   greaterThan = right; // arbitrarily choose middle element as partition value

   partitionValue = Array[ (left + right) / 2 ];

   // rearrange elements (partition) ...fill in the code...

   // recurse (if necessary) on elements smaller/greater

   // than partitionValue ...fill in the code...

}

---------------------------------------------------------------------- Linked list recursion

A linked list can be considered a recursive structure. That is, we can define it in terms of itself, like this: Definition: A linked list is: the pointer value NULL (base case, an empty list) OR a pointer to an element consisting of a data value and a pointer to a linked list. You can write a recursive function for manipulating a linked list:

void PrintList(ListItem *ptr)

{

   // base case is empty list (NULL ptr), in which case do nothing

   if (ptr != NULL)

   {    

   cout << ptr->word << endl;

   PrintList(ptr->next);

   }

}

Most recursive linked list functions will have a similar structure: the base case is an empty list, the recursive case does something with the data pointed to and makes the recursive call on the rest of the list. How would I change the code above to print the list elements in reverse order?

-------------------------------------------------------------------------------- Trees

Another common recursive pointer structure is called a tree. A tree is: the pointer value NULL (an empty tree) OR a pointer to an element consisting of a data value and a collection of pointers (0 or more) to trees What it looks like: Note that our trees branch downwards, with the root at the top! --------------------------------------------------------------------------- Tree terminology

The elements in the tree are called nodes. The topmost node is called the root. The nodes that an element points to are called its children; it is the parent. We can also talk about grandparents and, more generally ancestors, as well as grandchildren and descendants. A child node plus all of its descendants are referred to as one subtree of the parent. A node with no children is called a leaf.

To make things easier, we'll restrict ourselves to one particular kind of tree. A binary tree is one in which each node has at most two children. We'll term them the left child and the right child. Here's a class definition for a node; I've written it as a template class so that it can be used for data of different types:

template class TreeNode

{

itemType data;

TreeNode *left, *right;

};

TreeNode *root;

--------------------------------------------------------------------- Recursive tree functions

Most tree processing code is more easily written recursively. With a simple linked list, you can just use a loop to run all the way through the list, since there's essentially a straight line of links to follow to "visit" all of the elements.

With trees, the elements are not in a straight line. Suppose we want to print all the values stored in a tree. To print the values in a tree, we must: print the value at the root, print the values in the left subtree (if there is one), and print the values in the right subtree (if there is one). Here it is in code:

void PrintTree(TreeNode *node)

{

   cout << node->data << endl;

   if (node->left != NULL) PrintTree(node->left);

   if (node->right != NULL) PrintTree(node->right);

}

What will be printed for this tree? The way we've written the code, the value at a node gets printed before the values in its subtrees, and all the values in a left subtree get printed before all the values in a right subtree.

-------------------------------------------------------------------------- Processing orders

As we've written it, PrintTree processed the tree in what's called pre-order; that is, it "processes" the data at a node before processing either of its children. There are two other common processing orders. In post-order, the data at a node is processed after processing both its children. In in-order, the left subtree is processed, then the data at the node itself, then the right subtree.

The only change to our code would be where the cout line occurs in the function: as the first, middle, or last statement. What would result from doing post-order processing of the arithmetic expression tree? What would result from in-order processing?

-------------------------------------------------------------------------- Another neat tree

Can you guess what this tree represents? Notice which letters are on what level of the tree. Another hint: there is significance to left vs. right children of a node. No idea? Send out an SOS!

------------------------------------------------------------------- Answer: Morse Code tree

It's a Morse Code decoding tree! Travelling to a left child in the tree represents a dot; travelling to a right child represents a dash. We can write a node class specific to this tree instance:

class MorseNode

{

   char letter;

   MorseNode *dot, *dash;

};

Now suppose we want to write a function to decode a message typed in Morse Code. Our input might look something like this: .--. .- -.-. -.- -- -.-- -... --- -..- This translates to PACKMYBOX. As it turns out, this one does not need to be coded recursively.

The reason is that we're no longer interested in exploring the whole tree. Instead, we start from the root and use the input to direct our path down through the tree, until the next space character is reached. At this point, we print out the character we've translated, then reset the pointer back to the root of the tree, ready to translate the next character.

In general, if you don't need to be able to "back up" to complete your task, you don't need to write a recursive function. --------------------------------------------------------------------------- Decoding function

void Decode(MorseNode *root)

{

   MorseNode *current = root;

   char ch; cin.get(ch);

   while (ch != '\n')

   {

   switch (ch)

   {

   case '.': current = current->dot;

   break;

case '-': current = current->dash;

   break;

   case ' ': cout << current->letter;

   current = root;

   break;

   default:

   break;

   }

   cin.get(ch);

       }

cout << endl;

}

-------------------------------------------------------------- Using a tree for searching

Consider the following tree: Note that for any node, all names in its left subtree come before the name at the node, and all names in its right subtree come after the name at the node. What processing order would print out the names in the tree in alphabetical order? This tree structure makes it easy to search for a name in the tree.

To do so, we start at the root node and repeat these steps:

If current node = NULL, stop -- name not found.

If name searched for equals name at current node, stop -- name found.

If name searched for comes before name at current node, search left subtree

If name searched for comes after name at current node, search right subtree

Does this need to be written recursively?

-------------------------------------------------------------------------------- Search code

The formal name of this type of tree is a binary search tree, or BST. Finish this function to search for a name in the tree. It should return a boolean value indicating whether or not the name was found. Assume the data field of a TreeNode class is of type apstring.

bool BSTSearch(TreeNode *root, const apstring& name)

   {

   TreeNode current = root;

   while (TRUE) {

   // check for NULL pointer

   // check if name found

   // determine which subtree to search and adjust current accordingly

   }

}

-------------------------------------------------------------- Creating a binary search tree

Searching a BST is fairly easy, but how easy is it to get the elements in the tree into the desired arrangement? Turns out, it's just a slight modification to the search code. The tree is formed by repeatedly inserting elements into the tree. To insert an element, we perform a search; when the search reaches an empty subtree, that's exactly where the new element should go. For instance, here's how to insert "Lance" into the previous tree: Where will "John" and "Rick" be placed?

------------------------------------------------------------------ Writing the insertion code

The search code needs to be changed a little bit, since we now need to stop before current gets set to NULL (in which case, we would no longer have a pointer to the element we're going to be attaching our new element to).

void BSTInsert(TreeNode *root, const apstring& name)

{

   TreeNode *current = root;

   TreeNode *newNode = new TreeNode;

   newNode->data = name; newNode->left = newNode->right = NULL;

   while (TRUE)

   {

   // check if name found (don't insert if already there!)

   // determine which subtree to search

   // if appropriate subtree is empty, insert element there

}

}

---------------------------------------------------------------------------- Tree shape

The shape of the tree depends on the order in which the elements are inserted. The depth may be as many as N levels (where N is the number of elements) or as few as log N levels. The shallower the better, since it will mean more efficient searching. The BST example tree was created by inserting the names in the order given below. What tree do you get if the order is reversed?

Mona Deborah Victor Sonja Larry Jim Brian Ron Danielle Chris Anthony

What's the worst order of insertion (producing a tree with 11 levels)? What's the best order of insertion (producing a tree with 4 levels)?