AT70.02 - Data Structures and Algorithms Lecturer: Jittat Fakcharoenphol Nov 10, 2004 Scribe: Long X. Nguyen Lecture 13A: Dynamic Programming This lecture introduces the dynamic programming. We examine three problems with dynamic programming: Fibonacci numbers, 0-1 Knapsack, and longest common subsequence. We have learnt about divide-and-conquer approach to design the algorithm. In this approach, the function is called recursively to compute the solution. Unlike the divide-and-conquer approach, the dynamic programming proposes the bottomup approach. Dynamic programming is an optimization procedure that is particularly applicable to problems requiring a sequence of interrelated decisions [1]. The art of dynamic programming is how to discover the recursive relationship and define the sub-problem. There are two key points of dynamic programming: (1) Optimal substructure: An optimal solution to a problem (instance) contains optimal solutions to sub-problem. (2) Overlapping subproblems: A recursive solution contains a “small” number of distinct subproblems repeated many times. 1. Fibonacci Numbers The Fibonacci numbers are defined by the following recurrence: F(0) = 0 F(1) = 1 F(n) = F(n-1) + F(n-2), where n > 1 Each Fibonacci number is the sum of the two previous ones, yielding the sequence 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, … Problem: given n, compute the Fibonacci number F(n)? 1.A. Algorithm by Divide-and-Conquer approach The algorithm is designed by divide-and-conquer approach as the following: Fibonacci(n) if n = 0 return 0 if n = 1 return 1 else return (Fibonacci(n-1) + Fibonacci(n-2)) The behavior of this algorithm is illustrated in the figure Recursive calls F(n) F(n-1) F(n-2) Re-use computed results F(n) F(n-2) F(n-1) F(n-2) F(4) F(3) Computed Repeatly F(4) F(3) F(2) F(2) F(2) 1 0 F(1) 1 1 0 F(0) (1a) (1b) Figure 1 – (1a) Divide-and-Conquer and (1b) Dynamic Programming This algorithm computes some results that it has in different branches. Hence, it save a lot of resource if it could re-use the computed results of some other branches. The running time of algorithm is O(2n). 1.B. Algorithm by Dynamic Programming Dynamic Programming provides another solution that utilizes the computed result. To compute F(n), it computes F(0), F(1), F(2), …, F(n-1), to F(n). Hence, the computed results F(n-2), F(n-1) is used to compute F(n). Fibonacci(n) f[0] 0 f[1] 1 for i = 2 to n do f[i] = f[i-1] + f[i-2] return f(n) The behavior of the algorithm is illustrated in the figure (1b). Another example of dynamic programming: Given n, m, computer P(n,m) according to the formula P(n, m) = P(n-1, m) + P(n, m-1), P(0, 0) = 1 P(n, m) = 0 if n, m < 0 if n, m ≥ 0 The behavior of dynamic programming on this problem is illustrated in the following figure: 1 4 (0, 3) 1 (1, 3) 3 (0, 2) 1 6 2 1 1 (0, 0) (2, 2) (3, 2) 4 (2, 1) 1 (1, 0) (3, 3) 10 3 (1, 1) 20 (2, 3) (1, 2) (0, 1) Bottom-Up approach 10 (3, 1) 1 (2, 0) (3, 0) Figure 2 – Illustrate of Bottom-Up approach 2. 0-1 Knapsack The 0-1 Knapsack problem is posed as follows [2]: A thief robbing a store finds n items; the i-th item is worth ci dollars and weights wi pounds (or kilograms), where ci and wi are integers. He wants to take as valuable a load as possible, but he can carry at most W pounds in his knapsack for some integer W. Which item he should take? The problem is re-stated formally: given some set N of n items, where the i-th item is worth ci dollars and weights wi pounds. Find the subset S of n items to maximize c iS i such that w W . iS i We have to make decision that pick an item or drop it. Suppose that W is a small integer. The 0-1 Knapsack problem could be decomposed into O(NW) number of subproblems. ({wi, ci}i=1n, W) = cn + MAX({wi, ci}i=1n-1, W-wn) = ({wi, ci}i=1n-1, W) if pick (wn, cn), otherwise Example of 0-1 Knapsack problem: W = 4 6 4 ci 3 2 wi 4 2 Compute the table bottom-up 4 3 2 1 0 Weight/ {N items} 0 0 0 0 0 6 6 4 0 0 6 6 4 0 0 8 6 4 0 0 0 1 2 3 Each cell in the above table represent the value V(i, j) that is the maximum total value if we can pick i objects from the set and put them in the knapsack of weight j. V(i, j) = MAX {ci + V(i -1, j – wi) if wi ≤ j; V(i-1, j)} V(i, j) = 0 if i = 0 or j = 0. Example of 0-1 Knapsack problem with W = 10 3 4 2 ci 2 3 5 wi The best value is 20, computed in the following figure: 10 1 3 4 Total weight 10 0 10 14 17 20 20 9 0 10 14 17 17 17 8 0 10 14 17 17 17 7 0 10 14 17 17 17 6 0 10 14 17 17 17 5 0 10 14 14 14 14 4 0 10 14 14 14 14 3 0 10 13 13 13 13 2 0 10 10 10 10 10 1 0 10 10 10 10 10 0 0 0 0 0 0 0 0 1 2 3 4 5 Bag of size i Figure 3 – 0-1 Knapsack computation 3. Longest Common Subsequence A subsequence of a string S is a string that can be obtained by deleting some character from S. Given two strings x[1…m] and y[1…n], the problem is to find a string u which is a subsequence of both x and y of longest length. For example: x= A G A T C A G G y= G C A T G A G LCS(x,y) = G-A-T-A-G Simplification (1) Look at the length of a longest-common subsequence (2) Extend the algorithm to find the LCS itself. Consider the prefixes of x and y. Define c[i, j] = |LCS(x[1..i], y[1..j])| Then c[m, n] = |LCS(x, y)| The idea of the algorithm is to match the subsequence of the prefix of x with that of y. Hence, there are two cases. c[i 1, j 1] 1, ifx[i ] y[ j ] c[i, j ] max{c[i 1, j ], c[i, j 1]}, otherwise Procedure LCS-LENGTH takes two sequences X = x1, x2, ..., xm and Y = y1, y2, ..., yn as inputs. It stores the c[i, j] values in a table c[0… m, 0…n] whose entries are computed in row-major order. (That is, the first row of c is filled in from left to right, then the second row, and so on.) It also maintains the table b[1…m, 1…n] to simplify construction of an optimal solution. Intuitively, b[i, j] points to the table entry corresponding to the optimal subproblem solution chosen when computing c[i, j]. The procedure returns the b and c tables; c[m, n] contains the length of an LCS of X and Y. LCS-LENGTH(x, y) m length[x] n length[y] for i 1 to m do c[i, 0] 0 for j 1 to n do c[0, j] 0 for i 1 to m do for j 1 to n do if (x[i] = y[j]) then c[i, j] c[i-1, j-1] + 1 b[i, j] “*” else if c[i-1, j] ≥ c[i, j-1] then c[i, j] c[i-1, j] b[i, j] “” else c[i, j] c[i, j-1] b[i, j] “” return c and b The figure shows how the algorithm compute the table by dynamic programming. G 1 2 3 3 4 4 5 G 1 2 3 3 4 4 5 A 1 2 3 3 3 4 4 C 1 2 2 3 3 3 3 T 1 1 2 3 3 3 3 A 1 1 2 2 2 3 3 G 1 1 1 1 2 2 2 A 0 0 1 1 1 1 1 G C A T G A G Figure 4 – Longest Common Subsequence References [1] [2] S. E. Dreyfus and A. M. Law, The art and theory of dynamic programming: Academic Press, 1977. T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms, 2nd edition ed: MIT Press, 2001.