Terminology
Recursive Algorithms
Recursive constructs are described in terms of themselves. From the Jargon File:A recursive algorithm is simply one that is defined in terms of itself. Some examples:recursion /n./ See recursion. or GNU /gnoo/ 1. [acronym: `GNU's Not Unix!']
Natural Numbers
bool IsNatural(unsigned 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
In programming terms, recursion is very much like iteration (looping). In fact, anything you can do iteratively, you can do recursively.
The fundamental difference between iteration and recursion is that you make a function call instead of jumping to the top of a loop. Here's an iterative example:
Counting down from 5 iteratively:
Version 1 | Version 2 |
---|---|
void PrintDown1() { for (int i = 5; i > 0; i--) cout << i << endl; } |
void PrintDown2() { int i = 5; while (i > 0) { cout << i << endl; i--; } } |
Counting down from 5 recursively using a global variable: (This is a less desirable way to implement a recursive function. In fact, often times using a global prevents recursion.)
You would simply call it like this:
Version 1 Version 2 Version 3 int Value = 5; void PrintDown1() { if (Value < 1) return; else { cout << Value << endl; Value--; PrintDown1(); } } int Value = 5; void PrintDown2() { if (Value > 0) { cout << Value << endl; Value--; PrintDown2(); } } int Value = 5; void PrintDown3() { if (Value > 0) { cout << Value-- << endl; PrintDown3(); } }
This is a special form of recursion called tail recursion because the very last statement is the recursive call. Some compilers can optimize this into something like this, for example:PrintDown1();
With a very high number for Value, the non-optimized version will overflow the stack. On my computer, it will overflow the stack if Value is set over 525,000. The optimized version (gcc with -O2 or -O3) has no limitations. Also, if you remove the cout call, some compilers will simply set Value to 0, since that's the ultimate goal.int Value = 5; void PrintDown2() { top: if (Value > 0) { cout << Value << endl; Value--; goto top; } }
Counting down from 5 recursively using parameters: (This is a better way to implement a recursive function.)
You would call it like this:
Version 1 Version 2 void PrintDown1(int Value) { if (Value < 1) return; else { cout << Value << endl; PrintDown1(Value - 1); } } void PrintDown2(int Value) { if (Value > 0) { cout << Value << endl; PrintDown2(Value - 1); } }
PrintDown1(5);
For example, it is easier to sort 2 items than it is to sort 3, or 4 items.In fact, the easiest sort is when there is only 1 element in the data set.
|
|
void quicksort(void *base, size_t elem_count, size_t elem_size, int (* compare)(const void *, const void *)) { // If there is more than 1 element in the array, // Call partition to partition the array // Call quickqsort to sort the left portion // Call quickqsort to sort the right portion // End }
int maximum(int array[], int left, int right) { // One element in the array (the base case) if (left == right) return array[left]; int middle = (left + right) / 2; int x = maximum(array, left, middle); // max in left int y = maximum(array, middle + 1, right); // max in right // Choose the larger if (x > y) return x; else return y; }
A couple of other "optimized" functions: (Never do this!)
int maximum2(int array[], int left, int right) { if (left == right) return array[left]; int middle = (left + right) / 2; if (maximum2(array, left, middle) > maximum2(array, middle + 1, right)) return maximum2(array, left, middle); else return maximum2(array, middle + 1, right); } int maximum3(int array[], int left, int right) { if (left == right) return array[left]; int middle = (left + right) / 2; return maximum3(array, left, middle) > maximum3(array, middle + 1, right) ? maximum3(array, left, middle) : maximum3(array, middle + 1, right); }
Self-check Compile and run the different versions of maximum above with the test driver below and see what you get.
Test program:void TestMax() { int a[] = {3, 6, 8, 3, 7, 5, 9, 1, 2, 6, 4}; int max = maximum(a, 0, 10); printf("Max is %i\n", max); }
Never use a conditional operator ( ? : ) in any code on any exam.
Other Examples
A classic example is the definition of factorial. To calculate N! for all values >= 0:Expanded general form:(base case) If N = 0 then N! = 1 (recursive case) 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
A very simplified BNF Grammar (Backus-Naur Form) describing an expression:
(recursive case) <expression> ::= <number> | <number> <operator> <expression> (recursive case) <number> :: = <digit> | <digit> <number> (base case) <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 (base case) <operator> ::= + | - | * | /
Given the trivial grammar above, these are some example expressions:
These are invalid:5 3 + 5 7 / 2 6 - 4 * 3
The C programming language BNF-5 (3 + 5) 12 % 7 6.5 * 2 14 * a + b
Recursion and Function Calls
int i = 5; // Terminates when i <= 0 while (i > 0) { // do stuff i--; } // Terminates when i >= 10 for (i = 0; i < 10; i++) { // do stuff } // Terminates when something is true while (1) { // do stuff if (/* something is true */) break; }
void foo() { foo(); } Clang warning: all paths through this function will call itself [-Winfinite-recursion]
void foo() void bar() { { bar(); foo(); } }
First, write down the algorithm in English:
What's the complexity of the code above? (Assume N characters in the string. How many characters did we have to touch?)void print_string(const char *string) { while (*string) putchar(*string++); }
Using a for loop:
What's the complexity of the code above? (Assume N characters in the string. How many characters did we have to touch?)void print_string(const char *string) { int len = strlen(string); for (int i = 0; i < len; i++) putchar(string[i]); }
Using another for loop (as seen in freshmen programming):
What's the complexity of the code above? (Assume N characters in the string. How many characters did we have to touch?)void print_string(const char *string) { for (int i = 0; i < strlen(string); i++) putchar(string[i]); }
Printing a string recursively:
First, write down the algorithm in English:
The head is a single item (no recursion, base case), and the tail is a (smaller) string.
The rest of the string is itself a string, (albeit a shorter one), and we just stated above how to print a string recursively.
Code to implement the algorithm might look like this:
Of course, our black box function print_the_rest needs to be implemented at some point. How do we print the rest of the string? Same as before:// Print the first character, and then print the rest ... void print_string_rec(const char *string) { putchar(*string); // Print the first character print_the_rest(string + 1); // Print the rest of the string }
and then:void print_the_rest(const char *string) { putchar(*string); // Print the first character print_the_rest2(string + 1); // Print the rest of the string }
etc. It's not difficult to see the pattern here and a lot of duplicated functionality (not to mention the limitation of printing only strings of a certain length). We only need one version of these identical functions:void print_the_rest2(const char *string) { putchar(*string); // Print the first character print_the_rest3(string + 1); // Print the rest of the string } void print_the_rest3(const char *string) { putchar(*string); // Print the first character print_the_rest4(string + 1); // Print the rest of the string }
But this is still the same functionality as our original print_string_rec function:void print_the_rest(const char *string) { putchar(*string); // Print the first character print_the_rest(string + 1); // Print the rest recursively }
So, we can fold all of this functionality into one function:void print_string_rec(const char *string) { putchar(*string); // Print the first character print_the_rest(string + 1); // Print the rest of the string }
At this point, we're missing a very crucial piece of code: the terminating condition. (Recursion is a form of iteration and iterations need to end.)void print_string_rec(const char *string) { putchar(*string); // Print the first character print_string_rec(string + 1); // Print the rest of the string }
The terminating condition for printing a string should be when we encounter the NUL character at the end:
This function will handle NUL-terminated strings of any length (including zero-length).void print_string_rec(const char *string) { // If it's the NUL character, do nothing (just return) if (*string) { putchar(*string); // Print the first character print_string_rec(string + 1); // Print the rest of the string } }
Printing the string magic:
Notes:print_string_rec("magic") putchar("m") print_string_rec("agic") putchar("a") print_string_rec("gic") putchar("g") print_string_rec("ic") putchar("i") print_string_rec("c") putchar("c") print_string_rec("NUL") <---- recursion terminates
More Examples
Printing a string in reverse (iteratively):Algorithm:
orvoid print_string_rev1(const char *string) { int len = strlen(string); for (int i = len; i > 0; i--) putchar(*(string + i - 1)); }
void print_string_rev2(const char *string) { int len = strlen(string); while (len) { putchar(*(string + len - 1)); len--; } }
Algorithm:
Compare this function with the first string-printing function:void print_string_rec_r(const char *string) { // If it's the NUL character, do nothing and return if (*string) { print_string_rec_r(string + 1); // Print the rest of the string, recursively putchar(*string); // Print the head } }
Also, compare the iterative version with the recursive version. It is almost impossible to get the recursive function wrong, whereas, it's very easy to get the iterative version wrong!void print_string_rec(const char *string) { // If it's the NUL character, do nothing and return if (*string) { putchar(*string); // Print the head print_string_rec(string + 1); // Print the rest of the string, recursively } }
Self-check: Implement these recursive functions:
void PrintTen(int value);
For example, PrintNumbers(4, 8) will print:void PrintNumbers(int start, int end);
4 5 6 7 8
More Recursion Details
Looking again at the factorial function defined recursively:
This function assumes that N > 0 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
Assembly (Showing the tail recursion removal)
Diagram of the recursive function calls:int recursiveFact(int number) { if (number == 0) return 1; else return number * recursiveFact(number - 1); }
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!)
Base: MaxVal([x]) = x Recursive: MaxVal([a1, a2, a3, ..., aN]) = if a1 > MaxVal([a2, ..., aN]) then a1 else MaxVal([a2, ..., aN])
Complexities:
Sample code:
Output:int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int b[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; int m; X = 0; m = RecMaxVal(a, sizeof(a) / sizeof(int)); cout << "RecMaxVal is " << m << ", X = " << X << endl; X = 0; m = RecMaxVal(b, sizeof(b) / sizeof(int)); cout << "RecMaxVal is " << m << ", X = " << X << endl;
Larger test:RecMaxVal is 10, X = 1023 RecMaxVal is 10, X = 10
Output (after running for about 7 seconds, compiling with -O2)int array[32]; for (int i = 0; i < 32; i++) array[i] = i; X = 0; m = RecMaxVal(array, sizeof(array) / sizeof(int)); cout << "RecMaxVal is " << m << ", X = " << X << endl;
RecMaxVal is 31, X = 4294967295
Tracing Function Calls (Call stack review)
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() { int x; float f; f = 2.7f; x = f1(2, 3); cout << x << endl; }
in f3 is executed. (Actually, it's not exactly like 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; } int main() { int x; float f; f = 2.7f; x = f1(2, 3); cout << x << endl; return 0; }
The "stack" and Recursive Programming
Given this recursive definition of Print: (which prints a section of an array)
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)
Self Check: Write another version to print a "section" of the array. This function should only take 2 parameters: A pointer to an element in an array and the number of elements to print.
Simulating recursionPrint a range of integers in reverse recursively:
Algorithm:
Print a range of integers in reverse iteratively, using an explicit 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) { // Simulates the runtime stack std::stack<int> s; // Simulating the recursive calls while (first <= last) s.push(first++); // Simulating the returns from the recursive calls while (!s.empty()) { cout << list[s.top()] << endl; s.pop(); } }
Reversing Strings
Here's a very simple function that reverses a range of characters in a string iteratively (in place without printing):
Simple usage:void ReverseStringIt(char *s, int from, int to) { while (from < to) { // Swap the edges first char c = s[from]; s[from] = s[to]; s[to] = c; // Advance the "edge pointers" from++; to--; } }
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) { if (from < to) { // Swap the edges char c = s[from]; s[from] = s[to]; s[to] = c; // Reverse the "middle" characters ReverseStringRec(s, from + 1, to - 1); } }
Self Check: Write a recursive function that can reverse a singly-linked list in-place. Use the Node struct and function prototype below. (You may want to first try to do this iteratively.)
struct Node { Node *next; int data; };
void ReverseListRec(Node*& list, Node *prev); // Recursively reverse list in-place
Self Check: Take any existing linked-list implementation that you've written and rewrite some of the functions to be recursive. (e.g find, free, length, etc.) Caution: using recursion on a linked-list is generally not a good idea, especially if the list can be very long as you can run out of stack space.
Here is the code to print out a singly-linked list recursively and in reverse. It is trivial to do so:More linked-list examples.void List::PrintListRec(const Node* list) const { if (list) { std::cout << list->data << " "; PrintListRec(list->next); } } void List::PrintListRevRec(const Node* list) const { if (list) { PrintListRevRec(list->next); std::cout << list->data << " "; } }
I hope you can see now why recursion can be a much better solution than iteration in certain situations. You will do well to familiarize yourself and become comfortable with recursion. Recursion can take a really ugly solution and turn it into an efficient and elegant solution.
Printing Matrices Recursively
In these examples, each row in the matrix is a recursive call and each column is done iteratively. There are several ways to accomplish the output. ROWS is 8 and COLUMNS is 8 in all of the examples.
Of course, all of the above could have easily been done using a nested loop (iteration for rows and columns). These examples demonstrate various ways to perform iteration using recursion instead.
Code Output // ROWS in order, COLUMNS in order void print_matrix1(int row) { if (row > ROWS) return; // print columns for (int i = 0; i < COLUMNS; i++) printf("%4i", row * COLUMNS + i); printf("\n"); // next row print_matrix1(row + 1); } 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 Call: print_matrix1(0); // ROWS reversed, COLUMNS reversed void print_matrix2(int row) { if (row == 0) return; // print columns for (int i = COLUMNS; i > 0; i--) printf("%4i", (row - 1) * COLUMNS + i); printf("\n"); // next row print_matrix2(row - 1); } 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 Call: print_matrix2(8); // ROWS reversed, COLUMNS in order void print_matrix3(int row) { if (row > ROWS) return; // next row print_matrix3(row + 1); // print columns for (int i = 0; i < COLUMNS; i++) printf("%4i", row * COLUMNS + i); printf("\n"); } 64 65 66 67 68 69 70 71 56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39 24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 Call: print_matrix3(0); // ROWS in order, COLUMNS reversed void print_matrix4(int row) { if (row == 0) return; // next row print_matrix4(row - 1); // print columns for (int i = COLUMNS; i > 0; i--) printf("%4i", (row - 1) * COLUMNS + i); printf("\n"); } 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 24 23 22 21 20 19 18 17 32 31 30 29 28 27 26 25 40 39 38 37 36 35 34 33 48 47 46 45 44 43 42 41 56 55 54 53 52 51 50 49 64 63 62 61 60 59 58 57 Call: print_matrix4(8); int WIDTH = 8; int SIZE = 64; // all recursive void print_matrix5(int index) { if (index >= SIZE) return; printf("%4i", index); // print newline? if (!((index + 1) % WIDTH)) printf("\n"); print_matrix5(index + 1); } 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Call: print_matrix5(0);
Fibonacci Example
For all values of n > 1, we have the Fibonacci numbers defined recursively as: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 does Foo do?
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; } } |
int Foo(int X) { if (X == 0) return 0; else if (X == 1) return 1; else { int v1 = 1, v2 = 0; for (int i = 2; i <= X; i++) { int temp = v1; v1 += v2; v2 = temp; } return v1; } } |
How many times does the for loop iterate? What is the complexity?
Now, implementing it recursively from the definition is trivial and almost writes itself:
How many times is the function called for a given number?int RecFibonacci(int number) { if (number == 0) // Base case: F0 = 0 return 0; else if (number == 1) // Base case: F1 = 1 return 1; else // Recursive case: 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 evaluates F6 (25 calls) and F5 (15 calls) plus the original call for F7.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 67 109
So, the number of calls to evaluate FN is F(N - 1) + F(N - 2) + 1
This is roughly on the order of 2N, which as we know is very, very bad.
Notes
Self-check Modify the recursive Fibonacci implementation above to improve it (it should be O(N)). The key to solving this is to recognize what (discarded) calculations must be passed to the next call of the function.
Case Study: The Eight Queens Problem (and related)
The goal behind the problem is to place 8 queens on a chessboard so that no queen threatens another. Another way to state this (for those not familiar with the capabilities of the queen in chess) is to place 8 markers on a standard 8x8 chess board so that no two markers are on the same line vertically, horizontally, or diagonally.
Queen's movements A solution
Number of solutions for boards of size:
Pseudo-code for a recursive implementation:Solutions for 1 x 1: 1 Solutions for 2 x 2: 0 Solutions for 3 x 3: 0 Solutions for 4 x 4: 2 Solutions for 5 x 5: 10 Solutions for 6 x 6: 4 Solutions for 7 x 7: 40 Solutions for 8 x 8: 92 Solutions for 9 x 9: 352 Solutions for 10 x 10: 724 Solutions for 11 x 11: 2,680 Solutions for 12 x 12: 14,200 Solutions for 13 x 13: 73,712 Solutions for 14 x 14: 365,596 Solutions for 15 x 15: 2,279,184 Solutions for 16 x 16: 14,772,512 Solutions for 17 x 17: 95,815,104 Solutions for 18 x 18: 666,090,624
bool board[8][8];
int solutions = 0;
int Solve()
{
// Initialize all squares to empty
// Call PlaceQueen with row 0 to start the search
PlaceQueen(0);
// Return number of solutions
}
void PlaceQueen(int row)
{
// Put a queen in the specified row, starting at the first column
for(int column = 0; column < 8; column++)
{
board[row][column] = true;
// Call Threatened to determine if placement was valid
// (may have to remove it and try another column or go back to previous row)
// Call PlaceQueen recursively to place queens in subsequent rows
// until a queen is successfully place in last row, or all rows/columns
// have been attempted
}
}
int Threatened(int row, int column)
{
// Determine if there is a collision with any other queen
// (vertically, horizontally, and diagonally)
// If there is a collision, return TRUE, otherwise return FALSE
}
A client would simply do this:
int solutions = Solve(); // Returns the number of solutions
Another version of this uses rooks instead of queens: (Download)
Number of solutions for boards of size:
It's just the factorial of the board size.Solutions for 1 x 1: 1 Solutions for 2 x 2: 2 Solutions for 3 x 3: 6 Solutions for 4 x 4: 24 Solutions for 5 x 5: 120 Solutions for 6 x 6: 720 Solutions for 7 x 7: 5040 Solutions for 8 x 8: 40320
Yet-another chess-related recursive algorithm is "The Knight's Tour":
Valid moves for the knight A demo program
Download/run the demo program.
Several years ago, a Digipen student (John Lykins) took his 8-Queens implementation and created a 3D driver for it:
An OpenGL version of the driver for the Knight's Tour algorithm.
Several years ago a Digipen student (Nathan Williams) created an OpenGL version of the driver for his Knight's Tour implementation.