CS 1723-001 Data Structures
Fall 2001 -- Lectures for Week 9


Recursion

Week's Objectives:
    Study recursion by means of examples.

Details About the Objectives (Examples of Recursion):

  1. The factorial function. This function n! is often defined as
    
            n! = n * (n-1) * (n-2) * . . . * 3 * 2 * 1, n > 0
          
    From this formula, one can write the following Java function:
    
            public int fact(int n) {
               int res = 1;
               for (int i = n; i > 1; i++)
                  res = res*i;
            }
          
    However, the formula above has problems: it isn't exactly precise (the three dots), and it doesn't work for n =2, say. Instead, mathematicians would generally use the following definition:
    
            n! = 1, if n = 1,
            n! = n * (n-1)!, if n > 1
          
    This definition is recursive (refers to itself), and amazingly, it can be directly implemented in Java:
    
            public int fact(int n) {
               if (n == 1) return 1;
               return n * fact(n-1);
            }
          
  2. Converting a number to a different base. The example below converts a given base 10 number to base 8. (The same technique will work for any conversions.)
            n = 93.  Repeatedly divide by 8 and take the remainder.
                     Then repeat with the quotient by 8. Stop at 0 quotient.
    
            93%8 == 5 (remainder) and 93/8 == 11 (quotient)
            11%8 == 3 (remainder) and 11/8 ==  1 (quotient)
             1%8 == 1 (remainder) and  1/8 ==  0 (quotient)
          
    The numbers in boldface (the remainders) are the base 8 digits of 93, generated backwards. We want to write the forwards, to get 135 (base 8), and for this purpose, we can push them on a stack, and pop them off, as in the following code:
    
            public void writebase(int n) {
               System.out.print(n + " in base 8 = ");
               while (n > 0) {
                  push(n%8);
                  n = n/8;
               }
               while (!empty())
                  System.out.print(pop());
               System.out.println("(base 8)");
            }
          
    Here is complete code to carry out the above program. (The code includes the "simplest possible" stack, to avoid ugly details.)
    // BaseConversion: comvert to base 8
    public class BaseConversion { 
       public int[] s = new int[100]; // storage for the stack
       public int top = 0; // points one above the top of the stack
       public int pop() { return s[--top]; } // pop and return top item
       public void push(int n) { s[top++] = n; } // push another int
       public boolean empty() { return top == 0; } // is stack empty?
    
       public void writebase(int n) {
          while (n > 0) {
             push(n%8);
             n = n/8;
          }
          while (!empty())
             System.out.print(pop());
       }
    
       public static void main(String[] args) {
          BaseConversion baseConversion = new BaseConversion();
          System.out.print("93 in base 8 = ");
          baseConversion.writebase(93);
          System.out.println(" (base 8)");
       }
    }
          
    The output from this program is:
    
          93 in base 8 = 135 (base 8)
          
    However, there is a much simpler version of the writebase function that uses recursion. Here it is enclosed in a class. It has the same output as the previous program.
    // BaseConvRecurs: comvert to base 8
    public class BaseConvRecurs { 
    
       public void writebase(int n) {
          if (n != 0) {
             writebase(n/8);
             System.out.print(n%8);
          }
       }
    
       public static void main(String[] args) {
          BaseConvRecurs baseConvRecurs = new BaseConvRecurs();
          System.out.print("93 in base 8 = ");
          baseConvRecurs.writebase(93);
          System.out.println("(base 8)");
          
       }
    }
    
    Notice that the recursive function is much simpler. (It is also possible to make use of strings and concatenation to avoid the stack and recursion.)

    Here is a handout that shows the recursive calls made to writebase in the above example. (This example shows the code for writebase in the C language, but the ideas are the same.

    In Postscript, In PDF.

  3. The Fibonacci sequence. This is the sequence of numbers: f0 = 0, f1 = 1, f2 = 1, f3 = 2, f4 = 3, f5 = 5, f6 = 8, f7 = 13, f8 = 21, f9 = 34, ...

    Here is a short recursive program to calculate them and to measure the elasped time (not the CPU time):

    // Fibonacci: compute Fibonacci numbers
    public class Fibonacci { 
    
       public static int f(int n) {
          if (n <= 1) return n;
          return f(n-1) + f(n-2);
       }
    
       public static void elapsedTime(long startTime) {
          long stopTime = System.currentTimeMillis();
          double elapsedTime = ((double)(stopTime - startTime))/1000.0;
          System.out.println("Elapsed time: " + elapsedTime + " seconds");
       }
    
       public static void main(String[] args) {
          long startTime = System.currentTimeMillis();
          for (int i = 0; i< 20; i++)
             System.out.println("n: " + i + "\tF(n): " + f(i));
          System.out.println();
          for (int i = 5; i< 50; i += 5) {
             System.out.print("n: " + i + "\tF(n): " + f(i) + "\t");
             elapsedTime(startTime);
          }
       }
    }
    
    With output (slightly edited):
    n:  0   F(n): 0
    n:  1   F(n): 1
    n:  2   F(n): 1
    n:  3   F(n): 2
    n:  4   F(n): 3
    n:  5   F(n): 5
    n:  6   F(n): 8
    n:  7   F(n): 13
    n:  8   F(n): 21
    n:  9   F(n): 34
    n: 10   F(n): 55
    n: 11   F(n): 89
    n: 12   F(n): 144
    n: 13   F(n): 233
    n: 14   F(n): 377
    n: 15   F(n): 610
    n: 16   F(n): 987
    n: 17   F(n): 1597
    n: 18   F(n): 2584
    n: 19   F(n): 4181
    
    n:  5   F(n): 5 		Elapsed time:   0.02 seconds
    n: 10   F(n): 55        	Elapsed time:   0.032 seconds
    n: 15   F(n): 610       	Elapsed time:   0.032 seconds
    n: 20   F(n): 6765      	Elapsed time:   0.034 seconds
    n: 25   F(n): 75025     	Elapsed time:   0.046 seconds
    n: 30   F(n): 832040    	Elapsed time:   0.186 seconds
    n: 35   F(n): 9227465   	Elapsed time:   1.63 seconds
    n: 40   F(n): 102334155 	Elapsed time:  17.687 seconds
    n: 45   F(n): 1134903170        Elapsed time: 197.155 seconds
    
    Contrast the above recursive calculation of the Fibonacci numbers with the following non-recursive version, whose code is much trickier and more error-prone:
    // Fibonacci0: compute Fibonacci numbers without recursion
    public class Fibonacci0 { 
    
       public static long f(long n) {
          long f0 = 0, f1 = 1, f2 = -1;
          for (long i = 1; i < n; i++) {
             f2 = f0 + f1;
             f0 = f1;
             f1 = f2;
          }
          return f2;
       }
    
       public static void elapsedTime(long startTime) {
          long stopTime = System.currentTimeMillis();
          double elapsedTime = ((double)(stopTime - startTime))/1000.0;
          System.out.println("Elapsed time: " + elapsedTime + " seconds");
       }
    
       public static void main(String[] args) {
          long startTime = System.currentTimeMillis();
          for (int i = 2; i< 20; i++)
             System.out.println("n: " + i + "\tF(n): " + f(i));
          System.out.println();
          for (long i = 5; i< 95; i += 5) {
             System.out.print("n: " + i + "\tF(n): " + f(i) + "\t");
             elapsedTime(startTime);
          }
       }
    }
    
    The following output shows that none of these calculations take any time to speak of. A calculation of the 90th Fibonacci number using the previous method would take at least 10^11 seconds, or over 3000 years, not allowing for the fact that arithmetic using longs would be slower, and that computer memory would not remotely permit the calculation.
    n: 2    F(n): 1
    n: 3    F(n): 2
    n: 4    F(n): 3
    n: 5    F(n): 5
    n: 6    F(n): 8
    n: 7    F(n): 13
    n: 8    F(n): 21
    n: 9    F(n): 34
    n: 10   F(n): 55
    n: 11   F(n): 89
    n: 12   F(n): 144
    n: 13   F(n): 233
    n: 14   F(n): 377
    n: 15   F(n): 610
    n: 16   F(n): 987
    n: 17   F(n): 1597
    n: 18   F(n): 2584
    n: 19   F(n): 4181
    
    n: 5    F(n): 5 Elapsed time: 0.02 seconds
    n: 10   F(n): 55        Elapsed time: 0.032 seconds
    n: 15   F(n): 610       Elapsed time: 0.033 seconds
    n: 20   F(n): 6765      Elapsed time: 0.033 seconds
    n: 25   F(n): 75025     Elapsed time: 0.035 seconds
    n: 30   F(n): 832040    Elapsed time: 0.035 seconds
    n: 35   F(n): 9227465   Elapsed time: 0.035 seconds
    n: 40   F(n): 102334155 Elapsed time: 0.036 seconds
    n: 45   F(n): 1134903170        Elapsed time: 0.037 seconds
    n: 50   F(n): 12586269025       Elapsed time: 0.038 seconds
    n: 55   F(n): 139583862445      Elapsed time: 0.038 seconds
    n: 60   F(n): 1548008755920     Elapsed time: 0.038 seconds
    n: 65   F(n): 17167680177565    Elapsed time: 0.04 seconds
    n: 70   F(n): 190392490709135   Elapsed time: 0.04 seconds
    n: 75   F(n): 2111485077978050  Elapsed time: 0.04 seconds
    n: 80   F(n): 23416728348467685 Elapsed time: 0.041 seconds
    n: 85   F(n): 259695496911122585        Elapsed time: 0.041 seconds
    n: 90   F(n): 2880067194370816120       Elapsed time: 0.041 seconds
    
    The moral here is important: recursion is a powerful and useful tool that every computer scientist must be familiar with. However, recursion is not always the appropriate method.

  4. Recursive Reversal of a String. Here is another illustration of recursion in action.
    // ReverseString: test a recursive method to reverse a String
    //  Uses the String method substring.
    //    s.substring(i) goes from index i to the end.
    //    s.substring(i, j) goes from index i up to but not including index j
    
    public class ReverseTest {
    
       // recursive string reversal
       public static String reverse(String s) {
          if (s.length() == 1) return s;
          char ch = s.charAt(0);
          String res = reverse(s.substring(1)) + ch;
          return res;
       }
    
       // recursive: get rid of unnecessary local variables
       public static String reverse2(String s) {
          if (s.length() == 1) return s;
          return reverse(s.substring(1)) + s.charAt(0);
       }
    
       // recursive: work from the other end
       public static String reverse3(String s) {
          if (s.length() == 1) return s;
          return s.charAt(s.length() - 1) +
             reverse(s.substring(0, s.length() - 1));
       }
    
       // NON-RECURSIVE: jus concatenate chars in opposite order
       public static String reverse4(String s) {
          String res = "";
          for (int i = 0; i < s.length(); i++)
             res = s.charAt(i) + res;
          return res;
       } 
    
       public static void main(String[] args) {
          String str = "The quick brown fox jumps over the lazy dog.";
          System.out.println(str);
          System.out.println(reverse (str));
          System.out.println(reverse2(str));
          System.out.println(reverse3(str));
          System.out.println(reverse4(str));
       }
    }
    
    Here is the output:
    The quick brown fox jumps over the lazy dog.
    .god yzal eht revo spmuj xof nworb kciuq ehT
    .god yzal eht revo spmuj xof nworb kciuq ehT
    .god yzal eht revo spmuj xof nworb kciuq ehT
    .god yzal eht revo spmuj xof nworb kciuq ehT
    

  5. Recursive Binary Search. Finally, here is a recursive version of the binary search program that many of you may have seen in a non-recursive form:
    // BinarySearch: test a recursive method to do binary search on an array
    public class BinarySearch {
    
       // need extra "helper" method to feed in correct parameters
       public static int binarySearch(int[] a, int x) { 
          return binarySearch(a, x, 0, a.length - 1);
       }
    
      
       // recursive version requires extra low and high parameters
       public static int binarySearch(int[ ] a, int x, int low, int high) {
          if (low > high)
             return -1; 
          int mid = (low + high)/2;
          if (a[mid] == x)
             return mid;
          else if (a[mid] > x)
             return binarySearch(a, x, low, mid-1);
          else // last possibility: a[mid] < x
             return binarySearch(a, x, mid+1, high);
       }
    
    
       public static void main(String[] args) {
          int[] a = {12, 23, 27, 38, 42, 49, 55, 67, 68, 82, 88, 93};
          System.out.println("12 in position " + binarySearch(a, 12));
          System.out.println("93 in position " + binarySearch(a, 93));
          System.out.println("38 in position " + binarySearch(a, 38));
          System.out.println("67 in position " + binarySearch(a, 67));
          System.out.println("24 in position " + binarySearch(a, 24));
          System.out.println("8 in position " + binarySearch(a, 8));
          System.out.println("99 in position " + binarySearch(a, 99));
       }
    }
    
    Here is the output:
    12 in position 0
    93 in position 11
    38 in position 3
    67 in position 7
    24 in position -1
    8 in position -1
    99 in position -1
    

Revision date: 2001-10-23. (Please use ISO 8601, the International Standard.)