Dynamic Programming. Introduction, Weighted Interval Scheduling. Tyler Moore. Lecture 15

��������������������� Dynamic Programming Greedy. Build up a solution incrementally, myopically optimizing Introduction, Weighted Interval Scheduli...
Author: Edmund Hunter
���������������������

Dynamic Programming

Greedy. Build up a solution incrementally, myopically optimizing

Introduction, Weighted Interval Scheduling

some local criterion. Divide-and-conquer. Break up a problem into independent subproblems,

Tyler Moore

solve each subproblem, and combine solution to subproblems to form solution to original problem.

CSE 3353, SMU, Dallas, TX

Dynamic programming. Break up a problem into a series of overlapping

Lecture 15

subproblems, and build up solutions to larger and larger subproblems.

fancy name for caching away intermediate results in a table for later reuse

Some slides created by or adapted from Dr. Kevin Wayne. For more information see http://www.cs.princeton.edu/~wayne/kleinberg-tardos. Some code reused from Python Algorithms by Magnus Lie Hetland.

2

2 / 28

���������������������������

��������������������������������

Bellman. Pioneered the systematic study of dynamic programming in 1950s.

Areas.

�Bioinformatics. �Control theory. �Information theory. �Operations research. �Computer science: theory, graphics, AI, compilers, systems, …. �...

Etymology.

�Dynamic programming = planning over time. �Secretary of Defense was hostile to mathematical research. �Bellman sought an impressive name to avoid confrontation.

Some famous dynamic programming algorithms.

�Unix diff for comparing two files. �Viterbi for hidden Markov models. �De Boor for evaluating spline curves. �Smith-Waterman for genetic sequence alignment. �Bellman-Ford for shortest path routing in networks. �Cocke-Kasami-Younger for parsing context-free grammars. �...

3

4

3 / 28

4 / 28

Recurrence relations

Computing Fibonacci numbers

Recall that recurrence relations are equations deﬁned in terms of themselves. They are useful because many natural functions and recursive functions can easily expressed as recurrences

Fibonacci sequence can be deﬁned using the following recurrence: Fn = Fn−1 + Fn−2 , F0 = 0, F1 = 1

Recurrence

Solution

Example application

T (n) = T (n/2) + 1 T (n) = T (n/2) + n T (n) = 2T (n/2) + 1 T (n) = 2T (n/2) + n T (n) = T (n − 1) + 1 T (n) = T (n − 1) + n T (n) = 2T (n − 1) + 1 T (n) = 2T (n − 1) + n T (n) = nT (n − 1)

Θ(lg n) Θ(n) Θ(n) Θ(n lg n) Θ(n) Θ(n2 ) Θ(2n ) Θ(2n ) Θ(n!)

Binary Search Randomized Quickselect (avg. case) Tree traversal Mergesort Processing a sequence Handshake problem Towers of Hanoi

F 2 = F1 + F 0

=1+0=1

F 3 = F2 + F 1

=1+1=2

F4 = F3 + F2

=2+1=3

F5 = F4 + F3

=3+2=5

F6 = F5 + F4

=5+3=8

5 / 28

Computing Fibonacci numbers with recursion

6 / 28

Recursion Tree for Fibonacci function F (6) F (5)

F (4)

F (4) F (3)

def f i b ( i ) : i f i < 2: return i r e t u r n f i b ( i −1) + f i b ( i −2)

F (2) F (1)

F (1)

F (3) F (2)

F (1)

F (0)

F (2) F (1)

F (0)

F (3) F (1)

F (2) F (1)

F (2) F (1)

F (1)

F (0)

F (0)

F (0)

We know that T (n) = 2T (n − 1) + 1 = Θ(2n )

It turns out that T (n) = T (n − 1) + T (n − 2) ≈ Θ(1.6n )

Since our recursion tree has 0 and 1 as leaves, computing Fn requires ≈ 1.6n recursive function calls! 7 / 28

8 / 28

Computing Fibonacci numbers with memoization (manual)

Recursion tree for Fibonacci function with memoization F (6)

def fib memo ( i ) : mem = {} #d i c t o f c a c h e d v a l u e s def f i b ( x ) : i f x < 2: return x #c h e c k i f a l r e a d y computed i f x i n mem : r e t u r n mem[ x ] #o n l y i f n o t a l r e a d y computed mem[ x ] = f i b ( x −1) + f i b ( x −2) r e t u r n mem[ x ] fib ( i ) r e t u r n mem[ i ]

F (5)

F (4)

F (4) F (3) F (2) F (1)

F (3) F (2)

F (1)

F (1)

F (0)

F (2) F (1)

F (3) F (1)

F (0)

F (2) F (1)

F (2) F (1)

F (1)

F (0)

F (0)

F (0)

Black nodes: no longer computed due to memoization Caching reduced the # of operations from exponential to linear time!

9 / 28

Computing Fibonacci numbers with memoization (automatic)

10 / 28

Code for the memo wrapper from f u n c t o o l s import wr a ps def memo( f u n c ) : c a c h e = {} # Stored subproblem s o l u t i o n s @wraps ( f u n c ) # Make wrap l o o k l i k e f u n c def wrap ( ∗ a r g s ) : # The memoized w r a p p e r i f a r g s not i n c a c h e : # Not a l r e a d y computed ? c a c h e [ a r g s ] = f u n c ( ∗ a r g s ) # Compute & c a c h e t h e s o l u t i o n r e t u r n c a c h e [ a r g s ]# R e t u r n t h e c a c h e d s o l u t i o n r e t u r n wrap # Return the wrapper

>>> @memo . . . def f i b ( i ) : ... i f i < 2: return i ... r e t u r n f i b ( i −1) + f i b ( i −2) ... >>> f i b ( 1 0 0 ) 354224848179261915075 L

Example of Python’s capability as a functional language Provides cache functionality for recursive functions in general The memo function takes a function as input, then “wraps the function with the added functionality The @wraps statement makes the memo function a decorator

What sort of magic is going on here?

11 / 28

12 / 28

Discussion of dynamic memoization

Computing Fibonacci numbers with dynamic programming

Even if the code is a bit of a mystery, don’t worry, you can still use it by including the code on the last slide with yours, then making the ﬁrst line before your function deﬁnition decorated by ‘@memo’ If you don’t have access to a programming language supporting dynamic memoization, you can either do it manually or turn to dynamic programming Dynamic programming converts recursive code to an iterative version that executes eﬃciently

def f i b i t e r ( i ) : i f i < 2: return i #s t o r e t h e s e q u e n c e i n a l i s t mem= [ 0 , 1 ] f o r j i n range ( 2 , i +1): #i n c r e m e n t a l l y b u i l d t h e s e q u e n c e mem . append (mem[ j −1]+mem[ j −2]) r e t u r n mem[ −1]

13 / 28

14 / 28

Avoiding recomputation by storing partial results 6. D YNAMIC P ROGRAMMING I ‣ weighted interval scheduling

The trick to dynamic programming is to see that the naive recursive algorithm repeatedly computes the same subproblems over and over and over again. If so, storing the answers to them in a table instead of recomputing can lead to an eﬃcient algorithm.

‣ segmented least squares ‣ knapsack problem ‣ RNA secondary structure

Thus we must ﬁrst hunt for a correct recursive algorithm – later we can worry about speeding it up by using a results matrix. ���������������

15 / 28

16 / 28

����������������������������

����������������������������������

Weighted interval scheduling problem.

Earliest finish-time first.

� �Two jobs compatible if they don't overlap. �Goal: find maximum weight subset of mutually compatible jobs.

�Consider jobs in ascending order of finish time. �Add job to subset if it is compatible with previously chosen jobs.

Job � starts at ��, finishes at ��, and has weight or value ��.

Recall. Greedy algorithm is correct if all weights are 1.

a

Observation. Greedy algorithm fails spectacularly for weighted version. b c d e f

b

weight = 999

g a

weight = 1

h 0

1

2

3

4

5

6

7

8

9

h

���� 10

11

0

1

2

3

4

5

6

7

8

9

���� 10

11

6

7

17 / 28

18 / 28

����������������������������

�����������������������������������

Notation. Label jobs by finishing time: ����≤������≤�������≤����.

Notation. �������� value of optimal solution to the problem consisting of job requests ������������.

Def. ��������� largest index ������ such that job � is compatible with �. Ex. �����������������������������

Case 1. ��� selects job �.

�Collect profit ��. �Can't use incompatible jobs �����������������������������������. �Must include optimal solution to problem consisting of remaining

1

compatible jobs ����������������.

2

optimal substructure property (proof via exchange argument)

3

Case 2. ��� does not select job �.

�Must include optimal solution to problem consisting of remaining

4

compatible jobs �����������������.

5 6

⎧� � ����� � � ���� �� � ⎨� ��� � � � � ���� �� ���� ���� � −�� � ��������� ⎩� ��

7 8 0

1

2

3

4

5

6

7

8

9

���� 10

11 8

9

19 / 28

20 / 28

������������������������������������������

������������������������������������������ Observation. Recursive algorithm fails spectacularly because of redundant subproblems ⇒ exponential algorithms.

Input:

n, s[1..n], f[1..n], v[1..n]

Ex. Number of recursive calls for family of "layered" instances grows like

Sort jobs by finish time so that f[1] ≤ f[2] ≤ … ≤ f[n].

Fibonacci sequence.

Compute p[1], p[2], …, p[n]. 5

Compute-Opt(j) if j = 0

4

1

return 0.

3

2

else

3

3

return max(v[j] + Compute-Opt(p[j], Compute-Opt(j–1))).

4

1

2

1

1

2

0

1

1

0

5 ��������������������

1

0

��������������

10

11

21 / 28

22 / 28

������������������������������������������

�������������������������������������������

Memoization. Cache results of each subproblem; lookup as needed.

Claim. Memoized version of algorithm takes ���������� time.

Input:

�Sort by finish time: ����������. �Computing ��⋅� : ���������� via sorting by start time.

n, s[1..n], f[1..n], v[1..n]

�����������������:

Sort jobs by finish time so that f[1] ≤ f[2] ≤ … ≤ f[n].

for j = 1 to

� (ii) fills in one new entry M[j] and makes two recursive calls

n

�Progress measure Φ���� nonempty entries of M[].

M[j] ← empty. M[0] ← 0.

each invocation takes ���� time and either

� (i) returns an existing value M[j]

Compute p[1], p[2], …, p[n].

global array

� initially Φ����, throughout Φ��≤���.

� (ii) increases Φ by � ⇒ at most �� recursive calls.

M-Compute-Opt(j) if M[j] is empty

�Overall running time of ���������������� is ����.

M[j] ← max(v[j] + M-Compute-Opt(p[j]), M-Compute-Opt(j – 1)).

return M[j].

Remark. ���� if jobs are presorted by start and finish times.

12

23 / 28

13

24 / 28

Weighted Interval Scheduling Example

���������������������������������������� Bottom-up dynamic programming. Unwind recursion.

Index 1

��������������������������������������������������������������������������

2

v1 = 2 v2 = 4

���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

�������������������������������������≤������≤���≤����

v3 = 4

3

�����������������������������

4

������←���

v4 = 7 v5 = 2

5

�������������� ��������←����������������������������������������

v6 = 1

6

j

vj

p(j)

1 2 3 4 5 6

2 4 4 7 2 1

0 0 1 0 3 3

vj + M[p(j)] 2+0 4+0 4+2 7+0 2+6 1+6

M[j − 1]

M[j]

0 2 4 6 7 8

2 4 6 7 8 8

���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

15

25 / 28

Weighted Interval Scheduling Exercise

26 / 28

������������������������������������������������ Q. DP algorithm computes optimal value. How to find solution itself? A. Make a second pass.

Index 1 2 3 4 5

v1 = 2 v2 = 3 v3 = 6 v4 = 1 v5 = 9

j

vj

1 2 3 4 5

2 3 6 1 9

p(j)

vj + M[p(j)]

M[j − 1]

M[j]

Find-Solution(j) if j = 0 return ∅. else if (v[j] + M[p[j]] > M[j–1]) return ������ ∪ Find-Solution(p[j]). else return Find-Solution(j–1).

Analysis. # of recursive calls ≤����⇒������. 14

27 / 28

28 / 28