What is dynamic programming? [closed] - algorithm

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
What is dynamic programming?
How is it different from recursion, memoization, etc?
I have read the wikipedia article on it, but I still don't really understand it.

Dynamic programming is when you use past knowledge to make solving a future problem easier.
A good example is solving the Fibonacci sequence for n=1,000,002.
This will be a very long process, but what if I give you the results for n=1,000,000 and n=1,000,001? Suddenly the problem just became more manageable.
Dynamic programming is used a lot in string problems, such as the string edit problem. You solve a subset(s) of the problem and then use that information to solve the more difficult original problem.
With dynamic programming, you store your results in some sort of table generally. When you need the answer to a problem, you reference the table and see if you already know what it is. If not, you use the data in your table to give yourself a stepping stone towards the answer.
The Cormen Algorithms book has a great chapter about dynamic programming. AND it's free on Google Books! Check it out here.

Dynamic programming is a technique used to avoid computing multiple times the same subproblem in a recursive algorithm.
Let's take the simple example of the Fibonacci numbers: finding the n th Fibonacci number defined by
Fn = Fn-1 + Fn-2 and F0 = 0, F1 = 1
Recursion
The obvious way to do this is recursive:
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
Dynamic Programming
Top Down - Memoization
The recursion does a lot of unnecessary calculations because a given Fibonacci number will be calculated multiple times. An easy way to improve this is to cache the results:
cache = {}
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
if n in cache:
return cache[n]
cache[n] = fibonacci(n - 1) + fibonacci(n - 2)
return cache[n]
Bottom-Up
A better way to do this is to get rid of the recursion all-together by evaluating the results in the right order:
cache = {}
def fibonacci(n):
cache[0] = 0
cache[1] = 1
for i in range(2, n + 1):
cache[i] = cache[i - 1] + cache[i - 2]
return cache[n]
We can even use constant space and store only the necessary partial results along the way:
def fibonacci(n):
fi_minus_2 = 0
fi_minus_1 = 1
for i in range(2, n + 1):
fi = fi_minus_1 + fi_minus_2
fi_minus_1, fi_minus_2 = fi, fi_minus_1
return fi
How apply dynamic programming?
Find the recursion in the problem.
Top-down: store the answer for each subproblem in a table to avoid having to recompute them.
Bottom-up: Find the right order to evaluate the results so that partial results are available when needed.
Dynamic programming generally works for problems that have an inherent left to right order such as strings, trees or integer sequences. If the naive recursive algorithm does not compute the same subproblem multiple times, dynamic programming won't help.
I made a collection of problems to help understand the logic: https://github.com/tristanguigue/dynamic-programing

Memoization is the when you store previous results of a function call (a real function always returns the same thing, given the same inputs). It doesn't make a difference for algorithmic complexity before the results are stored.
Recursion is the method of a function calling itself, usually with a smaller dataset. Since most recursive functions can be converted to similar iterative functions, this doesn't make a difference for algorithmic complexity either.
Dynamic programming is the process of solving easier-to-solve sub-problems and building up the answer from that. Most DP algorithms will be in the running times between a Greedy algorithm (if one exists) and an exponential (enumerate all possibilities and find the best one) algorithm.
DP algorithms could be implemented with recursion, but they don't have to be.
DP algorithms can't be sped up by memoization, since each sub-problem is only ever solved (or the "solve" function called) once.

It's an optimization of your algorithm that cuts running time.
While a Greedy Algorithm is usually called naive, because it may run multiple times over the same set of data, Dynamic Programming avoids this pitfall through a deeper understanding of the partial results that must be stored to help build the final solution.
A simple example is traversing a tree or a graph only through the nodes that would contribute with the solution, or putting into a table the solutions that you've found so far so you can avoid traversing the same nodes over and over.
Here's an example of a problem that's suited for dynamic programming, from UVA's online judge: Edit Steps Ladder.
I'm going to make quick briefing of the important part of this problem's analysis, taken from the book Programming Challenges, I suggest you check it out.
Take a good look at that problem, if we define a cost function telling us how far appart two strings are, we have two consider the three natural types of changes:
Substitution - change a single character from pattern "s" to a different character in text "t", such as changing "shot" to "spot".
Insertion - insert a single character into pattern "s" to help it match text "t", such as changing "ago" to "agog".
Deletion - delete a single character from pattern "s" to help it match text "t", such as changing "hour" to "our".
When we set each of this operations to cost one step we define the edit distance between two strings. So how do we compute it?
We can define a recursive algorithm using the observation that the last character in the string must be either matched, substituted, inserted or deleted. Chopping off the characters in the last edit operation leaves a pair operation leaves a pair of smaller strings. Let i and j be the last character of the relevant prefix of and t, respectively. there are three pairs of shorter strings after the last operation, corresponding to the string after a match/substitution, insertion or deletion. If we knew the cost of editing the three pairs of smaller strings, we could decide which option leads to the best solution and choose that option accordingly. We can learn this cost, through the awesome thing that's recursion:
#define MATCH 0 /* enumerated type symbol for match */
#define INSERT 1 /* enumerated type symbol for insert */
#define DELETE 2 /* enumerated type symbol for delete */
int string_compare(char *s, char *t, int i, int j)
{
int k; /* counter */
int opt[3]; /* cost of the three options */
int lowest_cost; /* lowest cost */
if (i == 0) return(j * indel(’ ’));
if (j == 0) return(i * indel(’ ’));
opt[MATCH] = string_compare(s,t,i-1,j-1) +
match(s[i],t[j]);
opt[INSERT] = string_compare(s,t,i,j-1) +
indel(t[j]);
opt[DELETE] = string_compare(s,t,i-1,j) +
indel(s[i]);
lowest_cost = opt[MATCH];
for (k=INSERT; k<=DELETE; k++)
if (opt[k] < lowest_cost) lowest_cost = opt[k];
return( lowest_cost );
}
This algorithm is correct, but is also impossibly slow.
Running on our computer, it takes several seconds to compare two 11-character strings, and the computation disappears into never-never land on anything longer.
Why is the algorithm so slow? It takes exponential time because it recomputes values again and again and again. At every position in the string, the recursion branches three ways, meaning it grows at a rate of at least 3^n – indeed, even faster since most of the calls reduce only one of the two indices, not both of them.
So how can we make the algorithm practical? The important observation is that most of these recursive calls are computing things that have already been computed before. How do we know? Well, there can only be |s| · |t| possible unique recursive calls, since there are only that many distinct (i, j) pairs to serve as the parameters of recursive calls.
By storing the values for each of these (i, j) pairs in a table, we can
avoid recomputing them and just look
them up as needed.
The table is a two-dimensional matrix m where each of the |s|·|t| cells contains the cost of the optimal solution of this subproblem, as well as a parent pointer explaining how we got to this location:
typedef struct {
int cost; /* cost of reaching this cell */
int parent; /* parent cell */
} cell;
cell m[MAXLEN+1][MAXLEN+1]; /* dynamic programming table */
The dynamic programming version has three differences from the recursive version.
First, it gets its intermediate values using table lookup instead of recursive calls.
**Second,**it updates the parent field of each cell, which will enable us to reconstruct the edit sequence later.
**Third,**Third, it is instrumented using a more general goal cell() function instead of just returning m[|s|][|t|].cost. This will enable us to apply this routine to a wider class of problems.
Here, a very particular analysis of what it takes to gather the most optimal partial results, is what makes the solution a "dynamic" one.
Here's an alternate, full solution to the same problem. It's also a "dynamic" one even though its execution is different. I suggest you check out how efficient the solution is by submitting it to UVA's online judge. I find amazing how such a heavy problem was tackled so efficiently.

The key bits of dynamic programming are "overlapping sub-problems" and "optimal substructure". These properties of a problem mean that an optimal solution is composed of the optimal solutions to its sub-problems. For instance, shortest path problems exhibit optimal substructure. The shortest path from A to C is the shortest path from A to some node B followed by the shortest path from that node B to C.
In greater detail, to solve a shortest-path problem you will:
find the distances from the starting node to every node touching it (say from A to B and C)
find the distances from those nodes to the nodes touching them (from B to D and E, and from C to E and F)
we now know the shortest path from A to E: it is the shortest sum of A-x and x-E for some node x that we have visited (either B or C)
repeat this process until we reach the final destination node
Because we are working bottom-up, we already have solutions to the sub-problems when it comes time to use them, by memoizing them.
Remember, dynamic programming problems must have both overlapping sub-problems, and optimal substructure. Generating the Fibonacci sequence is not a dynamic programming problem; it utilizes memoization because it has overlapping sub-problems, but it does not have optimal substructure (because there is no optimization problem involved).

Dynamic Programming
Definition
Dynamic programming (DP) is a general algorithm design technique for solving
problems with overlapping sub-problems. This technique was invented by American
mathematician “Richard Bellman” in 1950s.
Key Idea
The key idea is to save answers of overlapping smaller sub-problems to avoid recomputation.
Dynamic Programming Properties
An instance is solved using the solutions for smaller instances.
The solutions for a smaller instance might be needed multiple times,
so store their results in a table.
Thus each smaller instance is solved only once.
Additional space is used to save time.

I am also very much new to Dynamic Programming (a powerful algorithm for particular type of problems)
In most simple words, just think dynamic programming as a recursive approach with using the previous knowledge
Previous knowledge is what matters here the most, Keep track of the solution of the sub-problems you already have.
Consider this, most basic example for dp from Wikipedia
Finding the fibonacci sequence
function fib(n) // naive implementation
if n <=1 return n
return fib(n − 1) + fib(n − 2)
Lets break down the function call with say n = 5
fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
In particular, fib(2) was calculated three times from scratch. In larger examples, many more values of fib, or sub-problems, are recalculated, leading to an exponential time algorithm.
Now, lets try it by storing the value we already found out in a data-structure say a Map
var m := map(0 → 0, 1 → 1)
function fib(n)
if key n is not in map m
m[n] := fib(n − 1) + fib(n − 2)
return m[n]
Here we are saving the solution of sub-problems in the map, if we don't have it already. This technique of saving values which we already had calculated is termed as Memoization.
At last, For a problem, first try to find the states (possible sub-problems and try to think of the better recursion approach so that you can use the solution of previous sub-problem into further ones).

Dynamic programming is a technique for solving problems with overlapping sub problems.
A dynamic programming algorithm solves every sub problem just once and then
Saves its answer in a table (array).
Avoiding the work of re-computing the answer every time the sub problem is encountered.
The underlying idea of dynamic programming is:
Avoid calculating the same stuff twice, usually by keeping a table of known results of sub problems.
The seven steps in the development of a dynamic programming algorithm are as follows:
Establish a recursive property that gives the solution to an instance of the problem.
Develop a recursive algorithm as per recursive property
See if same instance of the problem is being solved again an again in recursive calls
Develop a memoized recursive algorithm
See the pattern in storing the data in the memory
Convert the memoized recursive algorithm into iterative algorithm
Optimize the iterative algorithm by using the storage as required (storage optimization)

in short the difference between recursion memoization and Dynamic programming
Dynamic programming as name suggest is using the previous calculated value to dynamically construct the next new solution
Where to apply dynamic programming : If you solution is based on optimal substructure and overlapping sub problem then in that case using the earlier calculated value will be useful so you do not have to recompute it. It is bottom up approach. Suppose you need to calculate fib(n) in that case all you need to do is add the previous calculated value of fib(n-1) and fib(n-2)
Recursion : Basically subdividing you problem into smaller part to solve it with ease but keep it in mind it does not avoid re computation if we have same value calculated previously in other recursion call.
Memoization : Basically storing the old calculated recursion value in table is known as memoization which will avoid re-computation if its already been calculated by some previous call so any value will be calculated once. So before calculating we check whether this value has already been calculated or not if already calculated then we return the same from table instead of recomputing. It is also top down approach

Here is a simple python code example of Recursive, Top-down, Bottom-up approach for Fibonacci series:
Recursive: O(2n)
def fib_recursive(n):
if n == 1 or n == 2:
return 1
else:
return fib_recursive(n-1) + fib_recursive(n-2)
print(fib_recursive(40))
Top-down: O(n) Efficient for larger input
def fib_memoize_or_top_down(n, mem):
if mem[n] is not 0:
return mem[n]
else:
mem[n] = fib_memoize_or_top_down(n-1, mem) + fib_memoize_or_top_down(n-2, mem)
return mem[n]
n = 40
mem = [0] * (n+1)
mem[1] = 1
mem[2] = 1
print(fib_memoize_or_top_down(n, mem))
Bottom-up: O(n) For simplicity and small input sizes
def fib_bottom_up(n):
mem = [0] * (n+1)
mem[1] = 1
mem[2] = 1
if n == 1 or n == 2:
return 1
for i in range(3, n+1):
mem[i] = mem[i-1] + mem[i-2]
return mem[n]
print(fib_bottom_up(40))

Related

Trying to optimize DP solutions from 2D Array to 1D Array

I've been trying to wrap my head around the fact that a lot of DP questions that involve bottom up tabulation via a 2D Matrix can be simplified into a 1D Array to save on space since you only rely on the previous two rows but I don't really understand the why/how/intuition behind this.
Just wondering if anyone could offer the most dumb downed version of why this works...
Generally, we use can apply DP when the optimal solution to a given problem can be determined from the optimal solutions of its subproblems. When coming up with a solution to some algorithm, it usually helps to come up with a recursive one first. From there, if we observe that recursive subproblems are re-calculated multiple times, we can just memoize the intermediate results for fast reference to them later.
In some special cases, we don't actually need to remember the solution to all subproblems at once; we just need to know a certain subset at a time.
The space optimization described above seems to best answer to the question you're asking - how does one condense the total set of solutions as a 2D matrix into a single 1D array? Well, at a given time, we don't actually store all solutions (the 2D matrix) in any single point in time; we just store what is needed to calculate the next round of intermediate/final outputs in the algorithm.
Perhaps walking through an example application may help reinforce this description.
A nice example is the generalized stock trading problem. Basically, we have an input array prices of a stock on a given day and would like to calculate the maximum profit that can be earned if k buy-sell transactions are made, where one stock may be bought and held at any given time.
The trickiest part in my opinion is figuring out how to move from the one transaction case to the two transaction case. I'll assume we're proficient enough in dynamic programming to move immediately to the k transaction case. Notice that a nice formulation of the problem in terms of subproblems is the following:
prices = input array of prices, length is n
Define dp[k][i] = maximum profit earned by day i, after having made k transactions
dp[k][i] = max(dp[k][i - 1], (prices[i] + effectivePrice)
effectivePrice = max(dp[k - 1][i] - prices[i], effectivePrice) (compute on the fly for each i)
Now in this particular case our "naive" dp solution has a 2D matrix with k rows and n columns. The space reduction here is that, in order to calculate the result for k transactions, we only need knowledge of the case for k - 1 transactions. Therefore, it is certainly possible to solve the problem using two 1D arrays of size n.
Let oldDp = solution for the k - 1 case
Let newDp = solution for the k case (computed on the fly)
for each transaction:
for each day i:
newDp[i] = max(newDp[i - 1], (prices[i] + effectivePrice)
effectivePrice = max(oldDp[i] - prices[i], effectivePrice)
// Set up for next iteration
oldDp = newDp
newDp = blank array of size n
As we can see, we managed to save a lot of space - we went from having to use a 2D matrix with k rows and n columns to two 1D arrays of size n. An even better optimization is to just use a single 1D array; this is possible since the only indices that we examine in oldDp is the current one i when calculating effectivePrice. Because we only need to temporarily remember the old result for day i, we can just make use of a temporary variable. Thus, the optimized pseudocode (for our "naive" approach) appears as below:
Let dp = maximum profit, so that dp[i] = maximum profit after k transactions (built iteratively) on day i.
for each transaction:
for each day i:
// At this point, dp[i] is equivalent to dp[k - 1][i], yet
// for all j < i, dp[j] is equivalent to dp[k][j]!
temp = dp[i]
dp[i] = max(dp[i - 1], prices[i] + effectivePrice)
effectivePrice = max(temp - prices[i], effectivePrice)
And so, using the "naive" idea of determining the optimal solution after k transactions from k - 1 transactions, we optimize space by going from a 2D matrix of size kn to a 1D array of size n.

How many subproblems are there in this Activity Selection recursive breakdown?

Activity Selection: Given a set of activities A with start and end times, find a maximum subset of mutually compatible activities.
My problem
The two approaches seem to be the same, but the numSubproblems in firstApproach is exponential, while in secondApproach is O(n^2). If I were to memoize the result, then how can I memoize firstApproach?
The naive firstApproach
let max = 0
for (a: Activities):
let B = {Activities - allIncompatbleWith(a)}
let maxOfSubproblem = ActivitySelection(B)
max = max (max, maxOfSubproblem+1)
return max
1. Assume a particular activity `a` is part of the optimal solution
2. Find the set of activities incompatible with `a: allIncompatibleWith(a)`.
2. Solve Activity for the set of activities: ` {Activities - allImcompatibleWith(a)}`
3. Loop over all activities `a in Activities` and choose maximum.
The CLRS Section 16.1 based secondApproach
Solve for S(0, n+1)
let S(i,j) = 0
for (k: 0 to n):
let a = Activities(k)
let S(i,k) = solution for the set of activities that start after activity-i finishes and end before activity-k starts
let S(k,j) = solution for the set of activities that start after activity-k finishes and end before activyty-j starts.
S(i,j) = max (S(i,k) + S(k,j) + 1)
return S(i,j)
1. Assume a particular activity `a` is part of optimal solution
2. Solve the subproblems for:
(1) activities that finish before `a` starts
(2) activities that start after `a` finishes.
Let S(i, j) refer to the activities that lie between activities i and j (start after i and end before j).
Then S(i,j) characterises the subproblems needed to be solved above. ),
S(i,j) = max S(i,k) + S(k,j) + 1, with the variable k looped over j-i indices.
My analysis
firstApproach:
#numSubproblems = #numSubset of the set of all activities = 2^n.
secondApproach:
#numSubproblems = #number of ways to chooose two indicises from n indices, with repetition. = n*n = O(n^2)
The two approaches seem to be the same, but the numSubproblems in firstApproach is exponential, while in secondApproach is O(n^2). What's the catch? Why are they different, even thought the two approaches seem to be the same?
The two approaches seem to be the same
The two solutions are not the same. The difference is in the number of states possible in the search space. Both solutions exhibit overlapping sub-problems and optimal substructure. Without memoization, both solutions browse through the entire search space.
Solution 1
This a backtracking solution where all subsets that are compatible with an activity are tried and each time an activity is selected, your candidate solution is incremented by 1 and compared with the currently stored maximum. It utilizes no insight of the start times and end times of the activities. The major difference is that the state of your recurrence is the entire subset of activities (compatible activities) for which the solution needs to be determined (regardless of their start and finish times). If you were to memoize the solution, you would have to use a bitmasks (or (std::bitset in C++) to store the solution for a subset of activities. You could also use std::set or other Set data structures.
Solution 2
The number of states for the sub-problems in the second solution are greatly reduced because the recurrence relation solves for only those activities which finish before the start of the current activity and those activities which start after the current activity finishes. Notice that the number of states in such a solution is determined by the number of possible values of the tuple (start time, end time). Since, there are n activities, the number of states are atmost n2. If we memoize this solution, we simply need to store the solution for a given start time and end time, which automatically gives a solution for the subset of activities that fall in this range, regardless of whether they are compatible among themselves.
Memoization always don't lead to polynomial time asymptotic time complexity. In the first approach, you can apply memoization, but that'll not reduce the time complexity to polynomial time.
What is memoization?
In simple words, memoization is nothing but a recursive solution (top-down) that stores the result of computed solution to sub-problem. And if the same sub-problem is to be calculated again, you return the originally stored solution instead of recomputing it.
Memoization in your first recursive solution
In your case each sub-problem is finding optimal selection of activities for a subset. So the memoization (in your case) will result in storing the optimal solution for all the subsets.
No doubt memoization will give you performance enhancements by avoiding recomputation of solution on a subset of activities that has been "seen" before, but it can't (in this case) reduce the time complexity to polynomial time because you end up storing the sub-solutions for every subset (in worst case).
Where memoization gives us real benefit?
On the other hand, if you see this, where memoization is applied for fibonacci series, the total number of sub-solutions that you have to store is linear with the size of the input. And thus it drops the exponential complexity to linear.
How can you memoize the first solution
For applying memoization in the first approach, you need to maintain the sub-solutions. The data-structure that you can use is Map<Set<Activity>, Integer> which will store the maximum number of compatible activities for the given Set<Activity>. In java equals() on a java.util.Set works properly across all the implementations, so you can use it.
Your first approach will be modified like this:
// this structure memoizes the sub-solutions
Map<Set<Activity>, Integer> map;
ActivitySelection(Set<Activity> activities) {
if(map contains activities)
return map.getValueFor(activities);
let max = 0
for (a: activities):
let B = {Activities - allIncompatbleWith(a)}
let maxOfSubproblem = ActivitySelection(B)
max = max (max, maxOfSubproblem+1)
map.put(activities, max)
return max
}
On a lighter note:
The time complexity of the second solution (CLRS 16.1) will be O(n^3) instead of O(n^2). You'll have to have 3 loops for i, j and k. The space complexity for this solution is O(n^2).

Is dynamic programming backtracking with cache

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
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.
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.
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.
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.

Efficient algorithm for finding a set of non adjacent subarrays maximizing their total sum

I've come across this problem in a programming contest site and been trying different things for a few days but none of them seem to be efficient enough.
Here is the question: You are given a large array of integers and a number k. The goal is to divide the array into subarrays each containing no more than k elements, such that the sum of all the elements in all the sub arrays is maximal. Another condition is that none of these sub arrays can be adjacent to each other. In other words, we have to drop a few terms from the original array.
Its been bugging me for a while and would like to hear your perspective on approaching this problem.
Dynamic programming should do the trick. Short explanation why:
The key property of a problem susceptible to dynamic programming is that the optimal solution to the problem (here: the whole array) can always be expressed as composition of two optimal solutions to subproblems (here: two subarrays.) Not every split needs to have this property - it is sufficient for one such split to exist for any optimal solution.
Clearly if you split the optimal solution between arrays (on an element that has been dropped), then the subsolutions are optimal within both subarrays.
The algorithm:
Try every element of the array in turn as the splitting element, looking for the one that yields the best result. Solve the problem recursively for both parts of the array (the recursion stops when the subarray is no longer than k). Memoize solutions to avoid exponential time (the recursion will obviously try the same subarray many times.)
This is not a solution, but a clue.
Consider solving the following problem:
From an array X choose elements a subset of elements such that none of them are adjacent to each other and their sum is maximum.
Now, the above problem is a special case of your problem where K=1. Think how you can expand the solution to a general case. Let me know if you don't know how to solve the simpler case.
I don't have time to explain why this works and should be the accepted answer:
def maxK(a, k):
states = k+1
myList = [0 for i in range(states)]
for i in range(0, len(a)):
maxV = max (myList)
myList = [a[i] + j for j in myList]
myList[(states-i) % k] = maxV
return max(myList)
This works with negative numbers too. This is linear in size(a) times k. The language I used is Python because at this level it can be read as if it were pseudo code.

Knapsack with continuous (non distinct) constraint

I watched Dynamic Programming - Kapsack Problem (YouTube). However, I am solving a slightly different problem where the constraint is the budget, price, in double, not integer. So I am wondering how can I modify that? Double is "continuous" unlike integer where I can have 1,2,3 .... I don't suppose I do 0.0, 0.1, 0.2 ...?
UPDATE 1
I thought of converting double to int by multiply by 100. Money is only 2 decimal places. But that will mean the range of values will be very large?
UPDATE 2
The problem I need to solve is:
Items have a price (double) & satisfaction (integer) value. I have a budget as a constraint and I need to maximize satisfaction value.
In the youtube video, the author created two 2d array like int[numItems][possibleCapacity(weight)]. Here, I can't as budget is a double not integer
If you want to use floating point numbers with arbitrary precision (i.e., don't have a fixed number of decimals), and these are not fractions, dynamic programming won't work.
The basis of dynamic programming is to store previous results of a calculation for specific inputs. Therefore, if you used floating point numbers with arbitrary precision, you would need practically infinite memory for each of the possible floating point numbers and, of course, do infinite calculations, something that is impossible and non-optimal.
However, if these numbers have a fixed precision (as with the money, which only have two decimal numbers), you can convert these into integers by multiplying them (as you've mentioned), and then solve the knapsack problem as usual.
You will have to do what you said in UPDATE 1: express the budget and item prices in cents (assuming we are talking about dollars). Then we're not talking about arbitrary precision or continuous numbers. Every price (and the budget) will be an integer, it's just that that integer will represent cents.
To make things easier let's assume the budget is $10. The problem is that the Knapsack Capacity will have to take all the values in:
[0.00, 0.01, 0.02, 0.03, ..., 9.99, 10.00]
The values are two many. Each line of the SOLUTION MATRIX and the KEEP MATRIX will have 1001 columns so you won't be able to solve the problem by hand (if the budget is millions of dollars even a computer might have a hard time) but that is inherent to the original problem (you can't do anything about it).
Your best bet is to use some existing code about KNAPSACK or maybe write your own (I don't advice that).
If you can't find existing code about KNAPSACK and are familiar with Linux/Mac I suggest you install the GNU Linear Programming Kit (GLPK) and express the problem as an Integer Linear Program or a Binary Linear Program (if you're trying to solve the 0-1 Knapsack). It will solve the problem for you (plus you can use it through C, C++, Python and maybe Java if you need to). For help using GLPK check this awesome article (you'll probably need part 2, where it talks about integer variables). If you need more help with GLPK please leave a comment.
EDIT:
Basically, what I'm trying to say is that your constraint is not continuous, it's discrete (cents), your problem is that the budget might be too many cents so you won't be able to solve it by hand.
Don't get intimidated because your budget might be several dollars -> several hundreds of cents. If your budget is just 18 cents your problem's size will be comparable to the one in the YouTube video. The guy in the video wouldn't be able to solve his problem either (by hand) if his knapsack size was 1800 (or even 180).
This is not an answer to your question, but might as well be what you are looking for:
Linear Programming
I've used Microsoft's Solver Foundation 3 to make a simple code that solves the problem you described. It doesn't use the knapsack algorithm, but a simplex method.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SolverFoundation.Common;
using Microsoft.SolverFoundation.Services;
namespace LPOptimizer
{
class Item
{
public String ItemName { get; set; }
public double Price { get; set; }
public double Satisfaction { get; set; }
static void Main(string[] args)
{
//Our data, budget and items with respective satisfaction and price values
double budget = 100.00;
List<Item> items = new List<Item>()
{
new Item(){
ItemName="Product_1",
Price=20.1,
Satisfaction=2.01
},
new Item(){
ItemName="Product_2",
Price=1.4,
Satisfaction=0.14
},
new Item(){
ItemName="Product_3",
Price=22.1,
Satisfaction=2.21
}
};
//variables for solving the problem.
SolverContext context = SolverContext.GetContext();
Model model = context.CreateModel();
Term goal = 0;
Term constraint = 0;
foreach (Item i in items)
{
Decision decision = new Decision(Domain.IntegerNonnegative, i.ItemName);
model.AddDecision(decision); //each item is a decision - should the algorithm increase this item or not?
goal += i.Satisfaction * decision; //decision will contain quantity.
constraint += i.Price * decision;
}
constraint = constraint <= budget; //this now says: "item_1_price * item_1_quantity + ... + item_n_price * item_n_quantity <= budget";
model.AddConstraints("Budget", constraint);
model.AddGoals("Satisfaction", GoalKind.Maximize, goal); //goal says: "Maximize: item_1_satisfaction * item_1_quantity + ... + item_n_satisfaction * item_n_quantity"
Solution solution = context.Solve(new SimplexDirective());
Report report = solution.GetReport();
Console.Write("{0}", report);
Console.ReadLine();
}
}
}
This finds the optimum max for the number of items (integers) with prices (doubles), with a budget constraint (double).
From the code, it is obvious that you could have some items quantities in real values (double). This will probably also be faster than a knapsack with a large range (if you decide to use the *100 you mentioned).
You can easily specify additional constraints (such as number of certain items, etc...). The code above is adapted from this MSDN How-to, where it shows how you can easily define constraints.
Edit
It has occurred to me that you may not be using C#, in this case I believe there are a number of libraries for linear programming in many languages, and are all relatively simple to use: You specify constraints and a goal.
Edit2
According to your Update 2, I've updated this code to include satisfaction.
Have your looked at this.
Sorry, I don't have comment privilege.
Edit 1
Are you saying constraint is the budget instead of knapsack weight?
This still remains a knapsack problem.
Or are your saying instead of Item Values as Integers(0-1 knapsack problem) your have fractions. Then Greedy approach should do fine.
Edit 2
If I understand your problem correctly.. It states
We have n kinds of items, 1 through n. Each kind of item i has a value vi and a price pi. We usually assume that all values and pricess are nonnegative. The Budget is B.
The most common formulation of the problem is the 0-1 knapsack problem, which restricts the number xi of copies of each kind of item to zero or one. Mathematically the 0-1-knapsack problem can be formulated as:
n
maximize E(vi.xi)
i=i
n
subject to E(pi.xi) <= B, xi is a subset of {0,1}
i=1
Neo Adonis's answer is spot on here.. Dynamic programming wont work for arbitrary precision in practice.
But if you are willing to limit the precision say to 2 decimal places.. then carry on as explained in video.. your table should look something like this..
+------+--------+--------+--------+--------+--------+--------+
| Vi,Pi| 0.00 | 0.01 | 0.02 | 0.03 | 0.04 ... B |
+------+--------+--------+--------+--------+--------+--------+
|4,0.23| | | | | | |
|2,2.93| | | | | | |
|7,9.11| | | | | | |
| ... | | | | | | |
| Vn,Pn| | | | | | answer |
+------+--------+--------+--------+--------+--------+--------+
you can even convert real numbers to int as you have mentioned.
Yes, the range of values is very large, and you also have to understand knapsack is NP-complete, i.e, there is no efficient algorithm to solve this. only pseudo polynomial solution using DP. see this and this.
A question recently posted to sci.op-research offered me a welcome respite from some tedious work that I’d rather not think about and you’d rather not hear about. We know that the greedy heuristic solves the continuous knapsack problem
maximizec′xs.t.a′x≤bx≤ux∈ℜ+n(1)
to optimality. (The proof, using duality theory, is quite easy.) Suppose that we add what I’ll call a count constraint, yielding
maximizec′xs.t.a′x≤be′x=b˜x≤ux∈ℜ+n(2)
where e=(1,…,1) . Can it be solved by something other than the simplex method, such as a variant of the greedy heuristic?
The answer is yes, although I’m not at all sure that what I came up with is any easier to program or more efficient than the simplex method. Personally, I would link to a library with a linear programming solver and use simplex, but it was amusing to find an alternative even if the alternative may not be an improvement over simplex.
The method I’ll present relies on duality, specifically a well known result that if a feasible solution to a linear program and a feasible solution to its dual satisfy the complementary slackness condition, then both are optimal in their respective problems. I will denote the dual variables for the knapsack and count constraints λ and μ respectively. Note that λ≥0 but μ is unrestricted in sign. Essentially the same method stated below would work with an inequality count constraint (e′x≤b˜ ), and would in fact be slightly easier, since we would know a priori the sign of μ (nonnegative). The poster of the original question specified an equality count constraint, so that’s what I’ll use. There are also dual variables (ρ≥0 ) for the upper bounds. The dual problem is
minimizebλ+b˜μ+u′ρs.t.λa+μe+ρ≥cλ,ρ≥0.(3)
This being a blog post and not a dissertation, I’ll assume that (2) is feasible, that all parameters are strictly positive, and that the optimal solution is unique and not degenerate. Uniqueness and degeneracy will not cause invalidate the algorithm, but they would complicate the presentation. In an optimal basic feasible solution to (2), there will be either one or two basic variables — one if the knapsack constraint is nonbinding, two if it is binding — with every other variable nonbasic at either its lower or upper bound. Suppose that (λ,μ,ρ) is an optimal solution to the dual of (2). The reduced cost of any variable xi is ri=ci−λai−μ . If the knapsack constraint is nonbinding, then λ=0 and the optimal solution is
xi=uiri>0b˜−∑rj>0ujri=00ri<0.(4)
If the knapsack constraint is binding, there will be two items (j , k ) whose variables are basic, with rj=rk=0 . (By assuming away degeneracy, I’ve assumed away the possibility of the slack variable in the knapsack constraint being basic with value 0). Set
xi=uiri>00ri<0(5)
and let b′=b−∑i∉{j,k}aixi and b˜′=b˜−∑i∉{j,k}xi . The two basic variables are given by
xj=b′−akb˜′aj−akxk=b′−ajb˜′ak−aj.(6)
The algorithm will proceed in two stages, first looking for a solution with the knapsack nonbinding (one basic x variable) and then looking for a solution with the knapsack binding (two basic x variables). Note that the first time we find feasible primal and dual solutions obeying complementary slackness, both must be optimal, so we are done. Also note that, given any μ and any λ≥0 , we can complete it to obtain a feasible solution to (3) by setting ρi=ci−λai−μ+ . So we will always be dealing with a feasible dual solution, and the algorithm will construct primal solutions that satisfy complementary slackness. The stopping criterion therefore reduces to the constructed primal solution being feasible.
For the first phase, we sort the variables so that c1≥⋯≥cn . Since λ=0 and there is a single basic variable (xh ), whose reduced cost must be zero, obviously μ=ch . That means the reduced cost ri=ci−λai−μ=ci−ch of xi is nonnegative for ih . If the solution given by (3) is feasible — that is, if ∑ih . Thus we can use a bisection search to complete this phase. If we assume a large value of n , the initial sort can be done in O(nlogn ) time and the remainder of the phase requires O(logn) iterations, each of which uses O(n) time.
Unfortunately, I don’t see a way to apply the bisection search to the second phase, in which we look for solutions where the knapsack constraint is binding and λ>0 . We will again search on the value of μ , but this time monotonically. First apply the greedy heuristic to problem (1), retaining the knapsack constraint but ignoring the count constraint. If the solutions happens by chance to satisfy the count constraint, we are done. In most cases, though, the count constraint will be violated. If the count exceeds b˜ , then we can deduce that the optimal value of μ in (4) is positive; if the count falls short of b˜ , the optimal value of μ is negative. We start the second phase with μ=0 and move in the direction of the optimal value.
Since the greedy heuristic sorts items so that c1/a1≥⋯≥cn/an , and since we are starting with μ=0 , our current sort order has (c1−μ)/a1≥⋯≥(cn−μ)/an . We will preserve that ordering (resorting as needed) as we search for the optimal value of μ . To avoid confusion (I hope), let me assume that the optimal value of μ is positive, so that we will be increasing μ as we go. We are looking for values of (λ,μ) where two of the x variables are basic, which means two have reduced cost 0. Suppose that occurs for xi and xj ; then
ri=0=rj⟹ci−λai−μ=0=cj−λaj−μ(7)⟹ci−μai=λ=cj−μaj.
It is easy to show (left to the reader as an exercise) that if (c1−μ)/a1≥⋯≥(cn−μ)/an for the current value of μ , then the next higher (lower) value of μ which creates a tie in (7) must involve consecutive a consecutive pair of items (j=i+1 ). Moreover, again waving off degeneracy (in this case meaning more than two items with reduced cost 0), if we nudge μ slightly beyond the value at which items i and i+1 have reduced cost 0, the only change to the sort order is that items i and i+1 swap places. No further movement in that direction will cause i and i+1 to tie again, but of course either of them may end up tied with their new neighbor down the road.
The second phase, starting from μ=0 , proceeds as follows. For each pair (i,i+1) compute the value μi of μ at which (ci−μ)/ai=(ci+1−μ)/ai+1 ; replace that value with ∞ if it is less than the current value of μ (indicating the tie occurs in the wrong direction). Update μ to miniμi , compute λ from (7), and compute x from (5) and (6). If x is primal feasible (which reduces to 0≤xi≤ui and 0≤xi+1≤ui+1 ), stop: x is optimal. Otherwise swap i and i+1 in the sort order, set μi=∞ (the reindexed items i and i+1 will not tie again) and recompute μi−1 and μi+1 (no other μj are affected by the swap).
If the first phase did not find an optimum (and if the greedy heuristic at the start of the second phase did not get lucky), the second phase must terminate with an optimum before it runs out of values of μ to check (all μj=∞ ). Degeneracy can be handled either with a little extra effort in coding (for instance, checking multiple combinations of i and j in the second phase when three-way or higher ties occur) or by making small perturbations to break the degeneracy.
The answers are not quite correct.
You can implement a dynamic programm that solves the knapsack problem with integer satisfaction and arbitrary real number prizes like doubles.
First the standard solution of the problem with integer prizes:
Define K[0..M, 0..n] where K[j, i] = optimal value of items in knapsack of size j, using only items 1, ..., i
for j = 0 to M do K[j,0] = 0
for i = 1 to n do
for j = 0 to M do
//Default case: Do not take item i
K[j,1] = K[j, i-1]
if j >= w_i and v_i+K[j-w, i-1] > K[j, i] then
//Take item i
K[j,i] = v_i + K[j-w_i, i-1]
This creates a table where the solution can be found by following the recursion for entry K[M, n].
Now the solution for the problem with real number weight:
Define L[0..S, 0..N] where L[j, i] = minimal weight of items in knapsack of total value >= j, using only items 1, ..., i
and S = total value of all items
for j = 0 to S do L[j, 0] = 0
for i = 0 to n do
for j = 0 to S do
//Default case: Do not take item i
L[j,i] = L[j, i-1]
if j >= v_i and L[j-v_i, i-1] + w_i < L[j, i] then
//Take item i
L[j, i] = L[j -v_i, i-1] + w_i
The solution can now be found similiar to the other version. Instead of using the weight as first dimension we now use the total value of the items that lead to the minimal weight.
The code has more or less the same runtime O(S * N) whereas the other has O(M * N).
The answer to your question depends on several factors:
How large is the value of constraint (if scaled to cants and converted to integers).
How many items are there.
What kind of knapsack problem is to be solved
What is required precision.
If you have very large constraint value (much more than millions) and very many items (much more than thousands)
Then the only option is Greedy approximation algorithm. Sort the items in decreasing order of value per unit of weight and pack them in this order.
If you want to use a simple algorithm and do not require high precision
Then again try to use greedy algorithm. "Satisfaction value" itself may be very rough approximation, so why bother inventing complex solutions when simple approximation may be enough.
If you have very large (or even continuous) constraint value but pretty small number of items (less than thousands)
Then use branch and bound approach. You don't need to implement it from scratch. Try GNU GLPK. Its branch-and-cut solver is not perfect, but may be enough to solve small problems.
If both constraint value and number of items are small
Use any approach (DP, branch and bound, or just brute-force).
If constraint value is pretty small (less than millions) but there are too many (like millions) items
Then DP algorithms are possible.
Simplest case is the unbounded knapsack problem when there is no upper bound on the number of copies of each kind of item. This wikipedia article contains a good description how to simplify the problem: Dominance relations in the UKP and how to solve it: Unbounded knapsack problem.
More difficult is the 0-1 knapsack problem when you can pack each kind of item only zero times or one time. And the bounded knapsack problem, allowing to pack each kind of item up to some integer limit times is even more difficult. Internet offers lots of implementations for these problems, there are several suggestions in the same article. But I don't know which one is good or bad.

Resources