Recursion

Steven J. Zeil

Last modified: Oct 26, 2023
Contents:

Most of the time, when you want to process a lot of data, you use a loop. Recursion is an alternative that you could employ, but if you’re like most programmers, you’re a lot more comfortable with looping or iteration than you are with recursion. That can be a problem, in some circumstances, because there are some tasks that really are more easily solved via recursion.

A function is recursive if it calls itself, or calls some other function that eventually causes it to be called again. This means that a recursive function may have several different calls to it active at the same time. In fact, we call the collection of information that represents a function call in progress an activation of the function.

Recursive functions tend to fall into certain familiar patterns. Every recursive function will test its inputs to see if they represent a special case simple enough to be solved without recursion. These are called base cases. When the function’s inputs do not constitute a base case, the recursive function must somehow break apart the problem to be solved into one or more smaller sub-problems. These sub-problems are solved via recursive calls, and when the recursive calls are done the function must combine the sub-problem solutions into a solution for the original problem.

1 Overview

Here is a simple example of a recursive function.

template <class Iterator>
int length (Iterator start, iterator stop)
{
  if (start == stop) // range is empty: base case
    return 0;        // base case solution
  else
    {
      ++start;      // shorten the range
      return 1 + length(start, stop);
    }  
}

This one uses recursion to compute the length of a range of positions. (We’ll ignore, for now, the fact that there are easier ways to do this.) As simple as this example is, it illustrates all the important characteristics of a recursive function.

2 You’ve Gotta Believe!

In an earlier edition of your textbook, Weiss suggested the that understanding a recursive function is a bit like attending an old time gospel meeting: “you’ve gotta believe!”

template <class Iterator>
int length (Iterator start, iterator stop)
{
  if (start == stop) // range is empty: base case
    return 0;        // base case solution
  else
    {
      ++start;      // shorten the range
      return 1 + length(start, stop);
    }  
}

Question: How do we know next the recursive call on the shorter range really will return the correct value?

Answer

That may seem like a lot of work, but it’s not as if iterative functions were a whole lot easier to get right. When we write an algorithm using loops, we need to convince ourselves that the loop processes each individual element correctly, that the processing of the individual elements adds up to a solution for the entire problem, that the loop condition is correct and will cause us to go around the loop the correct number of times, that the entire loop will function correctly even in the case where we go around the loop zero times (if that is possible), and finally we must convince ourselves that the loop will eventually exit, and not go on looping forever.

3 Recursion versus Iteration

Recursion and iteration (looping) are equally powerful. We know, for example, that any recursive algorithm can be rewritten to use loops instead. We know this is true because that’s how recursion is implemented on the underlying machine. Your text describes how computer systems use a runtime stack (called the activation stack) to keep track of the return addresses, actual parameters, and local variables associated with function calls. Each function call actually results in pushing an activation record containing that information onto the stack. Returning from a function is accomplished by getting and saving the return address out of the top record on the stack, popping the stack once, and jumping to the saved address.

In a sense, then, computers really don’t do recursion. What we might write as a recursive algorithm really gets translated as a series of stack pushes followed by a jump back to the beginning of the recursive function, all implemented using the underlying CPU whose internal code is, fundamentally, iterative.

We can go the other way as well. Given an algorithm with loops, we could, without too much trouble, replace each loop body with a recursive function that would perform a single iteration of the original loop, check to see if the loop would terminate, and if not call itself recursively to simulate the next time around the loop.

4 Making the Choice

If neither recursion nor iteration is fundamentally more powerful than another, how do we decide which to use?