17

I've always wondered about this. And no books state this explicitly.

Backtracking is exploring all possibilities until we figure out one possibility cannot lead us to a possible solution, in that case we drop it.

Dynamic programming as I understand it is characterized by overlapping sub-problems. So, can dynamic programming can be stated as backtracking with cache (for previously explored paths) ?

Thanks

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Mohan
  • 1,850
  • 1
  • 19
  • 42
  • 1
    What you refer to, backtracking with cache is usually called "memoization". Memoization has most of the benefits of Dynamic Programming with much less pain. The key difference between DP and Memoization is that DP uses the **optimal** recursion order, which usually involves a proof to show it is optimal.For the record, Dynamic Programming is about the cheesiest name one could choose for a "type" of programming. – ldog Apr 07 '14 at 17:49
  • 1
    @ldog: Memoization is one way of implementing DP, and will in every case generate *at most as many* subproblems to solve (since bottom-up DP, where you loop through the table cells filling them all in, might compute solutions to subproblems you don't actually need). If you need all or most subproblems to be solved, bottom-up will usually be a small constant factor faster because you can avoid pushing/popping the stack to recurse and the `if (notAlreadyComputed)` test in memoized solutions. – j_random_hacker Apr 07 '14 at 18:13
  • 2
    @ldog: The "programming" in "dynamic programming" is an old term that meant "working with tables", rather than anything to do with computers. The same etymology can by seen in the phrase "linear programming". – Chris Okasaki Apr 07 '14 at 18:18
  • I think one essential point is missing in the answers so far: DP does never do "backtracking" as in, recurse into a non-optimal subtree. So there's an analogy between caching and memoized DP, but backtracking is something you want to avoid when designing efficient algorithms – Niklas B. Apr 07 '14 at 18:43
  • 4
    Apparently, the name 'dynamic programming' is so cheesy because the inventor of the algorithm was [hiding from his boss](http://en.wikipedia.org/wiki/Dynamic_programming#History) that he was doing mathematical research. – Bas Swinckels Apr 07 '14 at 19:48
  • @BasSwinckels Nice, that's one interesting piece of trivia :D Indeed it is really not a very fitting term – Niklas B. Apr 07 '14 at 20:01
  • @NiklasB. The question is not talking about memoized DP, but rather about memoized backtracking which unlike DP is guaranteed to never compute unnecessary solutions to non-needed subproblems, as already stated somewhere on this page. Backtracking is just like nested top down loops, and DP is like those loops fully unrolled, with the order flipped to bottom up. – Will Ness Jun 04 '22 at 07:34

4 Answers4

8

This is one face of dynamic programming, but there's more to it.

For a trivial example, take Fibonacci numbers:

F (n) =
        n = 0:  0
        n = 1:  1
        else:   F (n - 2) + F (n - 1)

We can call the above code "backtracking" or "recursion". Let us transform it into "backtracking with cache" or "recursion with memoization":

F (n) =
       n in Fcache:  Fcache[n]
       n = 0:  0, and cache it as Fcache[0]
       n = 1:  1, and cache it as Fcache[1]
       else:  F (n - 2) + F (n - 1), and cache it as Fcache[n]

Still, there is more to it.

If a problem can be solved by dynamic programming, there is a directed acyclic graph of states and dependencies between them. There is a state that interests us. There are also base states for which we know the answer right away.

  • We can traverse that graph from the vertex that interests us to all its dependencies, from them to all their dependencies in turn, etc., stopping to branch further at the base states. This can be done via recursion.

  • A directed acyclic graph can be viewed as a partial order on vertices. We can topologically sort that graph and visit the vertices in sorted order. Additionally, you can find some simple total order which is consistent with your partial order.

Also note that we can often observe some structure on states. For example, the states can be often expressed as integers or tuples of integers. So, instead of using generic caching techniques (e.g., associative arrays to store state->value pairs), we may be able to preallocate a regular array which is easier and faster to use.


Back to our Fibonacci example, the partial order relation is just that state n >= 2 depends on states n - 1 and n - 2. The base states are n = 0 and n = 1. A simple total order consistent with this order relation is the natural order: 0, 1, 2, .... Here is what we start with:

Preallocate array F with indices 0 to n, inclusive
F[0] = 0
F[1] = 1

Fine, we have the order in which to visit the states. Now, what's a "visit"? There are again two possibilities:

(1) "Backward DP": When we visit a state u, we look at all its dependencies v and calculate the answer for that state u:

for u = 2, 3, ..., n:
    F[u] = F[u - 1] + F[u - 2]

(2) "Forward DP": When we visit a state u, we look at all states v that depend on it and account for u in each of these states v:

for u = 1, 2, 3, ..., n - 1:
    add F[u] to F[u + 1]
    add F[u] to F[u + 2]

Note that in the former case, we still use the formula for Fibonacci numbers directly. However, in the latter case, the imperative code cannot be readily expressed by a mathematical formula. Still, in some problems, the "forward DP" approach is more intuitive (no good example for now; anyone willing to contribute it?).


One more use of dynamic programming which is hard to express as backtracking is the following: Dijkstra's algorithm can be considered DP, too. In the algorithm, we construct the optimal paths tree by adding vertices to it. When we add a vertex, we use the fact that the whole path to it - except the very last edge in the path - is already known to be optimal. So, we actually use an optimal solution to a subproblem - which is exactly the thing we do in DP. Still, the order in which we add vertices to the tree is not known in advance.

Gassa
  • 8,546
  • 3
  • 29
  • 49
  • 1
    I want to point out that the so-called "backward DP", i.e., the recursive form can be directly used in bottom-up DP fashion - once you figure out the total order, walk that order from base state to desired state, and the recursive function guarantees that, when the solution for a state is being calculated, all its dependency states are already solved. – KFL Apr 17 '18 at 05:07
  • @KFL Yeah, that's a middle ground. Encapsulating the calculation in a function helps reasoning about the solution. On the other hand, in extreme cases, the function itself may become a bottleneck. – Gassa Apr 17 '18 at 08:44
6

No. Or rather sort of.

In backtracking, you go down and then back up each path. However, dynamic programming works bottom-up, so you only get the going-back-up part not the original going-down part. Furthermore, the order in dynamic programming is more breadth first, whereas backtracking is usually depth first.

On the other hand, memoization (dynamic programming's very close cousin) does very often work as backtracking with a cache, as you describede.

Chris Okasaki
  • 4,875
  • 1
  • 20
  • 19
  • 2
    Yep, memoization is a means of implementing DP (and a good one too, because the table gets filled in the correct order "automagically") – Niklas B. Apr 07 '14 at 17:25
  • 2
    To my understanding, memoization and "proper" dynamic programming are the same in terms of complexity and in relative order of calculation of values; however, memoization, although seemingly naive, might skip the evaluation of some subproblems. – Codor Apr 08 '14 at 10:18
  • 1
    @Codor: It depends on what you mean by "relative order". They both calculate each value before it is needed, but that still leaves a wide variety of valid orderings. Memoization will follow a just-in-time ordering with respect to the recursion, but DP usually will not. – Chris Okasaki Apr 08 '14 at 10:54
  • @Chris Okasaki: Yes, my comment is a bit inaccurate; I meant it more in the sense you stated, like: "The value[s] which lie at the end of recursion get evaluated first". If the state space has more than one dimension, values in some "earlier slice" get calculated before the "later slices". – Codor Apr 08 '14 at 11:16
4

Yes and no.

Dynamic Programming is basically an efficient way to implement a recursive formula, and top-down DP is many times actually done with recursion + cache:

def f(x):
  if x is in cache:
    return cache[x]
  else:
    res <- .. do something with f(x-k)
    cahce[x] <- res
    return res

Note that bottom-up DP is implemented completely different however - but still pretty much follows the basic principles of the recursive approach, and at each step 'calculates' the recursive formula on the smaller (already known) sub-problems.

However, in order to be able to use DP - you need to have some characteristics for the problem, mainly - an optimal solution to the problem consists of optimal solutions to its sub-problems. An example where it holds is shortest-path problem (An optimal path from s to t that goes through u must consist of an optimal path from s to u).

It does not exist on some other problems such as Vertex-Cover or Boolean satisfiability Problem , and thus you cannot replace the backtracking solution for it with DP.

amit
  • 175,853
  • 27
  • 231
  • 333
  • It works for vertex cover and the alike too, but there we usually have exponentially many distinct subproblems – Niklas B. Apr 07 '14 at 17:06
  • @NiklasB. Not straight forward anyway, You can create a new problem that satisfies the stated characteristic - and then apply DP on it, but an optimal VC does not have to be constructed from optimal subsets. – amit Apr 07 '14 at 17:08
1

No. What you call backtracking with cache is basically memoization.

In dynamic programming, you go bottom-up. That is, you start from a place where you don't need any subproblems. In particular, when you need to calculate the nth step, all the n-1 steps are already calculated.

This is not the case for memoization. Here, you start off from the kth step (the step you want) and go on solving the previous steps wherever required. And obviously keep these values stored somewhere so that you may access these later.

All these being said, there are no differences in running time in case of memoization and dynamic programming.

HelloWorld123456789
  • 5,299
  • 3
  • 23
  • 33