=========================================================================== CSC 373H / L0101 Lecture Summary for Week 4 Winter 2005 =========================================================================== Matrix Chain Product (continued): - Algorithm computes minimum cost but does not give order of multiplications to achieve this cost. - Possibility: working from N[0,n-1], recompute all possibilities considered in order to find breakpoint k that yielded best value; then recursively do the same for each subproblem. This requires additional Theta(n^3) time. - Trick: use a second array B[i,j] to store best value of k used to compute N[i,j]. At the end, B[0,n-1] = index of last multiplication to perform, and we can recursively print each subproduct. MatrixChain(d, n): for i := n-1 downto 0: N[i,i] := 0; B[i,i] := i for j := i+1 to n-1: N[i,j] := oo; B[i,j] := -1 for k := i+1 to j: try := d[i]*d[k]*d[j+1] + N[i,k-1] + N[k,j] if try < N[i,j]: N[i,j] := try; B[i,j] := k parenthesize(B, 0, n-1) For example with dimensions 2 3 5 1 8: i = 3: i = 2: 0 1 2 3 0 1 2 3 0 0 1 1 2 2 2 3 3 3 3 3 i = 1: i = 0: 0 1 2 3 0 1 2 3 0 0 0 1 1 3 1 1 2 3 1 1 2 3 2 2 3 2 2 3 3 3 3 3 Subroutine to print the final answer recursively. // print best way to compute A_i...A_j parenthesize(B, i, j): if i = j: print "A_i" else: print "(" parenthesize(B, i, B[i,j]-1) print ") (" parenthesize(B, B[i,j], j) print ")" For example above: ((A_0) ((A_1) (A_2))) (A_3) Dynamic Programming Paradigm: - For optimization problems that satisfy the following properties: . "simple subproblems": subproblems can be characterized precisely using a constant number of parameters (usually numerical indices); . "subproblem optimality": an optimal solution to the problem can be obtained from optimal solutions to subproblems; . "subproblem overlap": smaller subproblems are repeated many times as part of larger problems. - Step 0: Describe recursive structure of problem: how problem can be decomposed into simple subproblems and how global optimal solution relates to optimal solutions to these subproblems. - Step 1: Define an array indexed by the parameters that define subproblems, to store the optimal value for each subproblem (make sure one of the "sub"problems actually equals the whole problem). - Step 2: Based on the recursive structure of the problem, describe a recurrence relation satisfied by the array values from step 1 (including degenerate or base cases). - Step 3: Write iterative algorithm to compute values in the array, in a bottom-up fashion, following recurrence from step 2. - Step 4: Use computed array values to figure out actual solution that achieves best value (generally, describe how to modify algorithm from step 3 to be able to find answer). Interval scheduling with profits: [5.1] - Just like activity (interval) scheduling but each activity (interval) has a "profit" and we want schedule with maximum profit. More precisely: Input: Activities (s_1,f_1,w_1), ..., (s_n,f_n,w_n) where s_i = start time, f_i = finish time, w_i = profit. Output: Subset of activities S subset {1,2,...,n} such that no activities in S overlap and profit(S) is maximal. - Greedy doesn't work, no matter how we sort. - Step 0: Subproblems consist of subsets of activities to choose from, but no easy way to characterize these using constant number of parameters. Trick: Sort activities by finish time, as before (f_1 <= ... <= f_n). Consider optimal schedule S, and last activity scheduled in S, say k. Then k must be larger than index of all other jobs scheduled (because of sorting order) and rest of schedule must consist of optimal way to schedule the other activities. - Step 1: Define array A[i] = max profit from scheduling activities 1,2,...,i - Step 2: Write recurrence A[0] = 0 A[i] = max( A[i-1], w_i + A[j] ) where j = largest index <= i-1 such that f_j <= s_i because either job i is not required to get an optimal schedule (in which case A[i] = A[i-1]) or job i is required (in which case A[i] = w_i + A[j]). - Step 3: Bottom-up algorithm A[0] := 0 for i := 1 to n: A[i] := A[i-1] find largest j <= i-1 such that f_j <= s_i if j > 0 and A[i] < w_i + A[j]: A[i] := w_i + A[j] - Step 4: Compute optimal answer S := {} i := n while i > 0: if A[i] = A[i-1]: // don't schedule job i i := i - 1 else: // schedule job i S := S U {i} i := largest j <= i-1 such that f_j <= s_i return S - Runtime? O(n^2) (for each job, may have to look at all previous ones to find j) -- if information about the values of j is precomputed, then only O(n). Scheduling jobs with deadlines, durations, and profits. - Input: Jobs (d_1,t_1,g_1), ..., (d_n,t_n,g_n), where d_i = deadline, t_i = duration, g_i = gain (profit) for job i. Output: A feasible schedule with maximum profit. "Schedule" = list of start times for each job, S(1),S(2),...,S(n), where S(i) = start time for job i (or -1 if job i not scheduled). "Feasible schedule": each job finishes by its deadline (i.e., S(i)+t_i <= d_i), no two jobs overlap. - Step 0: As before sort jobs by deadline (d_1 <= d_2 <= ... <= d_n). Consider optimal schedule S where k = largest job number scheduled and t = time by which all jobs finish. If job k is not scheduled last, then we can move job k to finish at time t (since d_k is largest deadline) and, if necessary, move other jobs earlier to make room. This gives optimal schedule where k is scheduled last. Moreover, rest of jobs must form optimal schedule of jobs from 1,...,k-1 that finish by time t-t_k (start time for job k). - Based on these observations, do steps 1-4.