Our final algorithmic technique is dynamic programming.
Two steps:
1 1 2 3 5 8 13 21 34 55 89...(Each number is the sum of the previous two.)
Algorithm Fibonacci(n) if n <= 1 then return 1 else return Fibonacci(n - 1) + Fibonacci(n - 2) fi
__5__
/ \
4 3
/ \ / \
3 2 2 1
/ \ / \ / \
2 1 1 0 1 0
/ \
1 0
We can approximate the running time with the number of additions, which we can write as a recurrence.
adds(0) = 0
adds(1) = 0
adds(n) = adds(n - 1) + adds(n - 2) + 1
||
\/
adds(n) = Fibonacci(n) - 1
~= 0.7236 * 1.618^n - 1
A real timing:
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) fib[0] <- 1, fib[1] <- 1 for i <- 2 to n do fib[i] <- fib[i - 2] + fib[i - 1] od return fib[n]
The number of additions is only n - 1!
to compute took now takes Fibonacci(40) 75.22 seconds 2 microseconds Fibonacci(70) 4.43 years 3 microseconds
Problem class MAKE-CHANGE:
example:
1. Think of a recursive solution.
Make-Change(a) = 1 + min Make-Change(a - d[i])
0 <= i < n
___6___
/ \ \
5__ 3 2
/ \ \ / \ |
_4 2 1 2 0 1
/ |\ | | | |
3 1 0 1 0 1 0
/ \ | | |
2 0 0 0 0
/ \
2 0
|
1
|
0
2. Compute bottom up.
Algorithm Make-Change(amt)
coins[0] <- 0
for a <- 1 to amt do
coins[a] <- infinity
for i <- 0 to n - 1 do
if d[i] <= a and 1 + coins[a - d[i]] < coins[a] then
coins[a] <- 1 + coins[a - d[i]]
fi
od
od
returns coins[amt]
This takes amt * n iterations.
Homework 2 could be approach with dynamic programming.
1. Think of a recursive solution.
Algorithm Calc-Spreadsheet(box) if left side of box's formula is constant then left <- left hand constant else left <- Calc-Spreadsheet(left hand reference) fi if right side of box's formula is constant then right <- right hand constant else right <- Calc-Spreadsheet(right hand reference) fi if left and right are defined then return operation on left and right else return undefined fi
2. Compute bottom up.
Algorithm Calc-Spreadsheet
for each box i do result[i] <- undefined od
while changes are still being made do
for each box i do
if result[i] = undefined then
left <- current left side
right <- current right side
if left != undefined and right != undefined then
result[i] <- operation on left and right
fi
fi
od
od
Problem class ALL-PAIRS-PATHS:
example:
1
2---4
3 |\_ | 1
| 6\|
1---3
2
output:
to
1 2 3 4
+-------
1|0 3 2 3
from 2|3 0 2 1
3|2 2 0 1
4|3 1 1 0
We define the following quantity:
(k) length of shortest path between s and t only passing through p = vertices 1, 2,..., k in between. s, tWe can calculate p[s,t]^(k) by recursive calls to compute p[u,v]^(k-1):
(k) (k - 1) (k - 1) (k - 1)
p = min { p , p + p }
s, t s, t s, k k, t
This is because the shortest path only through 1...k either passes
through k or it doesn't. If the path doesn't, the first term will be
the minimum. If it does, then the path will go from s to k only through
1...k-1 and from k to t only through 1...k-1, and so the second term
will hold. The path will never go through k more than once, since then
we could remove the loop involving k.
(0) d(s, t) if (s, t) is an edge in the graph
p <- {
s, t infinity otherwise
for k <- 1 to n do
for s <- 1 to n do
for t <- 1 to n do
(k) (k - 1) (k - 1) (k - 1)
p = min { p , p + p }
s, t s, t s, k k, t
od
od
od
(n)
return p
Example: (@ denotes infinity here.)
1
2---4
3 |\_ | 1
| 6\|
1---3
2
0 3 2 @
(0) 3 0 6 1
p : 2 6 0 1
@ 1 1 0
0 3 2 @
(1) 3 0 5 1
p : 2 5 0 1
@ 1 1 0
0 3 2 4
(2) 3 0 5 1
p : 2 5 0 1
4 1 1 0
0 3 2 3
(3) 3 0 5 1
p : 2 5 0 1
3 1 1 0
0 3 2 3
(4) 3 0 2 1
p : 2 2 0 1
3 1 1 0