Recursive Problem Solving
- Infinite Mirrors
- recursive-valued methods: factorial
- recursive void methods: printing backwards
- counting rabbits: fibonacci
- counting k out of n
Infinite Mirrors
- standing between two mirrors produces an infinite number of images,
all similar, each a little smaller
- suppose we would know how to solve a problem P (size n) if
we only knew how to solve a smaller version of the same problem (Pn-1)
- suppose also that for small enough n, e.g. n <= 0, we know
how to solve the problem
- write a method M(n) that calls M(n-1) to solve the smaller problem.
M(n-1) will call M(n-2) and so on.
The solution for M(0) should be computable without further calls to M.
Factorial
- the factorial of n is the product of
1 * 2 * 3 * ... * n - 1 * n, i.e.
n! = \prodi=1n i
- (n-1)! = \prodi=1n-i i
- so n! = n * (n-1)!
- also, 0! = 1
public static int factorial (int n) {
if (n == 0) {
return 1;
}
return n * factorial (n - 1);
}
Recursive-Valued Methods
- all the calls to factorial are active
at the same time, but each has a different
value for n
- calls to different methods have different parameters
- likewise, calls to the same methods have different parameters
- to obtain infinite recursion, simply give them the same parameter
- logically, these parameters are saved by the system in a data
structure called a stack
- with "infinite" recursion, we eventually have a stack overflow
Recursion Invariants
- should always guarantee preconditions of recursive call
- for example, what happens with the factorial method
if we give it a negative n?
- if we guarantee the preconditions of the recursive call,
we can use its postconditions to guarantee our postcondition
- a recursion always needs a stopping case: the {\em base
case}
- to avoid infinite recursion, we must prove we will reach
the base case in a finite number of recursive calls
Recursive Void Methods
- factorial returns a value
- we can use recursion even with methods that have side effects
- for example, to print a string backwards, print the last
character, then print the first n-1 characters backwards (base case:
the string is empty)
- for example, to print an integer, print the integer divided
by ten, then print the last digit (base case: the number is less than ten)
Printing a String Backwards
public static void
printBackwards (String s, int size) {
// pre: s.length >= size, size >= 0
// post: s has been printed backwards
if (size > 0) {
System.out.println
(s.subtring(size-1, size));
printBackwards (s, size - 1);
}
}
- the base case is to do nothing
- the method "ignores" any characters past size,
so initial caller can print any substring from the beginning
(which is a bug and/or a feature)
Printing an Integer
public static void printInt (int n) {
// pre: n >= 0
// post: n has been printed
if (n >= 10) {
printInt (n / 10);
}
printDigit (n % 10);
}
- hard problem to solve with iteration (requires a stack
or two loops)
- assumes a way to print digits (which you can do with
a switch statement)
- println may work this way
- debugging: does this work for 0? for 1? for 9? for 10?
Fibonacci
- rabbits (cats in M\=anoa :-) reproduce: the more rabbits
you have, the more you get
- how many rabbits do you have after n generations? assume:
- rabbits never die
- rabbits reproduce every month from their 3rd month of life
- each month (after the second), each rabbit produces one breeding pair
Fibonacci numbers
- starting with one pair of rabbits, we have:
month | rabbits (pairs) |
1 | 1 pair |
2 | 1 pair |
3 | 2 pairs |
4 | 3 pairs |
5 | 5 pairs |
6 | 8 pairs |
|
- the number of rabbits alive during month n is
- the number alive during month n-1 plus
- the number i born in month n,
which is the same as the number of rabbits alive during month n-2
- f(n) = f(n-1) + f(n-2)
Fibonacci: Implementations
// recursive implementation
// note there are two base cases
// pre: n > 0, post: returns fib(n)
public static int fib (int n) {
if ((n == 1) || (n == 2)) {
return 1;
}
return fib (n - 1) + fib (n - 2)
}
// iterative implementation
public static int fib (int n) {
if (n <= 2) return 1;
int n1 = 1; int n2 = 1; int i = 2;
while (i++ <= n) {
int sum = n1 + n2;
n1 = n2; n2 = sum;
}
return n2;
Recursion and Iteration
- recursion is only one problem-solving technique
- it is, however, a very powerful technique
- if we can reduce a problem to a smaller version of the problem,
- such that we eventually hit a small enough problem that we
know how to solve (a base case)
- then we can use recursion
Choosing k out of n
- choose k out of n possibilities
- if k == n it's easy, if k > n impossible, so the hard
case is when k < n
- consider choice 1: either we take it or we don't
- if we do, then we're left with picking k-1 out of n-1 items
- otherwise, we must still pick k out of n-1
- how many possible ways are there of doing this? Let's write a
method to compute this
In-Class exercise
- work alone
- sketch the implementation of a recursivemethod to count the
number of ways of choosing k out of n items
- what are the base cases?
- what recursive calls do you need?
- how is each recursive problem smaller?
- what is your method header?