reset("0","0","1000","780","Dynamic Programming")
# conf.lec
\macro(S1,S>)
\macro(S1b,S)
\macro(T1,T>)
\macro(T1b,T)
B2s.hide()
S! Dynamic programming is an algorithm design strategy especially
- useful for solving
- Optimization Problems>.
S!
B2- Optimization Problem:> "Find an object x satisfying certain
- constraints and such that Cost(x) is minimum over all x satisfying the
- constraints."
S! Here are some examples.
S!
B2:: Example 1:> Tailor's layout problem. Given a set of
- geometric shapes and a seminfinite roll of cloth of some given width
- W, place them onto the roll without overlap such that the length L
- that must be cut out of the roll to accomodate them should be minimized.
B2:: x = layout, Cost(x) = length L that must be cut from the roll.
B2:: Example 2:> Travelling salesman problem. Given a weighted
- graph in which each edge
- represents the time required to travel from vertex i to vertex j, find
- a permutation of the vertices such that if you travel in the graph
- according to the permutation, and return to the vertex you started
- with, the total distance covered is minimized.
B2:: x = permutation. Cost(x) = Length of entire path indicated by x.
S! We next define some commonly used terms.
S!
B2:: Feasible solutions:> All x satisfying the constraints.
B2: Optimal solutions:> That x satisfying the constraints and
- having minimum Cost(x)
S! Sometimes Optimization problems will be stated with a function
- Benefit(x) which is to be maximized. We could of course define
- and use our method here.
S:: Our next example, which we will study in detail requires us to
maximize benefit.
S!
B! Knapsack problem:> Given a set of n items
having values V[1,...,n] and weights W[1,...,n] respectively, and a
knapsack having capacity C, find a subset of items whose total
weight is at most C and whose total value is maximum over all
possible subsets.
B:: x = subset of {1,...,n}. Benefit(x) = value of items in x.
S! At first glance you might think this problem is easy. "Simply
select items in increasing order of value"...
B2! Algorithm attempt 1:>
- Sort items in non-increasing order of value. Renumber in this order.
- S = {}, w=0
- for i=1 to n
\sp if w + W[i] \le C then w = w+ W[i], S = {i} U S.
end
- Return S
S!
S! Here is an instance for which this doesnt work.
B2- Instance: (10, [10,9,8], [10,5,5])
S! Here capacity C=10, item values V=[10,9,8] and item weights
W=[10,5,5].
S:: The
algorithm will return the subset S={1} of value 10, while the subset
{2,3} has value 17 also achieved without exceeding capacity.
S! Another idea might be to sort in non-increasing order of V[i]/W[i].
You should be able to construct examples for which doesnt work either.
S! It is a good idea to think of such strategies, however it should
be kept in mind that ultimately it is necessary to prove that the
strategy will indeed give us the right answer, and not miss out some
good solution.
S:: So our first goal ought to be:
B2:: Goal 1: Correctness > Ensure that we report the solution which is the
best among all feasible solutions.
S! If we actually generate every feasible solution and pick the best,
our algorithm will be very slow, though correct.
S:: This is because there could be far too many feasible solutions.
S! For example, if C is very large, then most subsets could be
included in the knapsack.
S:: Even if we assume that all subsets with 3n/4 items can be included,
there still are essentially 2n> such subsets.
S:: We would like to spend much less time. So there is another goal too.
B2: Goal 2: Speed > Algorithm should run fast. We should not
ACTUALLY construct all feasible solutions.
S! It might seem intriguing how we can devise
rule out feasible solutions without actually constructing them.
S! Here is the outline of the lecture.
B2! Outline>
- Recursive algrorithm for searching: This will ACTUALLY examine
every feasible solution.
- Improvements: key dynamic programming idea: This will have the
effect of examining every feasible solution, but with much less work.
- Equivalent Non-recursive algorithm
S!
\scene(Outline)
S! We define a commonly used term first
B2! Search space:> set of all the feasible solutions.
S! Clearly our goal is to find the best solution in the search space.
S:: A natural way to organize this search is to partition the search
space into subspaces, find the best solution in each subspace, and
then take the best of those.
B2:: Approach:> partition the search
space into subspaces, find the best solution in each subspace, and
then take the best of those.
S:: Our hope is to device a recursive algorithm: presumably we
recurse on the subspaces.
S! We now try this approach for our knapsack problem.
B:: S = space of feasible solutions to instance (C,V[1..n],W[1..n]).
S! Perhaps the simplest way to partition this space is as follows.
B:: \S1 : Set of those elements of S in which item 1 is present.
\S1b : Set of those elements of S in which item 1 is absent.
S! Clearly, if we search both these spaces, take the best of the
solution found in each, we will have found the best solution overall.
S! Is there some property of \S1 and \S1b that can be help in
searching them?
S! Let us consider \S1b first. Clearly, every element in \S1b must
satisfy the following.
B2:: \claim Let x be any element in \S1b. Then x is a subset of
items 2,...,n having total weight at most C.
B2: \proof Item 1 is not allowed by the definition of \S1b. Since x
also belongs to S, it must have total weight at most C.
S! So the first observation is:
B2:: Every element in \S1b must be a feasible solution to a
knapsack instance (C,V[2..n],W[2..n])
S! But the converse is also true.
B2: Every feasible solution to the instance (C,V[2..n],W[2..n]) must belong to \S1b.
S! The proof is obvious.
B2:: \S1b is exactly the set of solutions to instance (C,V[2..n],W[2..n])
S! Thus the highest value element in \S1b must be an optimal
solution of the instance (C,V[2..n],W[2..n])
B:: Max value element in \S1b =
\sp optimal solution of (C,V[2..n],W[2..n]).
S! Thus \S1b can be searched using recursion.
S! Something similar happens for \S1.
B: Max value element in \S1 =
\sp {1} U optimal solution to (C-W[1],V[2..n],W[2..n]).
S! We will prove this, but note first that this implies that \S1 can
also be searched using recursion.
B2:: Let \T1 = set of solutions to instance (C-W[1],V[2..n],W[2..n]).
B2: For every x \in \S1 let f(x) denote the set y obtained after
removing item 1 from x.
B2:: \claim For any x \in \S1, y=f(x) \in \T1.
B2: \proof By definition of \S1, x must contain item 1. The rest of
the items y=f(x) in x must certainly be a subset of 2..n. Since x also
belongs to S, its total weight must be at most C. Of this, item 1
has weight W[1]. Hence the rest of the items, i.e. those in set y
must contribute at most C - W[1]. Thus y must be feasible for
(C-W[1],V[2..n],W[2..n]), and hence must be in \T1.
S! The converse is again true.
B2: \claim For y \in \T1, x = f-1>(y) \in \S1.
S! Here f-1>(y) is the set obtained by adding item 1 to y.
S! The proof is straightforward.
S! Thus we have a 1-1 correspondence.
B2: f defines a 1-1 correspondence from \S1 to \T1
S! Further x, f(x) only differ in item 1. Thus..
B2:: Total value of x = V[1] + total value of y=f(x)
S! So, to maximize the value of x, it suffices to maximize the value
of y and add element 1 to it.
S:: Thus we have proved-
B.H(maxsblue,yellow)
B.U(maxsblue)
S! So in summary we can write:
B:: Optimal solution to (C,V[1..n],W[1..n]) =
B: Most valuable( Optimal solution to (C,V[2..n],W[2..n]),
\sp \sp {1} U Optimal solution to
(C-W[1],V[2..n],W[2..n])).
S! We are almost ready to write down the algorithm, but we need a
slight generalization.
S! How would this change if we were trying to find a solution to the
instance (c,V[i..n],W[i..n])?
S:: It should be clear that the equation for this case would be:
B:: Optimal solution to (c,V[i..n],W[i..n]) =
Most valuable( Optimal solution to (c,V[i+1..n],W[i+1..n]),
\sp \sp {i} U Optimal solution to
(c-W[i],V[i+1..n],W[i+1..n])).
S:: We are now ready to write an algorithm.
B2! KS(c,i) : will return the subset of maximum value for the instance
(c,V[i..n],W[i..n]). The arrays V, W and their length n are global.
The initial call is KS(C,1).
\scene(Algorithm)
B2:: The subset will be
represented suitably, say as a linked list.
B2:: KS(c,i){
S! First the base case.
B2: if i > n then return {}
S! We next compute..
B.H(opt1,yellow)
B2:: best1 = KS(c,i+1)
S! As per the definition of KS,
B2.H(KSdef,red)
S- KS(c,i+1) will indeed return the solution for the instance
(c,V[i+1..n],W[i+1..n]).
B2.U(KSdef)
B.U(opt1)
S! We next compute..
B.H(opt2,green)
S! In this, the instance is properly defined only if c - W[i] \ge 0.
Else, the corresponding subspace \S1 would be empty. So in that
case we should simply return the best solution best1.
B2:: if C < W[i] then return best1
B2:
best2 = { i } \union KS(c-W[i],V[i+1..n],W[i+1..n])
B.U(opt2)
S! Now we simply need to compare which of the two sets, best1 and best2
has more value. For this we will assume that we are given a
procedure valueofset(set-of-indices) picks out the
corresponding elements of the array V, and returns their sum.
Writing this procedure is an easy exercise for you.
S!
B2:: if valueofset(best1) > valueofset(best2)
then return best1
else return best2
}
S! At this point we have finished the first part: we now have a
recursive algorithm for solving the knapsack problem.
S:: You should be able to argue correctness by induction on n-i.
S! This algorithm is not efficient: as you can show, it will
potentially recurse an exponential number of times in n.
S: So we now try to optimize it. The key to this optimization is the
main dynamic programming idea: check if the recursive calls made by
the algorithm overlap.
S! Here is now the key observation:
B2:: \claim There can be only (C+1) \cdot (n+1) distinct calls to the
procedure KS starting with the call KS(C,1), for integer C.
B2:: \proof The first argument must always decrease but never go below
0. The second argument can go up to n+1, but not higher than that.
S! Note that the value computed by KS is fixed once we fix the
arguments to it, since it does not modify> any global variables.
S:: In other words, if KS is called more than (C+1) \cdot (n+1) times,
some of the calls are with the same arguments and work is being repeated.
S! So the obvious question is:
S- Why not store the result of each call in a table, and use it
subsequently instead of repeating the call?>
S:: So we build a table.
B! Table for storing results from KS>
\scene(Memoization)
S! In the table we could store the entire set returned by KS, however,
to keep the subsequent discussion simple, we will only store the
total value of the set returned by KS.
B- T[c,i] : total value of the set returned by KS(c,i),
\sp for
0 \le c \le C, 1 \le i \le n. Initially set to null.
S! We will write a new procedure KS2 which will use the table before
making recursive calls. However, it will also differ from
procedure KS in that it will not return the the optimal set but the
value of the optimal set.
S:: Later on it will become clear that with a slight modification, you
will be able to recover the optimal set as well. This is left for
the exercises.
S!
B:: KS2(c,i){
if i > n then return 0
S! Remember that now we are returning the value of the set rather than
the set itself -- so instead of returning the empty set, we are
returning the associated value, 0.
S! Next is the first recursive call. Instead of directly recursing we
check the table and recurse.
B-
if T[c,i+1] = null
\sp T[c,i+1] = KS2(c,i+1)
vbest1 = T[c,i+1]
S! The next part is the same until we again encounter recursion.
B:
if c < W[i] then return vbest1
S! Again we check and recurse only if needed.
B-
if T[c-W[i],i+1] = null
\sp T[c-W[i],i+1] = KS2(c-W[i],V[i+1..n],W[i+1..n])
vbest2 = V[i] + T[c-W[i],i+1]
S! Notice that since we are returning values of the set, we add in the
value of element i, rather than include element i into the set as in KS.
S! We return the maximum of best1 or best2
B:: return max(vbest1, vbest2)
}
S! It should be clear to you that KS2(c,i) will compute exactly the total
value the set that would have been returned by a call KS(c,i)..
S:: The table will potentially make KS2 run faster -- we will now
quantify this.
S! The standard idea for analysing recursive algorithms is to write
down a recurrence. This will give a good estimate for the execution
time of KS. However, for KS2, we can do something sharper.
S! The first observation comes from the claim-
B2.H(memo,yellow)
B2:: \claim As a part of the call KS2(C,1), there will
be only (C+1) \cdot (n+1) calls made recursively.
S! This is because using the table KS2 explicitly checks before making
a call again with the same arguments.
B2.U(memo)
S! This claim will help us in analysing the total time taken.
S! One idea is to estimate how much time any single call takes.
S:: This is not easy to do because the calls are nested> inside
one another.
S:: However, here is a simple idea which will make the counting easy.
S! Imagine that each table entry T[c,i] also has a counter attached to
it. This counter is purely meant for helping us in the analysis --
in actual runs of the algorithm, no such counters will be present.
S:: Call this Counter(c,i).
S:: Suppose everytime an instruction executes, it increments the value
of the counter associated with current value of c and i.
B2:: \claim Counter(c,i) will get incremented to
at most T where T is the maximum number of instructions required to execute
the non-recursive portion of the procedure KS2.
S! Thus T is the time taken for everything show in yellow-
B.H("ks2top,ks2mid,ks2bot",yellow)
S:: The time might be different depending upon how the "if" conditions turn out.
S:: T is simply the maximum over all possible such executions.
S! The key question for the proof is why are the recursive calls
excluded.
B2: \proof Instructions executed in the recursive call KS2(c,i+1) will
increment Counter(c,i+1), or some other counter depending upon
further recursive calls.
B2- Similarly the instructions executed as
part of KS2(c-W[i],i+1) will not affect Counter(c,i).
B2:: Note further
that because of memoization, KS2 is called at most once for any
single value of the pair (c,i). Thus Counter(c,i) will increment
just once for every instruction shown in yellow. Thus each counter
will reach a maximum value of T.
S! Now we have an upper bound on the total time.
B2:: \claim The call KS2(C,1) requires at
most (C+1) \cdot (n+1) \cdot T time.
S! All that remains now is to estimate T. Note that the yellow region
does not have any loops, and nothing that depends on the problem
size n.
S:: Thus T = O(1).
S:: Total time will thus be O(1) \cdot (C+1) \cdot (n+1) = O(Cn).
B.U("ks2top,ks2mid,ks2bot")
B:: Time for call KS2(C,1) = O(Cn).
S! We have accomplished our main goal. This time will typically be
much less than 2n>, which is what would be needed to actually
examine every feasible solution.
S! It will be shown in the exercises that the optimal set can also be
obtained in the same amount of time.
S! The final topic is to observe that all this can be done without any recursion at all.
B2! A non-recursive implementation>
S! The important observation is that our procedure KS2 clearly gives
us the relationships between the different entries of the table.
S! From this part of the code-
B.H(eq1,yellow)
S- it follows that-
B2- if c < W[i]: T[c,i] = T[c,i+1]
B.U(eq1)
S! Otherwise, T[c,i] is the larger of vbest1 and
vbest2, which are in turn T[c,i] and T[c-W[i],i+1].
B2: if c \ge W[i]: T[c,i] = max(T[c,i+1], V[i] + T[c-W[i],i+1])
S! The base case of the recursion, i > n returns 0. Thus we might write--
B2:: for all n: T[c, n+1] = 0
S! Notice that this is consistent with the interpretation that
T[c,n+1] is the value of the set returned by the call
KS(c,n+1) -- which indeed returns the null set having value 0.
S! So all we need to do is to solve this recurrence.
S:: It will be seen that all table entries can be calculated in
decreasing order of the second index as per the following program:
B2:: Program:
for c=1 to C
\sp T[c,n+1] = 0
for i=n to 1
\sp for c=1 to C
\sp \sp if c < W[i] then T[c,i] = T[c,i+1]
\sp \sp else T[c,i] = max(T[c,i+1], V[i] + T[c-W[i],i+1])
S! It should be clear that T as calculated above will satisfy the
recurrence.
S:: T[C,1] gives the optimal value for our original instance
(C,V[1..n],W[1..n]).
B2: return T[C,1]
S! The time taken is seen to be O(Cn) exactly the same as the
recursive version. However, this version might be considered to be
more direct.
B2:: Time = O(Cn)
S!
B!
B2!
S! We finally summarize the dynamic programming approach and highlight
its main features.
B! How to design a dynamic programming algorithm for an instance X
of a problem P:>
- Clearly define the space S of feasible solutions, and the
objective function, i.e. what is to be maximized on minimized.
- Partition S into subspaces S1>,...,Sk>. This is
typically done by considering all possible ways (k) in which the
candidate solutions can start.
- Design an algorithm to find an optimal solution si> in
each Si>. This should mainly involve solving
smaller size instances Xi>' of problem P.
- Using the algorithms designed above write a procedure which
will
- Find si> for i=1..k
- Return s = most optimal of all si>.
- Check if the (recursive) procedure defined above will make
several calls with the same arguments, if so store previously
computed results in a table. This is often called MEMOIZATION.
- ALTERNATE PROGRAM: Identify a recurrence relating the table
entries. Write a program to solve the
recurrence.
\scene(Summary of Dynamic Programming)
B2! Exercises:>
- Show that procedure KS will recurse at least as many times as
the number of feasible solutions.
- Modify the basic recursive procedure given, KS, so that it
prints the cost of every feasible solution in the search space.
Hint: add an extra argument giving the value accumulated in the first
i-1 elements.
- Show how to modify KS2 to compute the optimal set, and not just
the value of the optimal set. Hint: Check whether T[c,i] is equal to
T[c,i+1] or not. This will tell you whether element i should be in
the set, and also what the subsequent elements in the set will be.
\scene(End)