In the last article, we looked in depth at the enormous cost of even fib(7) by drawing out the tree of calls for it and realizing that going even to fib(8) would be much more expensive. The traditional recursive way, we concluded, was beautiful and simple, but terribly inefficient due to its exponential runtime.
Of course, almost anything is better than an exponential
runtime, so we’ll take any improvement we can get. But here’s the good news: I claim (and will
soon prove) that I have a method that takes O(2^n) down to O(n). Those are enormous
savings: in the first case, if n is 10, you do 1024 operations, whereas
in the second case, with the same n, you only do 10—saving >=99% of the work
from the exponential version, getting the answer that much more quickly and
efficiently.
Here's how we can do it:
- Set up an array
- Use the array to remember values you’ve already calculated
- Look them up, instead of recalculating them
- Keep adding values as you calculate new ones
For comparison’s sake, let’s go calculate fib(7). For this, we need an array storing
8 integers, so we can index them from 0 to 7.
Initially, of course, the array looks like
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
But we can start by populating the values of the base cases, given in the recursive
formula, namely, that fib(0)—or, here, in this context, fib[0] with square
brackets because we’re accessing an index of an array, not calling a function—is
1, and so is fib[1].
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
Now, from fib[2] onward, let’s simply loop through the array
and do one of two things:
- Use an existing value
- Update a 0 into a real value, where necessary
fib[2] is not a base case, but we know the rule is to add
the previous two, so this is as simple as fib[2] = fib[0] + fib[1];
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
1 |
1 |
2 |
0 |
0 |
0 |
0 |
0 |
Now, we have fib[2] saved somewhere, and we’ll save
ourselves a lot of time by not throwing that result away.
We do the same for fib[3], which is fib[2] + fib[1]. fib[1]
was a base case, so we hard-coded that earlier, and we just calculated and
saved fib[2], so we can use it
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
1 |
1 |
2 |
3 |
0 |
0 |
0 |
0 |
Likewise, to get fib[4] by looking up (rather than recalculating) fib[2] and
fib[3]:
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
1 |
1 |
2 |
3 |
5 |
0 |
0 |
0 |
Likewise, to get fib[5] by looking up (rather than recalculating) fib[3] and
fib[4]:
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
1 |
1 |
2 |
3 |
5 |
8 |
0 |
0 |
Likewise, to get fib[6] by looking up (rather than recalculating)
fib[4] and fib[5]:
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
1 |
1 |
2 |
3 |
5 |
8 |
13 |
0 |
Likewise, to get fib[7]—our final answer— by looking up (rather
than recalculating) fib[5] and fib[6]:
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
Value |
1 |
1 |
2 |
3 |
5 |
8 |
13 |
21 |
This technique of storing a value and using it later is
called memoizing (for as much trouble as my word processor
gives me, there is no “r” as in “memoRizing”—it’s “memoizing” as in “writing a
memo”) and is key to a pattern of programming called dynamic programming, which
we’ll start covering explicitly soon.
This way, you still have the relationship that f(n) = f(n-1) + f(n-2), but, thanks
to the fact that you’re storing these values somewhere, you don’t need to waste
any time redoing past calculations.
Using a HashMap, we can write this much-improved solution like this:
import java.util.HashMap;
import java.util.Map;
public class FibonacciMemoization {
// Cache to store already computed Fibonacci values
private static Map<Integer, Long> memo = new HashMap<>();
public static long fibonacci(int n) {
// Base cases
if (n == 0) return 0;
if (n == 1) return 1;
// Check if already computed
if (memo.containsKey(n)) {
return memo.get(n);
}
// Compute, store, and return
long result = fibonacci(n - 1) + fibonacci(n - 2);
memo.put(n, result);
return result;
}
}
No comments:
Post a Comment