Recursion

PGSS Computer Science Core Slides

Fabulously complex structure through simple procedures

Towers of Hanoi

    a       b       c

    |       |       |
   ###      |       |
  @@@@@     |       |
 $$$$$$$    |       |
----+-------+-------+----

We want to move the tower from a, moving one disk at a time, never moving a disk on top of a smaller one.

A simple procedure does this, if it calls itself. This self-use is recursion.

   Algorithm Hanoi(disk, source, dest, other)
   if disk is smallest then
     move disk from source to dest
   else
     Hanoi(next disk, source, other, dest)
     move disk from source to dest
     Hanoi(next disk, other, dest, source)
   fi

A trace

IN Hanoi($$$$$$$, a, b, c)
  IN Hanoi(@@@@@, a, c, b)
    IN Hanoi(###, a, b, c)
            |       |       |
            |       |       |
          @@@@@     |       |
         $$$$$$$   ###      |
        ----+-------+-------+----
    OUT
            |       |       |
            |       |       |
            |       |       |
         $$$$$$$   ###    @@@@@
        ----+-------+-------+----
    IN Hanoi(###, b, c, a)
            |       |       |
            |       |       |
            |       |      ###
         $$$$$$$    |     @@@@@
        ----+-------+-------+----
    OUT
  OUT
            |       |       |
            |       |       |
            |       |      ###
            |    $$$$$$$  @@@@@
        ----+-------+-------+----
  IN Hanoi(@@@@@, c, b, a)
    IN Hanoi(###, c, a, b)
            |       |       |
            |       |       |
            |       |       |
           ###   $$$$$$$  @@@@@
        ----+-------+-------+----
    OUT
            |       |       |
            |       |       |
            |     @@@@@     |
           ###   $$$$$$$    |
        ----+-------+-------+----
    IN Hanoi(###, a, b, c)
            |       |       |
            |      ###      |
            |     @@@@@     |
            |    $$$$$$$    |
        ----+-------+-------+----
    OUT
  OUT
OUT

A Useful Example

Alice: I don't want the world to end! Is recursion still useful?
Algorithm Fast-Exponentiate(x, n)
if n = 0 then
  return 1
else if n is even then
  return Fast-Exponentiate(x^2, n / 2)
else
  return x * Fast-Exponentiate(x^2, (n - 1) / 2)
fi
IN Fast-Exponentiate(2, 10)
10 is even:
  IN Fast-Exponentiate(4, 5)
  5 is odd:
    IN Fast-Exponentiate(16, 2)
    5 is odd:
      IN Fast-Exponentiate(256, 1)
      5 is odd:
        IN Fast-Exponentiate(32768, 0)
        return 1
      return 256
    return 256
  return 1024
return 1024

More About Fast-Exponentiate

Correctness: We prove this using induction on n. The base case, when n is 0, is easy: Fast-Exponentiate(x, 0) = 1 = x^0. Say it works for all n smaller than k. Does it work for k? If k is even, then k/2 is smaller than k, so

  Fast-Exponentiate(x, k) = Fast-Exponentiate(x^2, k / 2)
                          = (x^2)^(k / 2)
                          = x^(2(k/2))
                          = x^k
If k is odd, then (k - 1)/2 is smaller than k, so
  Fast-Exponentiate(x, k) = x * Fast-Exponentiate(x^2, (k - 1) / 2)
                          = x * (x^2)^((k - 1) / 2)
                          = x * x^(2((k - 1)/2))
                          = x * x^(k - 1)
                          = x^k

Time bound: We use a recurrence.

  T(1) = 1
  T(n) < T(n / 2) + 1
Using the Master Theorem (see textbook appendix), we see that
  T(n) = O(log_2 n)

Recursion and Induction

Recursion and mathematical induction have a lot in common.

Like inductive proofs, recursive algorithms must have a base case - some situation where th efunction does not call itself.

Otherwise the program will never get to an answer!

Alice: But this is a good thing with the Tower of Hanoi!

Recursion in Java

public static int Factorial(int n) {
  if(n == 0) {
    return 1;
  } else {
    return n * Factorial(n - 1);
  }
}

More Java recursion

The Fibonacci sequence is the following:

 1 1 2 3 5 8 13 21 34 55 ...
Each number is the sum of the preceding two numbers in the sequence. We calculate the nth Fibonacci number.

public static int Fibonacci(int n) {
  if(n < 2) {
    return 1;
  } else {
    return Fibonacci(n - 1) + Fibonacci(n - 2);
  }
}

We can graph what's happening using a call tree.

                n = 4
            ___/     \___
           /             \
        n = 3           n = 2
       /     \         /     \
    n = 2   n = 1   n = 1   n = 1
   /     \
n = 1   n = 1

Notice that this takes Fibonacci(n) additions! Since Fibonacci(n) = O(1.618^n), this is a very poor running time. In fact one can calculate it in O(n) time.

Another Example

Problem class FIND_IN_SORTED_ARRAY
Input: an array arr sorted in increasing order, an integer n
Output: index of n in arr, -1 if absent
Algorithm Binary-Search(arr, n, low, high)
if high < low then
  if arr[low] = n then
    return low
  else
    return -1
  fi
else
  if arr[(low + high) / 2] < n then
    return Binary-Search(arr, n, low, (low + high) / 2)
  else if arr[(low + high) / 2] > n then
    return Binary-Search(arr, n, (low + high) / 2 + 1, high)
  else
    return (low + high) / 2
  fi
fi

This takes O(log_2 n) time. This is the solution to the recurrence relation

  T(n) < T(n / 2) + O(1)

A Trace

      +----+----+----+----+----+----+----+----+
arr = |  2 |  3 |  5 |  7 | 11 | 13 | 17 | 19 |
      +----+----+----+----+----+----+----+----+

  n = 13

IN Binary-Search(arr, 13, 0, 8)
11 < 13:
  IN Binary-Search(arr, 13, 5, 8)
  17 > 13:
    IN Binary-Search(arr, 13, 5, 6)
    13 = 13:
    RETURN 5
  RETURN 5
RETURN 5

About Variables

Each call to a recursive function gets its own copy of variables.

Changing a variable does not change it for higher recursive calls.

public static int SumSquares(int n) {
  int n_squared;
  int others;

  if(n < 1) {
    return 0;
  } else {
    n_squared = n * n;
    others = SumSquares(n - 1);
    return n_squared + others;
  }
}

The call tree looks like this.

        returns 5
    n = 2
n_squared = 4
      |
      | returns 1
    n = 1
n_squared = 1
      |
      | returns 0
    n = 0
When the call with n=1 changes n_squared, it just changes its copy of n_squared. The n_squared for the call with n=2 remains unchanged.