Click presentation screen icon to the right to see the slide show.
Chapter 3, then 6, summarizes and demonstrates key concepts of recursion.
What is the smallest java program you can think of that does recursion?
What is the smallest java program you can think of that does recursion?
public class Forever { public static void main(String [] args) { main(null); } }
What happens when we run this?
What is the smallest java program you can think of that does recursion?
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)
.
.
.
> 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.
The divide and conquer strategy.
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”!
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?
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?
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…?
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.
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?
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?
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?
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?
Key Concepts (page 140)
Let's do another problem. Calculate
Well,
Since we see that
Is this correct, for all ?
Right, only for the values of that we specified, , and really only for . What about for ?
Right, only for the values of that we specified, , and really only for . What about for ?
Must define it, so or
Let's code it!
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)); } }
Cool. Test it.
> java Factorial 4 factorial(4) is 24 >java Factorial 5 factorial(5) is 120
Does it always work?
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?
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!
Understand the “box trace” that the authors provide.
Really shows the “activation record” for each method call.
Remember, divide and conquer.
Print the string “abcd” backwards to get “dcba”.
What are the divide strategies?
Which one?
Remember, divide and conquer.
Print the string “abcd” backwards to get “dcba”.
What are the divide strategies?
Which one?
Let's do the first one. Our authors show examples of the last two strategies.
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!
Rabbits:
Number of rabbit pairs each month = 1, 1, 2, 3, 5, 8, …
Look familiar?
Rabbits:
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)
Rabbits:
Let's code…in math first. rabbits = 1, 1, 2, 3, 5, 8, …
Rabbits:
Let's code…in math first. rabbits = 1, 1, 2, 3, 5, 8, …
Now for some 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)); } }
This recursive solution to counting pairs of rabbits is very inefficient. Why?
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.
Parades:
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
Divide up the total count into
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,
What are the base cases?
Specified for all :
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); }
How many ways are there to pick things out of things? Call this number .
Spocks reasoning: Either
Put another way:
In each case, there remains objects to pick from.
Put yet another way:
What about base cases?
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.
So
You should now be able to write this in java and test it.
Draw the tree of recursive calls.
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?
How would you search for the maximum value in a list?
Same way, but combine results using a max operator.
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)); } }
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.
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)); } }
Towers of Hanoi Puzzle on Wikipedia
Three pegs, three disks.
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?
solve( 3, A, B, C)
How?
First, divide it into two smaller problems, moving the top 2 disks and moving the bottom biggest disk:
solve( 2, A, C, B )
solve( 1, A, B, C )
solve( 2, C, B, A )
Now what? How are each of these three steps solved?
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.
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)
“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.