Summary
A function can call itself: recursion solves a problem by reducing it to a smaller instance of the same problem. This lesson translates into C++ recursive definitions from mathematics (the factorial, the power, the digit sum), each with its base case and its recursive case; it shows that induction guarantees their correctness and that a decreasing measure guarantees their termination; it follows with the call stack the frames that pile up and unwind, and explains when the stack runs out. It examines the limit of the result type, which overflows long before the stack; it compares the recursive and the iterative versions of Fibonacci by counting calls, and it closes with a criterion for choosing between recursion and a loop.
Learning objectives
- Write a recursive function, with its base case and its recursive case, from a recursive definition.
- Justify the correctness and the termination of a recursive function by induction on the size of the problem.
- Follow with the call stack the execution of a recursive function and explain stack overflow.
- Compare the cost of the recursive and the iterative versions of Fibonacci by counting their calls and their operations.
Recursive definitions
The factorial
The factorial of a natural number is the product of the natural numbers from 1 to : , with the convention . The ellipsis in that formula hides a loop, and the lesson Loops taught how to write it. Now, the same definition admits another form, which mentions no loop: the factorial of is times the factorial of , and that of 0 is 1.
This is a recursive definition: it defines the factorial of by means of the factorial of a smaller number. It is not circular, because each application of the second rule goes down one step and the staircase ends at the first: . The two rules have names. The first is the base case, which gives the result without referring to anything else; the second is the recursive case, which reduces the problem to a smaller instance of the same problem.
C++ allows a function to call itself, and with that the definition translates almost word for word.
#include <print>// Factorial of n.// Precondition: 0 <= n <= 12 (the bound is justified further on).// Postcondition: returns n! = 1 · 2 · ... · n, with 0! = 1.int factorial(int n){ if (n == 0) { return 1; // base case } return n * factorial(n - 1); // recursive case}int main(){ for (int n{0}; n <= 6; ++n) { std::println("{}! = {}", n, factorial(n)); }}0! = 11! = 12! = 23! = 64! = 245! = 1206! = 720The function has exactly the shape of the definition: an if that handles the base case and a return that applies the recursive case. The call factorial(n - 1) is no different from any other call in the lesson Functions: its argument is evaluated, a new frame is created with its own n and, when it finishes, the returned value is multiplied by the n of the caller.
The contract is not decorative: with the recursive case would call with , then with , and would never reach 0; and does not fit in an int. Many introductory texts write this factorial with the base case if (n == 0 || n == 1), redundant but harmless, and without saying anything about negative numbers or about the size of the result: their code does not terminate with and gives a meaningless result from on. Both defects will be examined in their own section.
Base case and recursive case
Every recursive function has one or more base cases, which are solved directly, and one or more recursive cases, which call the same function with an argument that is smaller in some precise sense and combine what it returns.
The power , with natural, is defined by and : the size is the exponent. The sum of the digits of a natural number (that of 2026 is ) is defined as follows: if , the sum is , because it has a single digit; otherwise, it is the sum of the digits of , which is without its last digit, plus that last digit, . Here the size is the number of digits.
#include <print>// x raised to n.// Precondition: n >= 0.// Postcondition: returns x^n, with x^0 = 1.double power(double x, int n){ if (n == 0) { return 1.0; } return x * power(x, n - 1);}// Sum of the decimal digits of n.// Precondition: n >= 0.// Postcondition: returns the sum of the digits of n.int digitSum(int n){ if (n < 10) { return n; } return digitSum(n / 10) + n % 10;}int main(){ std::println("2^10 = {}", power(2.0, 10)); std::println("1.5^3 = {}", power(1.5, 3)); std::println("sum of the digits of 2026: {}", digitSum(2026));}2^10 = 10241.5^3 = 3.375sum of the digits of 2026: 10In digitSum, the integer division n / 10 removes the last digit and n % 10 extracts it, as in the change of base of the lesson Numerical algorithms. For , the function asks for the digit sum of 202, which asks for that of 20, which asks for that of 2; this one is a base case and returns 2, and the results are combined on the way back: , , .
Writing a recursive function therefore consists in answering two questions: which cases are solved without help, and how the solution of a case is obtained from that of a smaller one. If both answers are correct, so is the function, and the next section proves it.
Loading the content…
Completed
Sign in to save your progress