Lectures 23-24

CSC120: Programming I - Spring 2001
Prof. Christian A. Duncan
csc120@mail.cs.miami.edu

Announcements

Recursion

For many people, recursion is one of the harder topics in programming to grasp. However, it is an extremely important and effective technique. When understood correctly, recursion can be used to make difficult tasks far simpler.
What is recursion?
A method which in its definition (the code) invokes itself is a recursive method. Recursion is simply the topic of recursive methods.

Iteration and recursion are very related to each other. In iteration, it is natural to write a loop to repeat a task a certain amount of times. In recursion, it is natural to write a method that performs a task by calling itself to perform the same task!?!!?

For example, the following program shows two ways to count from a to b: an iterative method and a recursive method.

import java.io.*;

public class SampleCount {
  public static void main(String[] args) {
    System.out.println("Iteratively counting");
    iterativeCount(1,10);
    System.out.println();
    System.out.println("Recursively counting");
    recursiveCount(1,10);
  }

  // Count iteratively from a to b
  public static void iterativeCount(int a, int b) {
    int i;
    for (i = a; i <= b; i++) {
      System.out.println(i);
    }
  }

  // Count recursively from a to b
  public static void recursiveCount(int a, int b) {
    // This is the terminating condition
    if (a > b) {
      return;
    }

    // Print the number a
    System.out.println(a);

    // Now count from a+1 to b
    recursiveCount(a+1,b);
    }
}
The iterative method should be fairly easy to decipher by now. How does the recursive method work here? Let us look at the invocation recursiveCount(1,10). In this case, the argument a gets assigned the value 1 and argument b the value 10.
Is (a > b)? No
Print out a ---> 1
Call recursiveCount(a+1,b) -> rC(2,10)
Is (a > b)? No
Print out a ---> 2
Call recursiveCount(a+1,b) -> rC(3,10)
... many steps later ...
Call recursiveCount(a+1,b) -> rC(10,10)
Is (a > b)? No
Print out a ---> 10
Call recursiveCount(a+1,b) -> rC(11,10)
Is (a > b)? Yes
Return
This is a silly way to count to 10 since it is easier to just use a loop. However, it illustrates the use quite effectively. Counting from 1 to 10 is nothing more than saying 1 and then counting from 2 to 10 which is nothing more than saying 2 and counting from 3 to 10, etc. Whenever an algorithm has a subtask which is a smaller version of the entire algorithm, recursion can be used, but it has to be used carefully.

Printing out the digits

Let us write a piece of code that takes an integer and outputs the integer as digits in word format, like you were giving a telephone number. For example, 3674 would be written as three six seven four.

The following program shows how to do this recursively.

import java.io.*;

public class PrintDigits {
  public static void main(String[] args) {
    System.out.println("Please enter an integer");
    int a = Keyboard.inputInteger();
	
    System.out.println("Number " + a + " is ");
    printInWords(a);
    System.out.println();
  }

  // Print the digits of the number
  public static void printInWords(int number) {
    if (number < 10) {
      // Just one digit... easy
      System.out.print(digitToWord(number) + " ");
    } else {
      // Print out the "truncated part" first
      printInWords(number/10);
      System.out.print(digitToWord(number%10) + " ");
    }
  }

  // Returns the word form of a digit (0-9)
  public static String digitToWord(int digit) {
    if ((digit >= 10) || (digit < 0)) {
      System.out.println("Error: Not a digit");
      System.exit(1);
    }
    return wordDigit[digit];
  }

  public static String[] wordDigit = {
    "zero", "one", "two", "three", "four", 
    "five", "six", "seven", "eight", "nine"};
}
First, if the digit is only one character, you simply print out the digit in text form. For simplicity, I made a method which takes a digit and returns the string form of that digit. There are many ways to do this: if-else conditions, a switch statement, or the one I prefer for this situation which illustrates another important topic: using an array. Notice wordDigit[0] == "zero", wordDigit[1] == "one", etc.

There are several ways to break up this problem. Here is one way:

    Output all but the last digit (in words) 
    Output the last digit (as a word) 
The first part is the recursive part of the solution, it is in fact the exact same problem as the larger problem.
How do you get all but the last digit? Use the / operator. 1234/10 == 123.
How do you get the last digit? Use the % operator. 1234%10 == 4.

Why do we have the conditional at the beginning? If not, the program would never end. Remove it and see.
This is called the terminating condition. The case checked for is often called the stop case or base case. As with loops, you need to have some way to stop the whole process. We had this in the first example, too. Notice that once a > b you don't need to count any further. Otherwise, it would continue counting up and up and up.

Inner workings

What happens during these recursive calls? The java program does not do anything special or different for recursive methods than for any other method. Understanding how it all works though takes some time and thought.

I will describe this process in class, since it is a bit complicated to write down on paper. However, please refer to the book to see how this process works as well. Here is a summary of what happens. Let us look at what happens with printInWords(123).

// Entering printInWords(number == 123)
is (number < 10)? No
call printInWords(number / 10)
   // Entering printInWords(number == 12)
   is (number < 10)? No
   call printInWords(number / 10)
      // Entering printInWords(number == 1)
      is (number < 10)? Yes
      call digitToWord(1) 
         returns "one"
      print "one" + " "
      return
      // Exiting printInWords(number == 1)
   // Returned: number == 12!?!
   call digitToWord(number % 10)
      returns "two"
   print "two" + " "
   return
   // Exiting printInWords(number == 12)
// Returned: number == 123
call digitToWord(number % 10)
   returns "three"
print "three" + " "
return
// Exiting printInWords(number == 123)
Remember to always have the terminating condition. In class, I'll (try to remember to) remove the stopping condition to demonstrate what happens.

All recursive methods can be written as iterative methods. Sometimes this is easy, other times it involves a little thought.

Recursive methods are usually less efficient than iterative methods. Why? There is considerable overhead in making a method call. The state of the program has to be saved and values passed to the method before the method can actually be executed. So, in general method calls are slower. But, they do often make the program easier to read, write, and debug.

Recursion and returning values

Like all other methods, recursive methods may also return a value. And this value can be used later. For example, the following program shows two ways (iteratively and recursively) to compute "a" raised to the "b-th" power - assuming b >= 0.
import java.io.*;

public class SamplePow {
  public static void main(String[] args) {
    int pow = 0;
    System.out.println("Iteratively, 2**10 = ");
    pow = iterativePow(2,10);
    System.out.println(pow);
    System.out.println("Recursively, 2**10 = ");
    pow = recursivePow(2,10);
    System.out.println(pow);
  }

  // Return a**b (iteratively)
  public static int iterativePow(int a, int b) {
    int i;
    int total = 1;
    for (i = 0; i < b; i++) {
      total *= a;
    }
    return total;
  }

  // Return a**b (recursively)
  public static int recursivePow(int a, int b) {
    // This is the terminating condition
    if (b <= 0) {
      return 1;
    }

    // Compute a**(b-1), multiply by a,
    // and return value
    return recursivePow(a,b-1) * a;
  }
}
As you begin to program more and more, you will learn that recursion can be both a nightmare and a blessing.

Binary Searching (Classic Recursive Example)

Let us illustrate one more recursive topic. Have you ever tried to look up a name in the phone book? If you are looking for "Christian Duncan", do you start at the beginning and check every name until you find "Christian Duncan"? No. Why not? Because that would take way too long.

You instead employ a quicker strategy. Programming is the same way. A computer is fast but if the programs aren't written efficiently enough, even a human could be faster. In CSC 220 and in CSC 517, you will learn about making programs efficient. The design of algorithms is critical to making efficient code.

Let us generalize the phone book problem. Imagine you are given a sorted list of numbers. A query is to identify if (and where) a given number exists in the list.

The naive approach which works but does not work effeciently would be to start at the beginning and check every number until the end. If it found the number, it would return the location, otherwise, it would say there was no result found.

A quicker approach would be to recursively do the following procedure. Search for the number between ranges a and b (initially the first and last element). Look at the middle value in the range mid=(a + b)/2 and see if that value equals the given search number. If not, it is either too large or too small. If it is too large, repeat the search with new values a and mid. If it is too small, repeat the search with new values mid+1 and b. Notice that after each step we cut the search in half. How many levels do we need to search to get to the solution? About log n where n is the initial size of the array i.e. initial value of b. Given time I will try to explain this operation further, but it depends on how much time is left in class.

The class BinarySearch is sample code for the improved version.

The class RecursiveExamples gives a few more examples of recursion. Test them out to see how they work and to get used to understanding recursion.