Natural Numbers
bool IsNatural(int number)
{
if (number == 1)
return true;
else
return IsNatural(number - 1);
}
Call stack for IsNatural(5):
IsNatural(int 1) line 127 <--- base case stops the recursion IsNatural(int 2) line 132 + 12 bytes IsNatural(int 3) line 132 + 12 bytes IsNatural(int 4) line 132 + 12 bytes IsNatural(int 5) line 132 + 12 bytes main() line 335 + 12 bytes mainCRTStartup() line 206 + 25 bytes KERNEL32! 7c4e87f5()
Some example expressions:<expression> ::= <number> | <number> <operator> <expression> <number> :: = <digit> | <digit> <number> <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 <operator> ::= + | - | * | /
5
3 + 5
12 / 7
6 - 14 * 11
A classic example is the definition of factorial. To calculate N! for all values >= 0:
Expanded general form:If N = 0 then N! = 1 If N > 0 then N! = N * (N - 1)!
Expanded example:N! = N * (N - 1) * (N - 2) * ... * 2 * 1
Some other values:5! = 5 * 4! = 5 * (4 * 3!) = 5 * (4 * (3 * 2!)) = 5 * (4 * (3 * (2 * 1!))) = 5 * (4 * (3 * (2 * (1 * 0!)))) (we've hit the base case) = 5 * (4 * (3 * (2 * (1 * 1)))) = 5 * (4 * (3 * (2 * 1))) = 5 * (4 * (3 * 2)) = 5 * (4 * 6) = 5 * 24 = 120
0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800
Looking again at the factorial function defined recursively:
This function assumes that N >= 1 and by definition 0! = 1. This function is recursive because it is defined in terms of itself. The traditional way of writing recursive definitions is like this:N! = N · (N - 1)!
For example, 5! is defined like this:0! = 1 base case N! = N · (N - 1)! recursive case
And 4! is defined like this:5! = 5 · 4!
And so on:4! = 4 · 3!
Which ultimately gives us:3! = 3 · 2! 2! = 2 · 1! 1! = 1 · 0! 0! = 1 this is the base case
5 · 4 · 3 · 2 · 1 · 1 = 120
Another popular function that is defined recursively is the power function:
This function assumes that N > 0 and by definition X0 = 1. We would write the recursive definition like this:XN = X · X(N - 1)
X0 = 1 base case XN = X · X(N - 1) recursive case
Find the maximum value in a list of integers recursively. (Don't use this method in an actual project!)
Best case: O(n) (Sorted high to low)Base: MaxVal([x]) = x Recursive: MaxVal([a1, a2, a3, ..., aN]) = if a1 > MaxVal([a2, ..., aN]) then a1 else MaxVal([a2, ..., aN])
Tracing Function Calls
Given the 3 functions as defined below:
int f1(int a, int b) { int x, y; x = f2(a); y = 4 + b; return x + y; } |
int f2(int x) { int a, b, c; a = x; b = 5; c = f3(a + b); return c; } |
int f3(int x) { int a; // snapshot taken before // this line executes: a = x * 3; return a; } |
This image below is a "snapshot" of what the runtime stack (memory) looks like just before the statementvoid main(void) { int x; float f; f = 2.7; x = f1(2, 3); cout << x << endl; }
in f3 is executed. (Actually, it's a little more complicated than this, but it gets the point across.)a = x * 3;
![]()
int f1(int a, int b) { int x, y; x = f2(a); y = 4 + b; return x + y; } int f2(int x) { int a, b, c; a = x; b = 5; c = f3(a + b); return c; } int f3(int x) { int a; // snapshot taken before // this line executes: a = x * 3; return a; }
The "stack" and Recursive Programming
Given this recursive definition of Print:
We can print the second, third, and fourth integers in the array using this syntax:void Print(const int *list, int first, int last) { if (first <= last) { cout << list[first] << endl; Print(list, first + 1, last); } }
Print(list, 1, 3)
Write another version to print a "section" of the array. This function should only take 2 parameters: A pointer to an array and the number of elements to print.
Simple simulationReverse a list of integer recursively:
Reverse a list of integers iteratively using a stack:void PrintRev1(const int *list, int first, int last) { if (first <= last) { PrintRev1(list, first + 1, last); cout << list[first] << endl; } }
void PrintRev2(const int *list, int first, int last) { std::stack<int> s; while (first <= last) s.push(first++); while (!s.empty()) { cout << list[s.top()] << endl; s.pop(); } }
Simple usage:void ReverseStringIt(char *s, int from, int to) { char c; while (from < to) { // Swap the edges first c = s[from]; s[from] = s[to]; s[to] = c; // Advance the "edge pointers" from++; to--; } }
Implementing a recursive version:char p[] = "123456789"; ReverseStringIt(p, 0, 8); //987654321 ReverseStringIt(p, 0, 3); //432156789 ReverseStringIt(p, 3, 7); //123876549
void ReverseStringRec(char *s, int from, int to) { char c; if (from < to) { // Swap the edges c = s[from]; s[from] = s[to]; s[to] = c; // Reverse the "internal" characters ReverseStringRec(s, from + 1, to - 1); } }
Write a recursive function that can reverse a singly-linked list in-place. Use the Node struct and function prototype below.
struct Node { Node *next; int data; };
void ReverseListRec(Node*& list, Node *prev); // Recursively reverse list in-place
So the first 10 Fibonacci numbers are: 0, 1, 1, 2, 3, 5, 8, 13, 21, and 34.F0 = 0 base case F1 = 1 base case FN = Fn-1 + Fn-2 recursive case
Implementing this iteratively causes the "elegance" to get lost:
What's the complexity of the iterative function above? How many times does the for loop iterate?int IterFibonacci(int number) { if (number == 0) return 0; else if (number == 1) return 1; else { int v1 = 1, v2 = 0; for (int i = 2; i <= number; i++) { int temp = v1; v1 += v2; v2 = temp; } return v1; } }
Now, implementing it recursively from the definition is trivial and almost writes itself:
What's the complexity of the recursive function above? How many times is the function called for a given number?int RecFibonacci(int number) { if (number == 0) // F0 = 0 return 0; else if (number == 1) // F1 = 1 return 1; else // FN = Fn-1 + Fn-2 return RecFibonacci(number - 1) + RecFibonacci(number - 2); }
To help you really see how bad this is, we can build the execution tree:
The parent/child relationship has the meaning:
"To calculate the parent, we have to calculate the children first."So, to compute F5, we need to first compute F4 and F3. But, in order to compute F4, we need to compute F3 and F2, etc.
This table shows the number of times the RecFibonacci function is called for each value:
For example, the number of calls to RecFibonacci to evaluate F7 is 41. This is because it evalutes F6 (25 calls) and F5 (15 calls) plus the original call for F7. This implementation has Fibonacci complexity.Number F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 Value 0 1 1 2 3 5 8 13 21 34 Calls 1 1 3 5 9 15 25 41 66 108