CSC 306 Introduction to Programming with C++

Introduction to Recursion

Chapter 13


Objectives

  • Learn about recursion

Important Definitions

  • Recursion, recursive, base case, infinite recursion,
  • stack, queue
  • quick sort
There are 2 different kinds of people in the world... those who do not use recursion and those who believe that:
There are 2 different kinds of people in the world... those who do not use recursion and those who believe that
There are 2 different kinds of people in the world... those who do not use recursion and those who believe that
...

Recursion

We have seen that it is legal in C++ for one function to call another. This might lead you to wonder whether or not a function can call itself. The answer is yes, and it turns out to be one of the most useful and interesting things a function can do for certain applications. The process of a function calling itself is called recursion because the call to a function recurs in the current call of the same function. Such functions are said to be recursive.

Example

Consider the following simple function:

void countdown( int n ) {
    if( n == 0 ) { // This is the situation when recursion stops.
	cout <<  "Blastoff!" << endl;
    }
    else {         // call the function recursively with n-1
	cout << n << endl; 
	countdown (n-1);
    }
}
The function is called countdown(...) and it takes a single integer as an argument. If the parameter value is zero, it outputs the word "Blastoff." Otherwise, it outputs the value in the parameter and then calls countdown (itself) with the value of one minus the original parameter as the argument (indicated in red).

Suppose we called the function like this:

void main () {
  countdown (3);
}
The execution of countdown looks like:

The main() function now ends. Therefore, the total output looks like:

3
2
1
Blastoff!

The Basic Idea

Recursion is a common idea in mathematics and logic, and simple recursion consists of two parts:
  1. The base case:
    the flow of execution of the function is such that the recursive call ends. In other words, the function ends without making any more recursive calls! In the example above, it was when the countdown reached zero.
    Warning: Not having a base case or a poorly designed one can result in an infinite recursion, which is VERY BAD!

  2. The recursive call:
    The function must call itself again in order to continue processing information. When a function calls itself, something must be different so that eventually, it will do the base case. Note that if the function keeps doing the recursive call, it will never end, resulting in, you guessed it, infinite recursion. This is VERY BAD!
The reason by infinite recursion is a bad error is that in most programming environments, a program with an infinite recursive function can pretty easily fill up all of the avaiable memory and crash the computer.

The Mechanics of a Recursive Call

It is easier to see how recusion works with the aid of a diagram of how the execution works internally in the computer. Technically speaking, C++ arranges the memory spaces needed for each function call in a structure called a stack, which operates much like a stack of trays. The program calls the main() function when it starts, and space is made on the stack for this function; at this point, it is the only thing on the stack:

main()
Program Start

This space is used to store the temporary variables that main() needs, a space to store the return value when it finishes and other information. Each time a function is called, the memory area for that funtion is placed on the top of the stack, and then taken off again when the execution of the call is completed. Therefore, when main() calls countdown(3), a new memory area is created to for this function on top of the stack. countdown calls itself three times until the base case is reached when n=0, at which point the stack has five function calls stored in it.

main()
Program Start
countdown()
n=3
main()
Called countdown(3)
countdown()
n=2
countdown()
n=3
main()
Called countdown(2)
countdown()
n=1
countdown()
n=2
countdown()
n=3
main()
Called countdown(1)
countdown()
n=0
countdown()
n=1
countdown()
n=2
countdown()
n=3
main()
Called countdown(0)

As the functions end, they are at the top and are "popped" off the stack. A stack is an example of a "last in/first out" structure (as opposed to, for example, a queue, which is a "first in/first out" structure).

Two Mathematical Examples

Students who have taken MAT105 or MAT315 should recognize that recursion has a lot in common with mathematical induction.

Another Example

The following program on the left includes a call to the recursively defined function print_backwards() that (1) inputs a series of characters from the keyboard until it encounters a period, and then (2) prints them backwards on the screen. The one on the right, print_forwards(), inputs characters from the input stream and outputs each one until the user types in a period.

#include<iostream>
using namespace std;	

void print_backwards();  // function prototype
	
int main() {
    print_backwards();
    cout << "\n";

    return 0;
}
	
void print_backwards() {
    char character;

    cout << "Enter a character ('.' to end program): ";
    cin >> character;
    if (character != '.') {
	// output the character after the recursive call returns.
	print_backwards();
	cout << character;
    }
}
#include<iostream>
using namespace std;	

void print_forwards();  // function prototype
	
int main() {
    print_backwards();
    cout << "\n";

    return 0;
}
	
void print_forwards() {
    char character;

    cout << "Enter a character ('.' to end program): ";
    cin >> character;
    if (character != '.') {
	// output the character, and then do recursive call
	cout << character;
	print_forwards();
    }
}
The only difference between the two is the placement of the cout statement relative to the recursive call. The function print_backwards() waits until the recursive call returns before outputing the character input from the keyboard, while print_forward() outputs the character first.

A typical run of print_backwards() might look like:

Enter a character ('.' to end program): H
Enter a character ('.' to end program): i
Enter a character ('.' to end program): .
iH

Recursion and Iteration

From a purely mechanical point of view, recursion is not absolutely necessary because any function that can be defined recursively can also be defined iteratively, i.e. defined using "for", "while" and "do...while" loops. The iterative version of the Factorial(...) function can look like:

// Iterative version of the Factorial function
//precondition: n is positive
int Factorial(int n) {
    int product = 1;

    if (n == 0) {	// do nothing because product is already the factorial
    }
    else {		// calculate the factorial
	for (int i=n ; i > 0 ; i--)
	    product *= i;

    }
    return product;
}
Converting the print_forwards() function is relatively simple:

void print_forwards() {
    char chars;

    do {
	cout << "Enter a character ('.' to end program): ";
	cin >> chars;
	if( chars != '.' ) 
	    cout << chars;
    } 
    while( chars != '.' ) ;
}
Converting the print_backwards() function is more involved because we need more data structures to hold temporary data... In this case, we use an array:

void print_backwards() {
    char chars[MAX_ARRAY_LENGTH];
    int no_of_chars_input = 0;

    // Note that unlike print_forwards(), we cannot ask the user for infinite
    // number of characters.
    do {
	cout << "Enter a character ('.' to end program): ";
	cin >> chars[no_of_chars_input];
	no_of_chars_input++;
    }
    while( chars[no_of_chars_input - 1] != '.' 
	   && no_of_chars_input < MAX_ARRAY_LENGTH);

    for (int count = no_of_chars_input - 2 ; count >=0 ; count--)
	cout << chars[count];
    }
It is debatable whether using recursion or loops are clearere to understand for a specific function. Usually, an iterative definition will require more local variable declarations - for example, the array chars[MAX_ARRAY_LENGTH] in the print_backwards() example above, and the integer variable product in the Factorial(...) function. Temporary memory allocation is made explicit in the iterative versions of the functions by declaring variables, whereas it is implicit in the recursive definitions (the variable n has a different value depending on which recursive call of countdown(...) it is local to).

This extra stack manipulation comes at a price, and recursive versions of functions sometimes run slower and definately use more memory than their iterative counterparts. However, a function using recursion can sometimes be easier to understand and run faster than the iterative version.

Quick Sort - A Recursive Procedure for Sorting

Consider the problem of sorting an array of elements. We have already looked at selection sort and insertion sort that work with nested loops to place the elements in ascending or descending order. Quick sort is a fast a recursively defined sorting algorithm.

Why have different sorting algorithms? It turns out that some algorithms perform much faster than others as the number of elements in an array one needs to sort increases. There is little difference in how long each of the sorting algorithms takes to sort an array of small size (such as a single element). However, this is not true as the number of elements in an array increases. Both insertion and selection sort algorithms are substantially slower than quick sort as the number of elements increases. We will cover in CSC 320 this topic of comparing the relative speeds of one algorithm versus another.

Suppose we start with the following array of 10 integers (int a[10]):

1432119 8112107
a[0] a[9]

The idea of quick sort is to separate the array into two parts by using an element in the array called a pivot. The left portion contains only values less than or equal to the pivot, and the other will contain only values greater than or equal to the pivot. Suppose we pick the pivot element 9. The resulting parts are:

73218
911121014

We can then reapply exactly the same process to the left-hand and right-hand parts separately. This re-application of the same quick sort procedure leads to a recursive definition.

We create this arrangement as follows:

  1. Find a pivot point (P)
    Suppose that first is the index of the first element and last is the index for the last element of the array. Often times, the index to act as a pivot point is found by calculating:
    P = (first + last)/2
    Note that in this example, we have integer division, so P = (0+9)/2 = 4.

  2. Determine which elements to examine on both parts of the array separated by the pivot
    The left and right indexes (lft and rht) is initially set to the extreme left and rightmost indexes, respectively.

    1432119 8112107
    | P |
    lft rht

    Move the rht index to the left until the element it indexes is less than or equal to the value of the pivot; the opposite applies the lft. This is already true.

  3. Swap elements
    If a[lft]>a[rht] swap the two values, which produces:

    732119 81121014
    | P |
    lft rht

  4. Readjust position of lft and rht
    Move lft to the right as long as a[lft] is less than or equal to a[P]. On the opposite side, move rht to the left as long as a[rht] is greater than a[P]. After this operation, the array is as follows:

    732119 81121014
    | P |
    lft rht

  5. Swap values if necessary
    If a[lft]>a[rht] swap the two values, which produces:

    73219 811121014
    | P |
    lft rht

  6. Readjust position of lft and rht
    Move lft to the right as long as a[lft] is less than or equal to a[P]. On the opposite side, move rht to the left as long as a[rht] is greater than a[P]:

    73219 811121014
    | |
    lft rht

  7. Swap the element values

    73218 911121014
    | |
    lft rht

  8. Move rht and lft again.
    Now that lft >= rht is true, this part of the process stops.

  9. Recursive call
    Perform the same steps on the left and right portions of the array.

Here is the quick sort procedure encoded as a C++ function:

//precondition: left < right.
void quick_sort(int list[], int left, int right) {
    int pivot, lft, rht;

    lft = left;
    rht = right;
    pivot = (left + right)/2; // integer division

    do {
	while( list[rht] > list[pivot] )
	    rht--;
    	while( list[lft] < list[pivot] )
	    lft++;
	if( lft <= rht ) {
	    swap( list[lft], list[rht] );
	    lft++;
	    rht--;
	}
    }
    while( rht >= lft );

    if( left < rht )
    	quick_sort(list, left, rht);
    if( lft < right )
    	quick_sort(list, lft, right);
}

Fibonacci's Rabbits

An interesting original problem that Fibonacci investigated in the year 1202 was about how quickly rabbits could breed in ideal circumstances. Suppose we start with a newly-born pair of rabbits, one male and one female that are put in a field. There are several assumptions we make about these rabbits:
  • Rabbits are able to mate at the age of one month so that at the end of its second month a female can produce another pair of rabbits.
  • Our rabbits never die and
  • The female always produces one new pair (one male, one female) every month from the second month onwards.

http://www.homestead.com/mentorandmultiply/files/rabbits.jpg

The puzzle that Fibonacci posed was...

How many pairs will there be in one year?
  1. At the end of the first month, they mate, but there is still one only 1 pair.
  2. At the end of the second month the female produces a new pair, so now there are 2 pairs of rabbits in the field.
  3. At the end of the third month, the original female produces a second pair, making 3 pairs in all in the field.
  4. At the end of the fourth month, the original female has produced yet another new pair, the female born two months ago produces her first pair also, making 5 pairs.

The number of pairs of rabbits in the field at the start of each month is 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

http://www.jimloy.com/algebra/rabbits.gif


Assignment Specifics

This assignment must be completed individually.

  1. Write a recursive function for a function that has one parameter n of type int that returns the nth Fibonacci number.
  2. The Fibonacci numbers are defined as follows:
    1. F(0)=1
    2. F(1)=1
    3. F(i+2)=F(i)+F(i+1) for i = 0, 1, 2, ...
    4. In other words, the Fibonacci numbers are the sum of the previous two numbers. Note that the first few Fibonacci numbers are 1, 1, 2, 3, 5, 8, ...
  3. Embed this recursive function in a program and test it.

Notes:

When you are finished writing and testing your assignment, drop the file with your source code, YourLastName_306A19.cpp, into the CSC306_A19 dropbox on the Academic server.


Back to Introduction to Computer Programming with C++ Homepage