Recursion in C++
Using tail recursion and Fibonnaci-style recursion to solve the Fibonnaci sequence
The simple and most obvious way to use recursion to get the Nth term of the Fibonnaci sequence is this
int get_term_fib(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return get_term_fib(n - 1) + get_term_fib(n - 2);
}
However, this algorithm does not scale for higher terms: for bigger and bigger n
, the number of function calls that you need to make grows exponentially. This can be replaced with a simple tail recursion.
int get_term_fib(int n, int prev = 0, int curr = 1)
{
if (n == 0)
return prev;
if (n == 1)
return curr;
return get_term_fib(n - 1, curr, prev + curr);
}
Each call to the function now immediately calculates the next term in the Fibonnaci sequence, so the number of function calls scales linearly with n
.
Recursion with memoization
Recursive functions can get quite expensive. If they are pure functions (functions that always return the same value when called with the same arguments, and that neither depend on nor modify external state), they can be made considerably faster at the expense of memory by storing the values already calculated.
The following is an implementation of the Fibonacci sequence with memoization:
#include <map>
int fibonacci(int n)
{
static std::map<int, int> values;
if (n==0 || n==1)
return n;
std::map<int,int>::iterator iter = values.find(n);
if (iter == values.end())
{
return values[n] = fibonacci(n-1) + fibonacci(n-2);
}
else
{
return iter->second;
}
}
Note that despite using the simple recursion formula, on first call this function is $O(n)$. On subsequent calls with the same value, it is of course $O(1)$.
Note however that this implementation is not reentrant. Also, it doesn’t allow to get rid of stored values. An alternative implementation would be to allow the map to be passed as additional argument:
#include <map>
int fibonacci(int n, std::map<int, int> values)
{
if (n==0 || n==1)
return n;
std::map<int,int>::iterator iter = values.find(n);
if (iter == values.end())
{
return values[n] = fibonacci(n-1) + fibonacci(n-2);
}
else
{
return iter->second;
}
}
For this version, the caller is required to maintain the map with the stored values. This has the advantage that the function is now reentrant, and that the caller can remove values that are no longer needed, saving memory. It has the disadvantage that it breaks encapsulation; the caller can change the output by populating the map with incorrect values.