Contest Algorithms January 2016 Introduce DP; Look at several examples; Compare DP to Greedy 11. Dynamic Programming (DP) 1Contest Algorithms: 11. DP
1. DP Features 2. The fibonacci series 3. Binomial coefs knapsack 5. Longest Common Subsequence 6. Edit distance 7. Compare DP to Greedy using fractional knapsack DP Topics Contest Algorithms: 11. DP2
An optimal (best) solution to the problem is a composition of optimal (best) subproblem solutions makes the code recursive (perhaps) The same subproblems appear many times while solving the problem use tabling / memoziation to 'remember' answers perhaps calculate subproblems first; called bottom-up evaluation The current best solution choice may change solutions choices made earlier. 1. DP Features
Series defined by fib 0 = 0 fib 1 = 1 fib n = fib n-1 + fib n-2 Recursive algorithm: Running Time? O(2 n ) 2. Fibonacci Series 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, … public static int fib(int n) { if (n < 2) return n; else return fib(n-1) + fib(n-2); } see FibMemo.java
Fibonacci can be a DP problem: the solution (fib(n)) is a combination of sub-solutions fib(n-1) and fib(n-2)) There are many repeated subproblems 2 n subproblems, but only n are different 5
Running time is linear = O(n) Requires extra space for the fibs[] table = O(n) Memoization + top-down fib() private static long fibs = new long[MAX+1]; // in main() fibs[0] = 0; fibs[1] = 1; public static long fib(int n) { if (n < 2) return n; else { if (fibs[n] == 0) fibs[n] = fib(n−1) + fib(n−2) return fibs[n]; } } : fibs[] MAX :
10th Fib: 55 Number of fib() calls: 19 10th Fib: 55 Number of fib() calls: 1 11th Fib: 89 Number of fib() calls: 3 11th Fib: 89 Number of fib() calls: 1 Speed-up Contest Algorithms: 11. DP7
Bottom-up Fib int fib(int n) { if (n == 0) return 0; else { // deal with 1, 1, 2,... int prev = 0; int curr = 1; int temp; for (int i=1; i < n; i++) { temp = prev + curr; prev = curr; curr = temp; } return curr; } Running time = O(n) Space requirement is 5 variables = O(1) ! tempprevcurr F(k-1) F(k-2)F(k-1) tempprevcurr F(k-1)F(k) +
9 2. Binomial Coefficients How many ways are there to choose k things out of n? Used to calculate the coefficients of of (a+b) n
10 It’s difficult to calculate the value of binomial coefficients using that equation: arithmetic overflow will happen for n > 12! Use Pascal’s triangle instead: n == 0 n == 1 start counting k = 0 from left :::: a table of subproblems that are used repeatedly. A DP problem
11 Binomial Coefficients private static final int MAX = 100; // return n choose m static long binCoef(int n, int m) { long[][] bc = new long[MAX][MAX}; //table of binomial coefs for(int i = 0; i <= n; i++) bc[i][0] = 1; // init upper edges for(int j = 0; j <= n; j++) bc[j][j] = 1; for(int i = 1; i <= n; i++) // fill inside triangle for(int j = 1; j < i; j++) bc[i][j] = bc[i-1][j-1] + bc[i-1][j]; return bc[n][m]; } see Binomial.java
1 1 item item item item item 4 11 maximize cost, but total weight ≤ Knapsack Problem Crucial idea: total weight must be one of 12 values: 0-11 indivisible; use or not use a weight
1 1 item item item 2 Maximize cost with at most 3 items: w = c = Try to add item 3: 6 22 item w = c =
Items 0, 1,... n have weights w 0, w 1,...w n and costs c 0, c 1,...c n. All values are positive integers. W is the maximum capcity of the knapsack. Define m[i,w] to be the maximum total cost with weight ≤ w using items 0 to i. Mathematically Contest Algorithms14
m[0,w] = c 0 if w 0 ≤ w m[i,w] = m[i-1,w], if w i > w the i item weighs more than the current weight limit m[i,w] = max( m[i-1,w], m[i-1, w-w i ]+c i ), if w i ≤ w The solution is m[n,W]. To do this efficiently we must use a table to store previous computations. Define m[i,w] recursively Contest Algorithms: 11. DP15 the maximum total cost with weight ≤ w using items 0 to i
Why use Dynamic Prog.? The current selection may change an earlier selection: 1 1 item item item 2 c == 25; w == 8 earlier selection 5 18 item item 3 c == 40; w == 11 current selection
public static void main(String[] args) { int W = 11; // knapsack capacity System.out.println("Knapsack capacity: " + W); // costs and weights for items int[] ci = { 1, 6, 18, 22, 28}; int[] wi = { 1, 2, 5, 6, 7}; int numItems = ci.length; for (int i=0; i < numItems; i++) System.out.println("Item " + i + ": weight = " + wi[i] + ", cost = " + ci[i]); System.out.println(); // totCosts[i, w] stores the maximum total cost // of some items in {0,1,...,i} of combined weight <= w int[][] totCosts = new int[numItems][W + 1]; // used[i, weight] is true when item i is part of the solution for weight boolean[][] used = new boolean[numItems][W + 1]; // all false by default : Code Contest Algorithms17 see Knapsack0l.java m[][] has become totCosts[][]
// compute maximum cost for first item for (int w = 0; w <= W; w++) { if (wi[0] <= w) { totCosts[0][w] = wi[0]; used[0][w] = true; // means that item 0 can be used when weight is w } else totCosts[0][w] = 0; } // compute maximum cost for rest of items for (int i = 1; i < numItems; i++) { for (int w = 0; w <= W; w++) { // w == current weight limit if (wi[i] <= w) { // item within current weight limit int costWith_i = ci[i] + totCosts[i-1][w-wi[i]]; if (costWith_i > totCosts[i-1][w]) { // higher cost is better totCosts[i][w] = costWith_i; used[i][w] = true; } else // leave cost unchanged totCosts[i][w] = totCosts[i-1][w]; } else // item exceeds current weight limit; don't use totCosts[i][w] = totCosts[i-1][w]; } printTables(totCosts, used); itemsUsed(used, ci, wi); } // end of main() Contest Algorithms: 11. DP18
private static void itemsUsed(boolean[][] used, int[] ci, int[] wi) { System.out.println("Items used:"); int wCapacity = used[0].length-1; // start at maximum weight (W) int usedWeight = 0; int usedCost = 0; // check if i is part of the set of items weighing wCapacity, // if yes, print i info, and reduce wCapacity by item i's weight // and find the next item for this new capacity for (int i = used.length-1; i >= 0; i--) { if (used[i][wCapacity]) { System.out.println("Item " + i + ": weight = " + wi[i] + ", cost = " + ci[i]); usedWeight += wi[i]; usedCost += ci[i]; wCapacity = wCapacity - wi[i]; } System.out.println("Total weight: " + usedWeight + "; Total cost: " + usedCost); } // end of itemsUsed() Contest Algorithms: 11. DP19
Execution Contest Algorithms20
Items used: Weight Item 0: X X X X X X X X X X X Item 1: X X X X X X X X X X Item 2: X X X X X X X Item 3: X X X X X Item 4: X X X X Using used[][] Contest Algorithms21 W = 11 Item 3 used; weight == 6 W = 11 – 6 = 5 Item 2 used; weight == 5 W = 5 – 5 = 0 No item used; stop
4. Longest Common Subsequence (LCS) Given two sequences x[1.. m] and y[1.. n], find a longest subsequence common to them both. x:x:ABCBDAB y:y:BDCABA BCBA = LCS(x, y) or BDAB, BCAB
Check every subsequence of x[1.. m] to see if it is also a subsequence of y[1.. n]. Analysis Checking time for each subsequence is O(n). 2 m subsequences of x[] (can use or not use each element in x). Worst-case running time = O(n*2 m ), exponential time. Brute-force LCS Algorithm
Simplify the problem: 1.Find the length of a LCS 2. We'll extend the algorithm later to find the LCS. Towards a Better LCS Algorithm
If X = then A prefix is x[1.. 4] == we abbreviate this as x 4 Also x 0 is the empty sequence Prefixes
c[] is a table (2D array) for storing LCS lengths: c[i, j] = | LCS(x[1.. i], y[1.. j]) | | s | is the length of a sequence s Since x is of length m, and y is of length n, then c[m, n] = | LCS(x, y) | Creating a Table of Lengths
Since X 0 and Y 0 are empty strings, their LCS is always empty (i.e. c[0, 0] == 0) The LCS of an empty string and any other string is empty, so for every i and j: c[0, j] == c[i, 0] == 0 Calculating LCS Lengths
Initial c[]
The first line of this definition fills the top row and first column of c[] with 0's. Recursive Definition of c[]
When we calculate c[i, j], there are two cases: First case: x[i] == y[j]: one more symbol in strings X and Y matches, so the length of LCS X i and Y j equals the length of LCS of smaller strings X i-1 and Y i-1, plus 1
Second case: x[i] != y[j] As symbols don’t match, our solution is not improved, and the length of LCS(X i, Y j ) is the same as the biggest from before (i.e. max of LCS(X i, Y j-1 ) and LCS(X i-1,Y j )
Does the LCS() algorithm have many repeating (overlapping) subproblems? Consider the worst case execution x [ i ] ≠ y [ j ], in which case the algorithm evaluates two subproblems, each with only one parameter decremented Repeated Subproblems?
Height = m + n. The total work is exponential, but we’re repeating lots of subproblems. Recursion Tree (in worst cases)
LCS(x, y, i, j) if c[i, j] is empty then // calculate if not already in c[i, j] if i == 0 or j == 0 then c[i, j] ← 0 else if x[i] == y[ j] then c[i, j] ← LCS(x, y, i–1, j–1) + 1 else c[i, j] ← max( LCS(x, y, i–1, j), LCS(x, y, i, j–1) ) return c[i, j] Memoization Time = O(m*n) == constant work per table entry Space = O(m*n)
This algorithm works top-down start with large subsequences, and calculate the smaller subsequences Let's switch to bottom-up execution calculate the small subsequences first, then move to larger ones Bottom-up Execution
LCS-Length(X, Y) 1. m = length(X) // get the # of symbols in X 2. n = length(Y) // get the # of symbols in Y 3. for i = 1 to m c[i,0] = 0 // special case: Y 0 4. for j = 1 to n c[0,j] = 0 // special case: X 0 5. for i = 1 to m // for all X i 6. for j = 1 to n // for all Y j 7. if ( X i == Y j ) 8. c[i,j] = c[i-1,j-1] else c[i,j] = max( c[i-1,j], c[i,j-1] ) 10. return c LCS Length Bottom-up the same recursive definition of c[] as before
We’ll see how a bottom-up LCS works on: X = ABCB Y = BDCAB Bottom-up Examples LCS(X, Y) = BCB X = A B C B Y = B D C A B LCS-length(X, Y) = 3
LCS Example 1 j i Xi A B C B YjBBACD X = ABCB; m = |X| = 4 Y = BDCAB; n = |Y| = 5 Allocate array c[5,4] ABCB BDCAB
i Xi A B C B YjBBACD ABCB BDCAB for i = 1 to m c[i,0] = 0 for j = 1 to n c[0,j] = 0 j
i Xi A B C B YjBBACD ABCB BDCAB if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) j
i Xi A B C B YjBBACD ABCB BDCAB if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) j
i Xi A B C B YjBBACD ABCB BDCAB if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) j
i Xi A B C B YjBBACD ABCB BDCAB if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) j
i Xi A B C B YjBBACD ABCB BDCAB if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
i Xi A B C B YjBBACD if ( X i == Y j ) c[i,j] = c[i-1,j-1] + 1 else c[i,j] = max( c[i-1,j], c[i,j-1] ) ABCB BDCAB j
The bottom-up LCS algorithm calculates the values of each entry of the array c[m, n] So what is the running time? O(m*n) Since each c[i, j] is calculated in constant time, and there are m*n elements in the array Running Time
private static void lcs(String x, String y) { int[][] lengths = new int[x.length()+1][y.length()+1]; // filled with 0's by default for (int i = 1; i <= x.length(); i++) { for (int j = 1; j <= y.length(); j++) { if (x.charAt(i-1) == y.charAt(j-1)) // Java string index starts at 0, not 1 lengths[i][j] = lengths[i-1][j-1]+1; else lengths[i][j] = Math.max(lengths[i][j-1], lengths[i-1][j]); } System.out.println("LCS length: " + lengths[x.length()][y.length()]); System.out.println("LCS: " + getSubSeq(x, y, lengths)); } // end of lcs() Code Contest Algorithms: 11. DP54 see LCS.java c[][] has become lengths[][]
We have found the length of LCS. We want to modify this algorithm to have it calculate LCS of X and Y Each c[i, j] depends on c[i-1, j] and c[i, j-1] or c[i-1, j-1] For each c[i, j] we can trace back how it was calculated: Finding the LCS For example, here c[i, j] = c[i-1, j-1] +1 = 2+1=3
So we can start from c[m,n] (bottom right of c[]) and move backwards Whenever c[i,j] = c[i-1, j-1]+1, record x[i] as part of the LCS When i=0 or j=0, we have reached the beginning, so can stop. Remember that:
Finding LCS Example 1 j i Xi A B C YjBBACD B
j i Xi A B C YjBBACD B
j i Xi A B C YjBBACD B BCB LCS:
There may be several paths through the table, which represent different answers for LCS() LCS() = BDAB All of them have the LCS-length of 4 LCS() Offers Choices D C A B AABCBDB B A
private static String getSubSeq(String x, String y, int[][] lengths) { StringBuffer sb = new StringBuffer(); int i = x.length(); // start at bottom right int j = y.length(); while ((i != 0) && (j != 0)) { if (x.charAt(i-1) == y.charAt(j-1)) { // Java string index starts at 0, not 1 sb.append(x.charAt(i-1)); i--; j--; } else if (lengths[i][j-1] >= lengths[i][j]) j--; else i--; } return sb.reverse().toString(); } // end of getSubSeq() Code Contest Algorithms: 11. DP61 see LCS.java c[][] has become lengths[][]
Execution Contest Algorithms: 11. DP62
5. Edit Distance (Levenshtein distance) How to measure the difference between a pair of strings? Three natural types of edits Substitution : “shot” to “spot” Insertion : “ago” to “agao” Deletion : “hour” to “hor” There's also " match ", which isn't an edit. Edit distance : the minimum number of edits needed to transform one string into another assign a score of 1 to each type of change (this could be different) the edit distance is the minimum total score 63
Example The edit distance from TGCATAT to ATCCGAT is 4 In short hand, the edit path is "S S M S S M M" there are usually other possible edit paths of the same distance 64 TGCATAT substitute T -> A substitute G -> T substitute A -> C substitute T -> G match C's match A's match T's A GCATAT A T CATAT ATC C TAT ATCC G AT
Consider the last chars of strings S and T: If the characters are the same (they match ): recursively move onto finding the edit distance between the two strings left when this last char is deleted from both S and T Otherwise, make one of three changes: delete the last char of S insert the last char of T substitute the last char of S for the last char of T What is the edit distance from S (source) to T (target)? Contest Algorithms: 11. DP65
Prefixes and dist() function 66 The prefix substring of string S is s. The prefix substring of string T is t. The length of s is i, the length of t is j. The edit distance between s and t is dist(i, j) The edit distance for the complete strings is dist(S.size(), T.size()) Build a dist[][] table to store the dist(i, j) values
Calculating dist(i, j) 67 S T i j Consider character at location i in s (s i ) and location j in t (t j ). How is t j obtained? Three cases: 1.Match or substitution of s i sub-problem: dist (i-1, j-1) 2.Insertion sub-problem: dist (i, j-1) 3.Deletion sub-problem: dist (i-1, j) So, dist(i, j) = min { dist(i-1, j-1) + sub(i, j), dist(i, j-1) + 1, dist(i-1, j) +1 } int sub(char a, char b) { if (c == d) return 0; else return 1; }
The edit distance between an empty string and another string is the length of the second string. This corresponds to having to insert each letter for the transformation. Empty String Case Contest Algorithms: 11. DP68
Example: "keep" --> "hello" s="keep" and t="hello". To deal with empty strings, an extra row and column have been added to the chart below: An entry in this table simply holds the dist() value between the prefixes of the two strings. For example, the highlighted square indicates that the edit distance between the strings "he" and "keep" is 3. hello k e e p443334
Building the Table 1)Initialize values corresponding to the empty string base case. hello k1 e2 e3 p4
2)Loop through the table from the top left to the bottom right. In doing so, follow the recursive definition. If the characters match (e.g. "e" and "e"): hello k e22 e3 p4 Copy down the upper left value.
If the characters do not match e.g. "l" and "e": Contest Algorithms: 11. DP72 hello k e221 e3 p4 copy min ( 1+ above, 1+ left, 1+diag up)
public static int levDistance(String s, String t) // distance from s to t (source to target) { int m = s.length(); int n = t.length(); dist = new int[m+1][n+1]; // s is down row; t is across col for (int i = 0; i <= m; i++) dist[i][0] = i; for (int j = 0; j <= n; j++) dist[0][j] = j; // iterate though, and check last char for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { int sub = ((s.charAt(i-1) == t.charAt(j-1)) ? 0 : 1); // matching chars? dist[i][j] = min3( dist[i-1][j] + 1, // delete dist[i][j-1] + 1, // insert dist[i-1][j-1] + sub ); // match or sub } return dist[m][n]; } // end of levDistance() Code Contest Algorithms: 11. DP73
private static void printDists(String s, String t) { System.out.print("\n "); for (int j = 0; j < t.length(); j++) System.out.print(" " + t.charAt(j) + " "); System.out.println(); for (int i = 0; i < dist.length; i++) { if (i == 0) System.out.print(" : "); else System.out.printf(" " + s.charAt(i-1) + " : "); for (int j = 0; j < dist[0].length; j++) System.out.printf("%2d ", dist[i][j]); System.out.println(); } System.out.println(); } // end of printDists() Printing the dist[][] Table Contest Algorithms: 11. DP74
private static String buildPath(String s, String t) { // walk back through matrix to figure out path StringBuilder rPath = new StringBuilder(); int i = s.length(); int j = t.length(); while ((i != 0) && (j != 0)) { if (s.charAt(i-1) == t.charAt(j-1)) { // chars match rPath.append("M "); i--; j--; } else { // find previous position if (dist[i][j] == (dist[i-1][j-1] + 1)) { // substitute rPath.append("S "); i--; j--; } else if (dist[i][j] == (dist[i-1][j] + 1)) { // delete rPath.append("D "); i--; } else if (dist[i][j] == (dist[i][j-1] + 1)) { // insert rPath.append("I "); j--; } while (i != 0) { // j == 0, so must move up (i.e. delete) rPath.append("D "); i--; } while (j != 0) { // i == 0, so must move left (i.e. insert) rPath.append("I "); j--; } return rPath.reverse().toString(); } // end of buildPath() Building the Edit Path Contest Algorithms: 11. DP75
Execution Contest Algorithms: 11. DP76
Example 77 S: “thou shalt not” T: “you should not” D S M M M M M I S M S M M M M
More Examples Contest Algorithms: 11. DP78
DP Compared to Greedy DP features Again Optimal sub-structure: the best solution to the problem uses the best solutions to sub- problems use recursive code Overlapping (repeating) problems: the same subproblems appear several times while solving the problem use tabling / memoziation to 'remember' answer The current best solution choice may change solutions choices made earlier.
Greedy Features Optimal sub-structure: (same as DP) the best solution to the problem uses the best solutions to sub-problems use recursive code The current best solution choice is made using only current ('local') information greedy algorithms never change choices made earlier in the calculation makes "greedy" code easier to implement than DP
Previous Examples: Minimum Spanning Tree Algorithms – Kruskal’s and Prim’s Dijkstra’s Algorithm
Fractional Knapsack Problem Maximize the value of a knapsack that can hold at most W units worth of goods from a list of items I 1, I 2,... I n. Each item i has two attributes: 1)Value/unit == v i 2)Weight == w i
1)Sort the items into a list by value/unit. 2)Take as much of the most expensive item as possible, then move down the list. 3)You may end up taking a fractional portion of the last item. Fractional Knapsack Algorithm Contest Algorithms83
1 1 item item item item item 4 11 maximize cost, but total weight ≤ 11 Fractional Knapsack Problem uses a greedy algorithm Crucial idea: order by cost per unit weight divisible; can use parts of a weight
1 1 item item item item item item item item item item 4 cost/unit weight: 4 reorder by decreeasing cost/unit weight:
Maximize cost by adding weights (or parts) in decreasing cost/ unit weight: Max weight == 11 : 6 22 item item 4 Max cost ==7 * 44 * = =
No. of items, knapsack W Lines of item info; on each line: ci, wi for an item e.g Input Data Format Contest Algorithms87 see fkData.txt This is the example from the previous slides.
public static void main(String[] args) throws Exception { if (args.length != 1) { System.out.println("Usage: java FracKnapsack "); return; } Scanner sc = new Scanner(new File(args[0])); int numItems = sc.nextInt(); int maxW = sc.nextInt(); LinkedList items = new LinkedList (); for (int i = 0; i < numItems; i++) items.add( new KItem(sc.nextInt(), sc.nextInt()) ); Collections.sort(items); : Code Contest Algorithms88
int currWeight = 0; double currCost = 0; while ((currWeight < maxW) && (!items.isEmpty())) { int remWeight = maxW - currWeight; KItem item = items.poll(); if (item.weight <= remWeight) { // add all of the item currWeight += item.weight; currCost += item.cost; } else { // item.weight > remWeight // add a fraction of the item currCost += remWeight * item.costWeightRatio; currWeight += remWeight; } System.out.printf("%.3f", currCost); } // end of main() Contest Algorithms: 11. DP89
public class KItem implements Comparable { public int cost, weight; public double costWeightRatio; public KItem(int cost, int weight) { this.cost = cost; this.weight = weight; costWeightRatio = ((double) cost) / weight; } public int compareTo(KItem i) { double diff = costWeightRatio - i.costWeightRatio; if (diff > 0) return -1; else if (diff == 0) return 0; else return 1; } // end of compareTo() public String toString() { return "(cost: " + cost + ", weight: " + weight + ")"; } } // end of KItem class Contest Algorithms: 11. DP90
Why is it Greedy? The current selection does not affect the earlier selection: earlier selection c == ; w == 11 current selection 7 28 item 4 7 * 4 c = 28; w == item item 4 7 * 44 *