Recursion in general

Recursion means defining something in terms of the subject itself. For example, the mathematical concept of a natural number’s factorial can be defined like this:

\[n\,! = n \cdot (n - 1) \cdot (n - 2) \cdot \ldots \cdot 3 \cdot 2 \cdot 1\]

Also, we define that \(0\,! = 1\).

Therefore, the factorial of a natural number (larger-than-zero) is the product of it and all the positive integers smaller than it. For example, \(5\,! = 5\cdot4\cdot3\cdot2\cdot1 = 120\).

On the other hand, we can also define a factorial recursively:

\[\begin{split}n\,! = \left\{ \begin{array}{ll} 1 & ~~\mbox{if}~n~\mbox{is}~0 \\ n \cdot (n – 1)\,! & ~~\mbox{if}~n > 0 \end{array} \right.\end{split}\]

When we apply the recursive definition to calculate \(5\,!\), the result is:

\[\begin{split}\begin{array}{ll} 5! & = 5 \cdot 4! \\ & = 5 \cdot 4 \cdot 3! \\ & = 5 \cdot 4 \cdot 3 \cdot 2! \\ & = 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1! \\ & = 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 \cdot 0! \\ & = 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 \cdot 1 \\ & = 120 \end{array}\end{split}\]

The idea of recursion is that you present the solution in the same format as the original problem but as its sub-problems with a simpler solution. After that, you can apply the recursive rule over and over again onto the received sub-problems, until the problem is simple enough for you to see the solution directly.

The goal of recursion is to divide and conquer.

Recursive functions: factorial example

Usually, recursion in the context of programming is related to functions. [1] A recursive function is self-defined, meaning it calls itself.

Let us implement the aforementioned factorial calculation with a recursive C++ function:

unsigned int factorial(unsigned int n) {
    if ( n == 0 ) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

which calculates and returns the factorial of its parameter n.

Basically, you can write the function straight from the recursive definition of factorial, but it is far more useful to understand how the function works, and why it produces a correct result.

Let us presume you call the function factorial like this:

int main() {
    cout << factorial(5) << endl;
}

and examine, stage by stage, how the execution proceeds. After the main program has called the function, the program executes the body of the function factorial defined above. The situation seems like below:

Function activations after calling ``factorial`` function from the main function

The covered main box in the picture means that the execution of the main function is not finished. First, the factorial function covering main has to execute the return command, and only after that, the execution of main can continue from the point where factorial was called.

Because the value of n is 5, the program executes the else branch.

Before factorial is able to return a value, it must calculate a value to the expression n * factorial(n - 1). Therefore, the execution of factorial remains unfinished until the recursive call factorial(n - 1) finishes calculating the factorial of 4.

In fact, the execution of this recursion round is identical to the one before, except that the value of the parameter n is now 4:

Function activations after the first recursive call

The execution, again, goes to the else branch, and the recursion advances deeper and deeper. Take note of the unfinished executions of the factorial function increasing in the background.

The program will continue like this, until the value of the parameter n reaches 0:

Function activations after reaching the point when the parameter has the value 0

Now, instead of the recursive call, what happens is that factorial returns the value 1, and the stack of unfinished function calls starts to be released.

The last recursive call returns the value 1 to the second last call of factorial, and we multiply it by the value of parameter n that is also 1:

Function activations after returning from the deepest function call

Please note that each call (instance) of the factorial function has its own version of the parameter variable n. The value of n is still the same when we return from the recursively called function to execute the commands of the calling instance. Why?

It is the same with all local variables.

The next unfinished factorial function continues in the same way:

Function activations after returning from the second deepest function call

In this way, recursive calls unwind and the execution returns back to the first instance of the factorial function:

Function activations just before returning to the main function

It returns the value of the factorial of 5 to the main program:

Finally the value of the call ``factorial(5)`` is known to be 120

More examples

Materials of this round provide a recursive versio for number series game: examples/06/recursive_number_series_game/.

The example code has also another recursive function. It counts the amount of numbers divisible by 3 or 7 in a certain range.

Conclusion

Recursion is nothing special from the programming language’s viewpoint: recursive functions are just functions, just like all the rest. Recursion is difficult to understand because it demands the programmer to perceive the problem and its solution in a possibly nonintuitive (?) way.

It is easier to design and understand recursive functions when you remember the following facts:

  • A function is recursive if it calls itself (directly or indirectly).
  • During the execution, as many instances of the recursive function are ”on” as there are unfinished recursive calls.
  • Each instance has its own values for the parameters and local variables.

Recursive function has two essential characteristics you should remember:

  1. It has to have a terminating condition (or several) that recognizes the trivial cases of the problem and reacts to them without having to make a new recursive call.
  2. Each recursive call has to simplify the problem in question in order to finally reach the trivial case.

The previous was represented in the factorial function like so:

  1. The condition of the if structure (n == 0) is a trivial case: We know the value of zero’s factorial right away.
  2. Each recursion round was considering the factorial of a number smaller by one: The call parameter decreased by one.

If you forget either of these, the whole program will crash when you call the function (segmentation fault in Unix). The reason for that is that the function ends up calling itself indefinitely (an infinite recursion): The variables of the earlier calling instances [2] use memory until all the memory is used.

Recursion creates repetition. In principle, any loop structure can be replaced with a recursion. The best use of recursion is solving problems that are, by nature, recursive. You can simplify them ”naturally” in a way that the reduced sub-problems are simplified cases of the original problem.

Often, a recursive solution is shorter and cleaner than a corresponding loop structure solution. However, usually the recursive solution is at least slightly slower and uses more memory than the loop structure.

[1]Data structures can also be recursive.
[2]And the other data concerning the earlier recursion rounds.