Dynamic Programming 
(動的計画法)
Data Structures and Algorithms
12th lecture, December 15, 2022
https://www.sw.it.aoyama.ac.jp/2022/DA/lecture12.html
Martin J. Dürst

© 2009-22 Martin
J. Dürst 青山学院大学
 
Today's Schedule
  - Leftovers and summary of last lecture
 
  - Algorithm design strategies
 
  - Overview of dynamic programming
 
  - Example application: Order of evaluation of chain matrix
  multiplication
 
  - Dynamic programming in Ruby
 
 
Remaining Schedule
  - December 15 (today): 12th lecture (Dynamic Programming)
 
  - December 22: 13th lecture (Algorithm Design Strategies)
 
  - January 12: 14th lecture (NP-completenes, reducibility)
   
  - January 19: 15th lecture (approximation algorithms)
   
  - January 26, 09:30-10:55 (85 min): Term Final Exam
 
 
Leftovers of Last Lecture
(Boyer-Moore algorithm, string matching context,...)
 
Summary of Last Lecture
  - A simplistic implementation of string matching is
    O(nm) in the worst case
 
  - The Rabin-Karp algorithm is O(n), using a hash
    function that can be extended to 2D matching
 
  - The Knuth-Morris-Pratt algorithm is O(n), and views
    the text strictly in input order after a precomputation step
 
  - The Boyer-Moore algorithm is O(n/m) in
    most cases
 
 
Algorithm Design Strategies
  - Simple/simplistic algorithms
 
  - Divide and conquer
   
  - Dynamic programming
 
 
Overview of Dynamic Programming
  - Investigate and clarify structure of (optimal) solution
 
  - Recursively define (optimal) solution
 
  - Calculate (optimal) solutions bottom-up
 
  - Construct (optimal) solution from calculation results
 
  - Proposed by Richard Bellman in the 1950ies
 
  - Name now sounds arbitrary, but is firmly established
 
 
Simple Example of Dynamic Programming
 
Matrix Multiplication
  - Multiplying a matrix 0M1
    (r0 by r1) and a matrix
    1M2 (r1 by
    r2) results in a r0 by
    r2 matrix 0M2
    (0M1·
    1M2 ⇒
    0M1M2)
   
  - This multiplication needs
    r0r1r2
    scalar multiplications and
    r0r1r2
    scalar additions,
    so its time complexity is O(r0r1r2)   
  - Actual example: r0=100, r1=2,
    r2=200
    ⇒ Number of multiplications: 100×2×200 =
    40'000    
  - Because the number of scalar multiplications and additions is the same,
    we will only consider multiplications
 
 
Matrix Multiplication Program Skeleton
for (i=0; i<r0; i++)
    for (j=0; j<r2; j++) {
        sum = 0;
        for (k=0; k<r1; k++)
            sum += 0M1[i][k] * 1M2[k][j];
        0M2[i][j] = sum;
    }
 
Chain Multiplication of Scalars
  - A series of multiplications (e.g. 163·4·25) is called chain
    multiplication
 
  - Multiplication of scalars is associative (i.e. (163·4)·25 =
    163·(4·25))
 
  - Not all multiplication orders have the same speed.
    For humans, 163·(4·25) = 163·100 = 16300 is faster  than (163·4)·25 = 652·25 
  - Conclusion: Choosing a good order of multiplication can speed up
    calculation
 
Chain Multiplication of Matrices
 
Number of Matrix Multiplications Orders
  
    
      | Multiplications | 
      Orders | 
    
    
      | 0 | 
      1 | 
    
    
      | 1 | 
      1 | 
    
    
      | 2 | 
      2 | 
    
    
      | 3 | 
      5 | 
    
    
      | 4 | 
      14 | 
    
    
      | 5 | 
      42 | 
    
    
      | 6 | 
      132 | 
    
    
      | 7 | 
      429 | 
    
    
      | 8 | 
      1430 | 
    
    
      | 9 | 
      4862 | 
    
  
  - The number of orders for multiplying n matrices is small for
    small n, but grows exponentially
 
  - The number of orders is equal to the numbers in the middle of Pascal's
    triangle (1, 2, 6, 20, 70,...)
    divided by increasing natural numbers (1, 2, 3, 4, 5,...) 
  - These numbers are called Catalan numbers:
    Cn = (2n)! /
    (n!(n+1)!) =
    Ω(4n/n3/2)
   
  - Catalan numbers have many applications: 
    
      - Combinations of n pairs of properly nested parentheses
        (n=3: ()()(), (())(), ()(()), ((())), (()()))
 
      - Number of shapes of binary trees of size n
 
      - Number of triangulations of a (convex) polygon with n
        vertices
 
    
   
 
 
Optimal Order of Multiplications
  - Checking all orders is very slow
    (Ω(n4n/n3/2)
    = Ω(4n/n1/2))
 
  - Minimal evaluation cost (number of scalar multiplications): 
    
      - mincost(a, c): minimal cost for evaluating
        aMc 
        
          - if a+1 ≧ c, mincost(a,
            c) = 0
 
          - if a+1 < c, mincost(a,
            c) =
            minc-1b=a+1
            cost(a, b, c)
           
        
       
      - split(a, c): optimal spliting point
        
          - split(a, c) = arg
            minb cost(a, b,
            c)
 
        
       
      - cost(a, b, c): cost for calculating
        aMbMc 
        
          - i.e. cost for splitting the evaluation of
            aMc at
            b
 
          - cost(a, b, c) =
            mincost(a, b)+mincost(b,
            c) +
            rarbrc
 
        
       
    
   
  - Simple implementation in Ruby: 
MatrixSlow in Cmatrix.rb 
 
Inverting Optimization Order and Storing Intermediate Results
  - The solution can be evaluated from split(0, n)
    top-down using recursion
 
  - The problem with top-down evaluation is that intermediate results
    (mincost(x, y)) are calculated repeatedly
 
  - Bottom-up calculation: 
    
      - Calculate the minimal costs and optimal splitting points for chains
        of length k, starting with k=2 and increasing to
        k=n
 
      - Store intermediate results for reuse
 
    
   
  - Implementation in Ruby: 
MatrixPlan in Cmatrix.rb 
 
Example Calculation
  
  
  
  
  
  
  
  
  
  
  
    
       | 
      0M1M5:
        274  
        0M2M5: 450 
        0M3M5: 470 
        0M4M5: 320  | 
       | 
    
    
       | 
      0M1M4:
        260  
        0M2M4: 468  
        0M3M4: 400  | 
      1M2M5:
        366 
        1M3M5: 330 
        1M4M5:
        250 | 
       | 
    
    
      |   | 
      0M1M3:
        200  
        0M2M3: 288  | 
      1M2M4:
        360 
        1M3M4:
        220 | 
      2M3M5:
        330 
        2M4M5: 390 | 
        | 
    
    
      |   | 
      0M1M2:
        48  | 
      1M2M3:
        120 | 
      2M3M4:
        300 | 
      3M4M5:
        150 | 
        | 
    
    
      | 0M1: 0 | 
      1M2: 0 | 
      2M3: 0 | 
      3M4: 0 | 
      4M5: 0 | 
    
    
      | r0 = 4 | 
      r1 = 2 | 
      r2 = 6 | 
      r3 = 10 | 
      r4 = 5 | 
      r5 = 3 | 
    
  
Overall solution (optimal order of multiplications):
0M1·(((1M2·2M3)·3M4)·4M5)
 
Complexity of Optimizing Evaluation Order
  - The calculation of mincost(a, c) is O(c-a)  
 
  - Evaluating all mincost(a, a+k) is O((n-k)·k)  
 
  - Evaluation cost has the shape of a tetrahedron,
    with one edge left-right at the bottom,
    and another edge front-back at the top 
  - Total time complexity:
    ∑nk=1
    O((n-k)·k) = O(n3)
 
The time complexity of dynamic programming depends on the structure of the
problem
O(n3),
O(n2), O(n),
O(nm),... are frequent time complexities
(for this problem, there is a different, more difficult algorithm
with O(n log n))
 
Overview of Dynamic Programming 
  - Investigate and clarify structure of (optimal) solution
 
  - Recursively define (optimal) solution (e.g. 
MatrixSlow) 
  - Calculate (optimal) solutions bottom-up (e.g.
  
MatrixPlan) 
  - Construct (optimal) solution from calculation results
 
 
Conditions for Using Dynamic Programming
  - Optimal substructure:
    The global (optimal) solution can be constructed from the (optimal)
    solutions of subproblems
    (common with divide and conquer) 
  - Overlapping subproblems
    (different from divide and conquer) 
 
Memoization
  - The key in dynamic programming is to reuse intermediate results
 
  - Many functions can be changed so that they remember results
 
  - This is called memoization:
    
      - Add a data structure that stores results
        (a dictionary with arguments as key and result as value) 
      - Check the dictionary
 
      - If the result is stored, return it immediately
 
      - If the result is not stored, calculate it, store it, and return
      it
 
    
   
  - Only possible for pure functions (functions with no side effects)
 
 
Memoization in Ruby
  - Use metaprogramming to modify a function so that:
    
      - On first calculation, result is stored (e.g. in a 
Hash
        using function arguments as the key) 
      - Before each calculation, storage is checked, and stored result used
        if available
 
    
   
  - Metaprogramming changes the program while it runs
 
  - Simple application example: Cfibonacci.rb
    (caution: for very big numbers, calculation times are also affected by the
    size of the numbers themselves) 
 
Summary
  - Dynamic programming is an algorithm design strategy
 
  - Dynamic programming is suited for problems where the overall (optimal)
    solution can be obtained from solutions for subproblems, but the
    subproblems overlap
 
  - The time complexity of dynamic programming depends on the structure of
    the actual problem
 
 
Homework
  - Review this lecture (including the 'Example Calculation' and the
  programs)
 
  - Find three problems that can be solved using dynamic programming, and
    investigate the algorithms used
 
  - Prepare for final exam
 
 
Glossary
  - dynamic programming
 
    - 動的計画法
 
  - algorithm design strategies
 
    - アルゴリズムの設計方針
 
  - optimal solution
 
    - 最適解
 
  - scalar
 
    - スカラー
 
  - Catalan number
 
    - カタラン数
 
  - matrix chain multiplication
 
    - 連鎖行列積、行列の連鎖乗算
 
  - triangulations
 
    - (多角形の) 三角分割
 
  - (convex) polygon
 
    - (凸) 多角形
 
  - intermediate result
 
    - 途中結果
 
  - splitting point
 
    - 分割点
 
  - arg min (argument of the minimum)
 
    - 最小値点
 
  - top-down
 
    - 下向き、トップダウン
 
  - bottom-up
 
    - 上向き、ボトムアップ
 
  - (regular) tetrahedron
 
    - (正)四面体
 
  - optimal substructure
 
    - 部分構造の最適性
 
  - overlapping subproblems
 
    - 部分問題の重複
 
  - memoization (verb: memoize)
 
    - 履歴管理
 
  - pure function
 
    - 純粋関数
 
  - metaprogramming
 
    - メタプログラミング