CS 250 Problem Solving and Programming

[ Home | Syllabus |Course Notes]


Recursion: Part I


A Journey of a Million Steps

Many problems can be divided into two parts:

  1. Solve an immediate problem (take one step)
  2. Solve a smaller version of the original problem (a journey of 999,999,999 steps) using the same approach over and over.

This divide and conquer problem solving strategy involves:

A Journey

Loops are a classic example of this strategy.
To sum the values in an array.

Proof by Induction is another example

Recursion is yet another example

NOTE: recursions are often computed backwards of loops. Loops usually go from Base to N. Recursions usually go from N to Base. But in practice, the implementation of recursion often goes like the loop.

Here the post condition of the resursive procedure is like the loop invariant.


Criteria of a Recursive Solution

  1. Calls itself
  2. Makes problem smaller
  3. Solves for BASE CASE(s)
  4. Will terminate (ie. will eventually reach base case)

Computing N factorial: A Classic Example

Many mathematical concepts are defined by a recurrence relationship which can be used to form a loop invariant or a recursion post condition.

Example: N factorial:
N! = N * (N-1) * (N-2) * ... * 1 = N * (N-1)!

The multiplication is the immediate step, (N-1)! is the smaller version of the same problem.

BASE CASE: 1! = 1 or 0! = 1

Leads to the following recursive definition
(Recursive means defined in terms of itself - a kind of circular defintion - except the base case and getting smaller breaks the circle).

factorial(N) = 1, if N = 0
factorial(N) = N * factorial(N-1), if N > 0

Which leads (naturally) to the following recursive program:

// Chap 2, p 55
int Fact(int N)
// ---------------------------------------------------
// Computes the factorial of a nonnegative integer.
// Precondition: N must be greater than or equal to 0.
// Postcondition: Returns the factorial of N; N is
// unchanged.
// ---------------------------------------------------
{
   if (N == 0)
      return 1;
   else
      return N * Fact(N - 1);
}  // end Fact

Illustrating Recursion: the Box Method

As an example, compute Fact(3)

 


Making a Function Call

How does the computer handle suspending and reactivating recursive function calls?


Activation Records and the Run Time Stack

For the example given above:

 


Write A String Backwards

click here for complete program

// Chapter 2, p 62
#include <string.h>

const int MAX_LENGTH = 30;
typedef char stringType[MAX_LENGTH+1];

void WriteBackward(stringType S, int Size)
// ---------------------------------------------------
// Writes a character string backward.
// Precondition: The string S contains Size 
// characters, where Size >= 0.
// Postcondition: S is written backward, but remains
// unchanged.
// ---------------------------------------------------
{
   if (Size > 0)
   {  // write the last character
      cout << S[Size-1];

      // write the rest of the string backward
      WriteBackward(S, Size - 1);  // Point A
   }  // end if

   // Size == 0 is the base case - do nothing
}  // end WriteBackward

Click here to see example

 


Another Way to Write the String Backwards

WriteBackward2(S)
   if (string is empty)
      Do nothing
   else {
      WriteBackward2(S minus first character)
      Write first character
   }

Click here to see example


Raising an Integer to a Power

What do we know about this problem?

BaseCase: x^0 = 1

Extension Case: x^N = x*x^(N-1) , if N>0

This knowledge is the basis for both iterative (loop based) and recursive solution.

// Chap 2, p 71, p 72

// Demonstrates Pow1, Pow2, and Pow3.
#include <iostream.h>

int Pow1(int X, int N)
// ---------------------------------------------------
// Exponentiation function -- ITERATIVE SOLUTION
// Precondition: X is an integer; N is a nonnegative
// integer.
// Postcondition: Returns X raised to the Nth power.
// ---------------------------------------------------
{
   int Temp = 1; // Base Case x^0 = Temp
   for (int Exponent = 1; Exponent <= N; ++Exponent)
// Loop Invariant: x^(Exponent-1) = Temp
      Temp *= X;
   return Temp;
}  // end Pow1

int Pow2(int X, int N)
// Exponentiation function -- RECURSIVE SOLUTION
{
   if (N == 0)
      return 1;
   else
      return X * Pow2(X, N-1);
}  // end Pow2


Use some more knowledge about this problem area
(x^y)*(x^z) = x^(y+z)
let y = z = N/2, then
(x^(N/2)) * (x^(N/2)) = x^N
e.g. (x^3) * (x^3) = x^6

This works for N even, for N odd, x^N = x*(x^((N-1)/2))*(x^((N-1)/2))

This is nice since we only make one call to get each x^(N/2) and
the size of the problem is cut in half each time (instead of just reducing by one
as in the previous solution).

For instance, in the above algorithm x^8 = x*x^7=x*x*x^6=x*x*x*x^5 ...
which takes 9 recursive calls, (for N=9,8,7,6,5,4,3,2,1,0)

But with our new strategy of halfing
x^8 = (x^4)*(x^4) = (x^2)*(x^2)*(x^2)*(x^2) = (x^1)*... = x*x^0*...
which only takes 5 recursive calls! (for N=8,4,2,1,0)


int Pow3(int X, int N)
// Exponentiation function -- MORE EFFICIENT
// RECURSIVE SOLUTION
{
   if (N == 0)
      return 1;

   else
   {  int HalfPower = Pow3(X, N/2);
      if (N % 2 == 0)
	 return HalfPower * HalfPower;      // even N

      else
	 return X * HalfPower * HalfPower;  // odd N
   }  // end else
}  // end Pow3

Click here to see complete program

Click here to see example


Copyright chris wild 1997.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: February 07, 1997.