Picking up from last time, we'll continue exploring recursion by looking at recursive solutions to several problems.
Last time we saw a recursive method for computing xn by multiplying x together n times. Is there another way that we might do this?
Suppose we want to calculate 216. Is there a faster way to do this than doing 16 multiplications? The answer is yes - we could compute 28 and square the result. This uses the mathematical identity that if n is even, then xn = (xn/2)2. If we can take advantage of something like this, then we might be able to come up with a clever way of computing xn that is faster than n repeated multiplications.
For what we're trying to do, it's more useful to use a similar identity: xn = (x2)n/2 if n is even. To use this as the basis for a recursive function, we need to worry about what will be the basis case (n == 0), and what do we do if n is odd. In the later case, we can just do a single multiplication of x to reduce the problem to one where the exponent is even. So we'll get code that looks something like this:
public int fastpow(int x, int n) { if (n == 0) { return 1; } else if (n % 2 == 0) { return fastpow(x*x, n/2); } else { return x * fastpow(x, n-1); } }
Why bother? The answer is that this version is considerably faster than the original function. The original function took time linear in the value of the exponent: it used n multiplications to compute xn. Whenever n is even, we perform one multiplication (x*x), then recursively solve the problem with an exponent that is half as large (n/2). If the resulting exponent is also even, we repeat the squaring and halving operation. If the exponent is odd, we reduce it by 1, then we've got an even exponent again, which allows us to square and halve again. So instead of performing n multiplications, we perform at most log n multiply & square/halve operations to get the result, which means we calculate xn in time proportional to log n.
Another problem with numbers: Suppose we have an int value v that is, say, 23465217, and we want to print it on the screen. Well, we normally would just write System.out.println(v) and not think much more about it. But remember that ultimately we need to print characters on the screen, not int bits - so we need to print '2', '3', '4', '6', '5', '1', '7'.
How can we isolate a single digit from an int? Is it easier to "get at" the left or right-most digit?
Now how can we turn this into a Java method? What is the recursive case? What is the base case?
What if we wanted to print the digits backwards? What would change?
Another problem: suppose we have an array of strings containing some words:
String[] words = {"how", "now", "brown", "cow"};
It's fairly simple to write a loop to print this forward or backwards, one word per line, but it's an interesting exercise to think about doing this recursively, where each recursive function call can print at most a single word with a call to System.out.println(). Suppose we want to do this from the beginning to the end of the array. We need to figure out how to make progress by printing one more thing, then how to solve the rest of the problem. Throw in a base case and we're done.
print one thing:
print the rest:
base case:
What happens if we want to print the array backwards? What needs to change?
Two more problems that are fairly closely related. Suppose we want to know the
number of ways we can choose a given number of things from a larger set. A
good example is a card deck: suppose we want to know how many ways we can choose
three cards from a deck of 52. Well in this case, the order matters. There
are 52 possible choices for the first card, then 51 choices for the next card,
then 50 choices for the third. This is a specific example of the function permut(n,m),
which yields the number of sequences of length m that can be chosen from a
set of n objects. What is the simples case? One possibility is if n = 1, but
even simpler is if n = 0: there's only 1 way to pick nothing from a set of
things. Now if we start with n things, there are n possible ways to pick the
first one. If we are asked to pick a total of m things, then after picking
the first one, we are left with n-1 things to pick from m-1 remaining things.
So we get the following recursive definition:
permut(n,m) = n * permut(n-1, m-1)
A closely related calculation is to figure out the number of combinations of m things that can be picked from a set of n. Here the order doesn't matter (think of the cards in a hand during a card game - we don't care which one was dealt first). So, in the case of a deck of cards, we're trying to figure out how many possible hands of m cards there are given a deck of n. A way to look at this is to think about an individual card, say the ace of spades. Some of the possible hands we can pick have an ace of spades in them. In that case, we can choose m-1 more cards from the remaining n-1 cards to complete the hand. But there are other possible hands that don't include the ace of spades. In particular, these hands have m cards chosen from the set of n-1 cards that doesn't include the ace. So we can calculate the number of combinations of m things chosen from n as follows
comb(n,m) = comb(n-1, m-1) + comb(n-1, m)
With the addition of appropriate base cases and error conditions we can write
a straightforward recursive solution.