Optional Reading: Chapter 17
One important algorithmic technique is dynamic programming.
Looking at problems
upside-down can help!
(But be careful with your
hat!)
Two steps to formulating a dynamic programming algorithm:
1 1 2 3 5 8 13 21 34 44 89 ...
Fact: Fibonacci(n) is approximately 1.618n+1 / sqrt(5) (where 1.618 = (1 + sqrt(5) / 2)).
The golden ratio!
Algorithm Fibonacci(n) if n <= 1, then: return 1 else: return Fibonacci(n - 1) + Fibonacci(n - 2) end of if
A call tree:
5
/ \
4 3
/ \ / \
3 2 2 1
/ \ / \ / \
2 1 1 0 1 0
/ \
1 0
This takes O(1.618n) time!
to compute takes Fibonacci(40) 75.22 seconds Fibonacci(70) 4.43 years
Dynamic programming suggests we start at the bottom and work up.
Algorithm Fast-Fibonacci(n) Let fib[0] and fib[1] be 1. for each i from 2 to n, do: Let fib[i] be fib[i - 2] + fib[i - 1]. end of loop return fib[n].
The number of additions now is only n-1!
to compute took now takes Fibonacci(40) 75.22 sec 2 microseconds Fibonacci(70) 4.43 years 3 microseconds
Problem MakeChange:
Input: coin denominations d[0], d[1],..., d[n-1],
an amount a
Output: the number of coins needed to total a
exactly.
Consider Freedonia: we have pennies, 4-cent pieces, and nickels; and we have a=8. The output should be 2 (since we could use two four-cent pieces), even though the greedy method would output 4 (a nickel and three pennies).
MakeChange(a) = 1 + min MakeChange(a - d[i])
0 <= i < n
Algorithm DynamicMakeChange(amt, d[0], d[1],..., d[n-1]):
Let coins[0] be 0.
for each a from 1 to amt, do:
coins[a] <- infinity // an upper bound
for each j from 0 to n - 1, do:
if d[j] <= a and 1 + coins[a - d[j]] < coins[a], then:
Let coins[a] be 1 + coins[a - d[j]].
end of if
end of loop
end of loop
return coins[amt].
With our earlier example, coins would look like
+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 2 | 3 | 1 | 1 | 2 | 3 | 2 | +---+---+---+---+---+---+---+---+---+
This algorithm takes O(amt * n) time.
Problem AllPairsPaths:
Input: graph (V, E) with edge distances d : E ->
positive reals.
Output: length p[s,t] of shortest path from s to
t, for all pairs of vertices.
Example problem:
1
B --- E
| \ |
3| 6\ | 1
| \ |
A --- S
2
Output:
A B S E A 0 3 2 3 B 3 0 2 1 S 2 2 0 1 E 3 1 1 0
Define p[s,t]^(k) as the shortest path between s and t, passing only through vertices 1,2,...,k in between. (The superscripts do not indicate exponentiation; they are just another index.)
We can calculate p[s, t]^(k) in terms of the p^(k - 1):
p[s, t]^(k) = min { p[s, t]^(k - 1), p[s, k]^(k - 1) + p[k, t]^(k - 1) }
since any shortest path never goes through vertex k more than once.
Let n be the number of vertices.
Algorithm DynamicPaths(d):
for each s from 1 to n, do:
for each t from 1 to n, do:
Let p[s, t]^(0) be d(s, t).
end of loop
end of loop
for each k from 1 to n, do:
for each s from 1 to n, do:
for each t from 1 to n, do:
Let p[s, t]^(k) be min { p[s, t]^(k - 1), p[s, k]^(k - 1) + p[k, t]^(k - 1) }.
end of loop
end of loop
end of loop
return p^(n).
Example (using # to represent infinity):
0 3 2 #
p^(0) 3 0 6 1
2 6 0 1
# 1 1 0
0 3 2 #
p^(1) 3 0 5 1
2 5 0 1
# 1 1 0
0 3 2 4
p^(2) 3 0 5 1
2 5 0 1
4 1 1 0
0 3 2 3
p^(3) 3 0 5 1
2 5 0 1
3 1 1 0
0 3 2 3
p^(4) 3 0 2 1
2 2 0 1
3 1 1 0
This algorithm takes O(n3) time.