View page as slide show

Click presentation screen icon to the right to see the slide show.

3. Recursion: The Mirrors

Chapter 3, then 6, summarizes and demonstrates key concepts of recursion.

3.1 Introduction

What is the smallest java program you can think of that does recursion?

3.1 Introduction

What is the smallest java program you can think of that does recursion?

Forever.java
public class Forever {
   public static void main(String [] args) {
      main(null);
   }
}

What happens when we run this?

3.1 Introduction

What is the smallest java program you can think of that does recursion?

Forever.java
public class Forever {
   public static void main(String [] args) {
      main(null);
   }
}

What happens when we run this?

> javac Forever.java
> java Forever
Exception in thread "main" java.lang.StackOverflowError
	at Forever.main(Forever.java:3)
	at Forever.main(Forever.java:3)
	at Forever.main(Forever.java:3)
           .
           .
           .

3.1 Introduction

> javac Forever.java
> java Forever
Exception in thread "main" java.lang.StackOverflowError
	at Forever.main(Forever.java:3)
	at Forever.main(Forever.java:3)
	at Forever.main(Forever.java:3)
           .
           .
           .

How many mirrors did we look into?

> java Forever 2>&1 | grep "Forever.main" | wc

   1024    2048   33792

wc returns the number of lines, words, and characters it gets in stdin.

3.1 Recursive Strategies

The divide and conquer strategy.

  1. Divide problem into parts.
  2. Figure out how to solve the parts.

The beauty is that often once you have figured out a way to do Step 1, you are done!

If each part can be similarly divided, just continue dividing until the “Figure out how to solve the parts” is trivial.

It's all about the “divide”!

3.1 Introduction

Example: You have a list of integers. Is 42 in the list?

Sure…write a for loop. But how would you solve this by divide and conquer?

3.1 Introduction

Example: You have a list of integers. Is 42 in the list?

Sure…write a for loop. But how would you solve this by divide and conquer?

One way is to divide list into two parts, the first half and the second half.

Now what?

3.1 Introduction

Example: You have a list of integers. Is 42 in the list?

Sure…write a for loop. But how would you solve this by divide and conquer?

One way is to divide list into two parts, the first half and the second half.

Now what?

Do the same on each half. Again, and again, until…?

3.1 Introduction

Example: You have a list of integers. Is 42 in the list?

Sure…write a for loop. But how would you solve this by divide and conquer?

One way is to divide list into two parts, the first half and the second half.

Now what?

Do the same on each half. Again, and again, until…?

A resulting half is only one element. This is the base case. Then just compare to 42.

3.1 Introduction

A bit more formal. Algorithm to evaluate search(42, list).

boolean search(item, list):
    
    n = size of list

    firstHalf = list[0 ... n/2]
    secondHalf = list[n/2+1 ... n-1]

    return search(item, firstHalf) or search(item, secondHalf)    

Problems?

3.1 Introduction

A bit more formal. Algorithm to evaluate search(42, list).

boolean search(item, list):
    
    n = size of list

    firstHalf = list[0 ... n/2]
    secondHalf = list[n/2+1 ... n-1]

    return search(item, firstHalf) or search(item, secondHalf)    

    if n = 0, then return False
    if n = 1, then return item == list

Problems?

3.1 Introduction

A bit more formal. Algorithm to evaluate search(42, list).

boolean search(item, list):
    
    n = size of list

    if n = 0, then return False
    if n = 1, then return item == list

    firstHalf = list[0 ... n/2]
    secondHalf = list[n/2+1 ... n-1]

    return search(item, firstHalf) or search(item, secondHalf)    

Problems? Think about how many mirrors we looked into earlier?

3.1 Introduction

A bit more formal. Algorithm to evaluate search(42, list).

boolean search(item, list):
    
    n = size of list

    if n = 0, then return False
    if n = 1, then return item == list

    firstHalf = list[0 ... n/2]
    secondHalf = list[n/2+1 ... n-1]

    return search(item, firstHalf) or search(item, secondHalf)    

Problems? Think about how many mirrors we looked into earlier?

And why would we ever want to search for 42 this way?

3.1 Introduction

Key Concepts (page 140)

  1. How can you define the problem in terms of one or more smaller problems of the same type?
  2. How does each recursive call diminish the size of the problem?
  3. What instance or instances of the problem can serve as the base cases?
  4. As the problem size diminishes, will you reach a base case?
  5. Will you exhaust the call stack?

3.1 Introduction

Let's do another problem. Calculate

Well,

Since we see that

Is this correct, for all ?

3.1 Factorial

Right, only for the values of that we specified, , and really only for . What about for ?

3.1 Factorial

Right, only for the values of that we specified, , and really only for . What about for ?

Must define it, so or

3.1 Factorial

Let's code it!

Factorial.java
public class Factorial {
 
    public static int factorial(int n) {
 
        if (n == 0) {
            return 1;                    // Base case.
        } else {
            return n * factorial(n-1);   // Recursive step.
        }           
    }
 
    public static void main(String [] args) {
 
        int n = Integer.parseInt(args[0]);
 
        System.out.println("factorial(" + n + ") is " + factorial(n));
    }
}

3.1 Factorial

Cool. Test it.

> java Factorial 4

factorial(4) is 24

>java Factorial 5

factorial(5) is 120

Does it always work?

3.1 Factorial

Cool. Test it.

> java Factorial -4
Exception in thread "main" java.lang.StackOverflowError
	at Factorial.factorial(Factorial.java:5)
	at Factorial.factorial(Factorial.java:8)
	at Factorial.factorial(Factorial.java:8)
          .
          .
          .

What should we do?

3.1 Factorial

Cool. Test it.

> java Factorial -4
Exception in thread "main" java.lang.StackOverflowError
	at Factorial.factorial(Factorial.java:5)
	at Factorial.factorial(Factorial.java:8)
	at Factorial.factorial(Factorial.java:8)
          .
          .
          .

What should we do?

At the very least, add preconditions and postconditions in the comments. The most important precondition is that is greater than or equal to 0.

If anyone uses our code with an that violates this precondition, we are not responsible!

3.1 Factorial

Understand the “box trace” that the authors provide.

Really shows the “activation record” for each method call.

3.1 Reversing a string

Remember, divide and conquer.

Print the string “abcd” backwards to get “dcba”.

What are the divide strategies?

  1. divide string into two halves
  2. divide string into first character and the rest
  3. divide string into last character and the rest

Which one?

3.1 Reversing a string

Remember, divide and conquer.

Print the string “abcd” backwards to get “dcba”.

What are the divide strategies?

  1. divide string into two halves
  2. divide string into first character and the rest
  3. divide string into last character and the rest

Which one?

Let's do the first one. Our authors show examples of the last two strategies.

3.1 Reversing a string

Reverse.java
public class Reverse {
 
   public static void reverse(String string) {
 
      int n = string.length();
 
      // Base Cases.  Conquer!
 
      if (n == 0)
         return;
 
      if (n == 1) {
         System.out.print(string.charAt(0));
      }
 
      // Recursion.  Divide!
 
      String firstHalf = string.substring(0,n / 2);
      String secondHalf = string.substring(n/2);
 
      reverse(secondHalf);  // Take care of the order of these two lines.
      reverse(firstHalf);
   }
 
   public static void main(String [] args) {
      reverse(args[0]);
      System.out.println();
   }
}

Try it. Uh oh!

3.2 Counting Things

Rabbits:

  • Rabbits never die
  • Rabbits are always born in male-female pairs.
  • Once a pair is two months old, they produce another pair every month.

Number of rabbit pairs each month = 1, 1, 2, 3, 5, 8, …

Look familiar?

3.2 Counting Things

Rabbits:

  • Rabbits never die
  • Rabbits are always born in male-female pairs.
  • Once a pair is two months old, they produce another pair every month.

Number of rabbit pairs each month = 1, 1, 2, 3, 5, 8, …

Look familiar?

Yep, good old Fibonacci. What else is he known for? (Read page 348 in Rosen)

3.2 Counting Things

Rabbits:

  • Rabbits never die
  • Rabbits are always born in male-female pairs.
  • Once a pair is two months old, they produce another pair every month.

Let's code…in math first. rabbits = 1, 1, 2, 3, 5, 8, …

3.2 Counting Things

Rabbits:

  • Rabbits never die
  • Rabbits are always born in male-female pairs.
  • Once a pair is two months old, they produce another pair every month.

Let's code…in math first. rabbits = 1, 1, 2, 3, 5, 8, …

3.2 Counting Things

Now for some java.

Rabbits.java
public class Rabbits {
 
   public static int rabbits(int n) {
 
      if (n == 1 || n == 2)
         return 1;
 
      return rabbits(n-1) + rabbits(n-2);
   }
 
   public static void main(String [] args) {
 
      int n = Integer.parseInt(args[0]);
      System.out.println("rabbits(" + n + ") is " + rabbits(n));
   }
}

3.2 Counting Things

This recursive solution to counting pairs of rabbits is very inefficient. Why?

3.2 Counting Things

This recursive solution to counting pairs of rabbits is very inefficient. Why?

The number of rabbits for a given is often recalculated. How can we keep track of these counts?

Let's use a static int array. Do it in class.

3.2 Counting Things

Parades:

  • Each position in a parade is either a float or a band.
  • There are positions, or sum of the number of floats and bands.
  • A band cannot be followed immediately by another band.

How to count the number of valid parades?

For , valid parades are “f f f”, “f f b”, “f b f”, “b f f”, “b f b”

Divide up the total count into

  • number of parades that end with a float ”? ? ? … ? f”
  • number of parades that end with a band ”? ? ? … f b”
    because ”? ? ? … b b” is not allowed

3.2 Counting Things

Divide up the total count into

  • number of parades with positions that end with a float
    ”? ? ? … ? f”
  • number of parades with positions that end with a band
    ”? ? ? … f b” because ”? ? ? … b b” is not allowed

Call the first count and the second one .

If we call the total number of valid parades , then .

Notice that , because the final band must be preceded by a float.

Also notice that .

So,

3.2 Counting Things

What are the base cases?

  • , either “f” or “b”, so
  • , either “f f”, “f b”, or “b f”, so

Specified for all :

3.2 Counting Things

Code it up.

public static int parades(int n) {
 
   if (n == 1) return 2;
   if (n == 2) return 3;
 
   return parades(n-1) + parades(n-2);
}

3.2 Counting Spock's Things

How many ways are there to pick things out of things? Call this number .

Spocks reasoning: Either

  • my first choice is a particular object A, or
  • my first choice is some other object.

Put another way:

  • If I do pick A, then I only have left to pick.
  • If I do not pick A, then I still have to pick.

In each case, there remains objects to pick from.

Put yet another way:

What about base cases?

3.2 Counting Spocks Things

Put yet another way:

What about base cases?

must be less than or equal to , so the first term eventually hits . At this point , because there is nothing to choose.

For the second term, is decreasing and eventually must equal . When , also. There is only one way to pick 3 of 3 things, for example.

3.2 Counting Spock's Things

So

You should now be able to write this in java and test it.

Draw the tree of recursive calls.

3.3 Searching an Array

Already saw example of searching an array, for 42.

The dividing was done by recursively calling the search on two halves of the array.

The conquering was done by comparing a single element with 42 and returning True or False.

The results of each recursive call are combined with or.

How would you search for the maximum value in a list?

3.3 Searching an Array

How would you search for the maximum value in a list?

Same way, but combine results using a max operator.

Max.java
import java.lang.Math;
 
public class Max {
 
   public static int findMaximum(int [] numbers, int first, int last) {
 
      // Base Case
      if (first >= last)
         return numbers[first];
 
      // Recursion. Divide in half.
      int mid = (first+last)/2;
 
      return Math.max( findMaximum(numbers,first,mid), 
                       findMaximum(numbers,mid+1,last) );
   }
 
   public static void main(String [] args) {
      int [] numbers = new int[args.length];
      for (int i = 0; i < args.length; i++)
         numbers[i] = Integer.parseInt(args[i]);
 
      System.out.println(findMaximum(numbers,0,args.length-1));
   }
}

3.3 Searching an Array

Searching for a particular number is much easier if the list of numbers is sorted! Why?

Searching for a particular number is much easier if the list of numbers is sorted! Why?

Only must search one of the halves.

3.3 Binary Search

BinarySearch.java
public class BinarySearch {
 
   // Returns -1 if value not found
   public static int binarySearch(int [] numbers, int first, int last, int value) {
 
      // Base Case
      if (first > last)
         return -1;
 
      // Recursion. Divide in half.
      int index;
 
      int mid = (first+last)/2;
      if (value == numbers[mid]) {
         index = mid;
 
      } else if (value < numbers[mid]) {
         // value can only be in the first half.
         index = binarySearch(numbers,first,mid-1,value);
      } else {
         // value can only be in the second half.
         index = binarySearch(numbers,mid+1,last,value);
      }         
 
      return index;
   }
 
   public static void main(String [] args) {
      int value = Integer.parseInt(args[0]);
      int [] numbers = new int[args.length-1];
 
      for (int i = 1; i < args.length; i++)
         numbers[i-1] = Integer.parseInt(args[i]);
 
      System.out.println( binarySearch(numbers,0,numbers.length-1, value));
   }
}

3.4 Organizing Data

Towers of Hanoi Puzzle on Wikipedia

Three pegs, three disks.

  • Can only move one disk at a time.
  • Cannot place a larger disk on a smaller disk.

Problem is to find sequence of moves as answer to

solve( n disks, source peg, goal peg, spare peg)

or for our particular version

solve( 3, A, B, C)

How?

3.4 Organizing Data

solve( 3, A, B, C)

How?

First, divide it into two smaller problems, moving the top 2 disks and moving the bottom biggest disk:

  • Move top 2 disks to peg C (not the ultimate goal peg)
solve( 2, A, C, B )
  • Move remaining large disk to goal peg B
solve( 1, A, B, C )
  • Move other 2 disks from peg C to goal peg B
solve( 2, C, B, A )

Now what? How are each of these three steps solved?

3.4 Organizing Data

The three steps from last slide.

solve( 3, A, B, C )

became

  solve( 2, A, C, B )
  solve( 1, A, B, C )
  solve( 2, C, B, A )

The middle one is easy. Moving one disk doesn't even use the spare peg; just move it.

The other two are divided into smaller problems as we did before.

3.4 Organizing Data

The three steps from last slide.

solve( 3, A, B, C )

became

solve( 2, A, C, B )
solve( 1, A, B, C )
solve( 2, C, B, A )

The middle one is easy. Moving one disk doesn't even use the spare peg; just move it.

The other two are divided into smaller problems as we did before.

solve( 2, A, C, B )

becomes

  solve( 1, A, B, C)
  solve( 1, A, C, B)
  solve( 1, B, C, A)

3.4 Organizing Data

Similarly divide the third main step into three.

3.5 Recursion and Efficient

“Recursion can clarify complex solutions.”

“Recursion is truly valuable when a problem has no simple iterative solution.”

A more efficient solution may result from converting a recursive solution into an iterative one.

  • Recursive methods that call themselves once are easier to convert.
  • A tail-recursive method is the easiest to convert.
Recent changes RSS feed CC Attribution-Share Alike 3.0 Unported Driven by DokuWiki