-
Notifications
You must be signed in to change notification settings - Fork 255
Bottom Up DP Technique
In the Dynamic Programming Overview, we solved the "Hello World" of recursion problems, a function that found the $N$th Fibonacci number in
Let's now take a look at the call graph for the recursive algorithm with repeat calls to the same function pointing to the same node.
Here we see that f(5)
depends on the same result from f(3)
as f(4)
does. By flattening this graph out as well, we can see that it is a directed acyclic graph (DAG). It is important that it is acyclic in the top down technique because if there was a cycle in the call graph the recursive algorithm would have required a cached answer for one that did not exist already and you would have blown your stack as you continuously evaluated the cycle.
In the bottom up technique, forming the DAG is essential because you need to understand where the other end of the call graph is or the bottom or the opposite end of the answer from a topologically sorted graph. In this case, that is f(0)
. So a bottom up DP solution will start from f(0)
and move to the right solving each Fibonacci number until we get to the answer.
class FibBottomUp(Fib):
def __init__(self):
self.cache = {}
def f(self, n):
c = self.cache
c[0] = 0
c[1] = 1
self.work = 2
for i in range(2,n+1):
self.work += 1
c[i] = c[i-1] + c[i-2]
return c[n]
print(FibBottomUp().results(6))
The 6th Fibonacci number is 8 and requires 7 computations
In this solution we replaced the recursive call with an iterative call and rather than working from the desired number back to the dependencies, we start with the known dependencies and work up to the solution.
Because we are working up from the dependencies we can often spot when dependencies will no longer be necessary and use that information to reduce the space requirement of the algorithm.
class FibNoCache(Fib):
def f(self, n):
if n < 2:
return n
self.work = 2
pp = 0
p = 1
for i in range(2, n+1):
self.work += 1
t = p + pp
pp = p
p = t
return p
print(FibNoCache().results(6))
The 6th Fibonacci number is 8 and requires 7 computations
So now we've achieved the same computation time saving, i.e., going from