# Limitations of Dynamic Programming --- CS 137 // 2021-11-15 ## Administrivia - Daily exercise 11 turned in - Reflections on exercises 9 and 10 turned in # Exam 2 - Monday, November 22nd ## Exam 2 - **Format of the exam**: + Written, in-class, 75-minute exam + Closed textbook/notes/internet - Cumulative, but emphasis on new material
(Skiena Chapters 7, 8, 10) - Problems on exam will be directly taken or directly inspired from exercises at the end of each chapter ## Practice Problems - For practice, take a look at the following problems in [this exam](https://www3.cs.stonybrook.edu/~skiena/373/exams/z1.pdf) written by Skiena himself + 4, 5, 8, 9, 12, 13, 16, 21, 22, 28, 29, 31-34 - Wednesday will be dedicated to review and going over sample problems # Questions ## ...about anything? # Daily Exercise Solution # Dynamic Programming ## Dynamic Programming - Dynamic programming is a technique for improving the efficiency of certain recursive algorithms by caching previous results so they aren't recomputed - Name is synonymous with: + "filling in a table over time" # Edit Distance ## Edit Distance - We want to find the minimal number of "edits" required to transform a string $x$ into a string $y$ - We allow the following types of edits: 1. **Substitution:** Replace a single character with another single character 2. **Insertion:** Add a new character into the string 3. **Deletion:** Remove a character from the string - Costs for edits are $c_\text{sub}(x,y)$, $c_\text{ins}(x)$, and $c_\text{del}(x)$ ## Recursive Solution ```py def edit_dist(a, b, i, j): if i == 0 and j == 0: return 0 elif j == 0: return sum(c_del(a[k]) for k in range(i)) elif i == 0: return sum(c_ins(b[k]) for k in range(j)) else: d1 = edit_dist(a, b, i-1, j ) + c_del(a[i]) d2 = edit_dist(a, b, i , j-1) + c_ins(b[j]) d3 = edit_dist(a, b, i-1, j-1) + c_sub(a[i], b[j]) return min(d1, d2, d3) ``` - Runtime is exponential! ## Dynamic Programming Solution for Edit Distance
1. Create an $(n+1)\times (m+1)$ array, $D$ 2. $D[0,0] = 0$ 3. For $i = 1\ldots n$ do: 1. $D[i,0] = D[i-1,0] + c_\text{del}(a_i)$ 4. For $j = 1\ldots m$ do: 1. $D[0,j] = D[0,j-1] + c_\text{ins}(b_j)$ 5. For $i = 1\ldots n$ and $j = 1\ldots m$ do: $$D[i,j] = \min\begin{cases} D[i-1,j] + c_\text{del}(a_i)\\\\ D[i,j-1] + c_\text{ins}(b_j)\\\\ D[i-1,j-1] + c_\text{sub}(a_i,b_j) \end{cases}$$ 6. Return $D[n,m]$
## Analysis of DP Solution - Runtime of DP algorithm? + $O(nm)$, dominated by the nested for-loops # Seam Carving ![Comparison of image resizing approaches](http://community.wolfram.com//c/portal/getImageAttachment?filename=comparison.png&userId=73716) ## Seam Carving - Seam carving involves repeatedly finding a path from one side of the image to the opposite that can be deleted without affecting the image much ![Seam ](http://community.wolfram.com//c/portal/getImageAttachment?filename=seam.png&userId=73716) ## Energy Function - We formalize "not affecting the image much" with an **energy function** that assigns a value to every pixel of the image ![energy function visualization](http://community.wolfram.com//c/portal/getImageAttachment?filename=energy.png&userId=73716) - The energy function can be represented as an array $E$ where $E[i,j]$ is a non-negative real number ## Seam Carving Visualization - The approach is to find the path with minimal total energy and remove it from the image - Repeatedly doing this will allow us to reduce the width of an image to a desirable amount ![Seam carving animation](https://andrewdcampbell.github.io/images/blog/seam_carving.gif) ## Finding a Seam - Suppose we have an $w\times h$ image with associated energy table $E$ (Note that $(0,0)$ is the top-left pixel) - We wish to find a path from some pixel $(x_1, 0)$ to some other pixel $(x_2,h-1)$ with minimum energy + i.e., $\sum_{(x,y)\in\text{Path}} E[x,y]$ - For now, let's focus on finding the numerical minimal energy value and worry about reconstructing the path later ## Recursive Seam Finding - There are several approaches to solving this problem, but let's use dynamic programming - First, we need to solve the problem recursively - How can we break the problem of seam carving into sub-problems of the same type? + Take a minute and discuss with your neighbors your ideas for breaking this into sub-problems ## Recurrence for Seam Finding - Define $SF(x,y)$ to be the energy of the best path from the top of the image that ends in $(x,y)$ - How do we specify this recursively? + **Base case:** $SF(x,0) = E[x,0]$ + **Recursive case:** for $y > 0$ $$SF(x,y) = E[x,y] + \min\begin{cases} SF(x-1,y-1)\\\\ SF(x,y-1)\\\\ SF(x+1,y-1) \end{cases}$$ ## Recursive Solution ```py def seam_find(E, x, y): if y == 0: return E[x,y] else: e1 = seam_find(E, x-1, y-1) e2 = seam_find(E, x, y-1) e3 = seam_find(E, x+1, y-1) return E[x,y] + min(e1, e2, e3) ``` - The minimum energy seam is then the minimum of over all $x$ of $SF(x,h-1)$ - Runtime? + Exponential! (Roughly $3^h$) ## Dynamic Programming Solution for Seam Carving
1. Create an $w\times h$ array, $SF$ 2. $SF[x,0] = E[x,0]$ for all $x\in\\{0\ldots w-1\\}$ 3. For $y = 1\ldots h-1$ do: 1. For $x = 0\ldots w-1$ do: $$SF[x,y] = E[x,y] + \min\begin{cases} SF(x-1,y-1)\\\\ SF(x,y-1)\\\\ SF(x+1,y-1) \end{cases}$$ 4. Return smallest $SF[x,h-1]$
## Analysis of DP Solution - Runtime of DP algorithm? + $O(wh)$, dominated by the nested for-loops # Limitations of Dynamic Programming ## When can we use DP? - Dynamic programming computes recurrences efficiently by storing partial results. Thus, DP is efficient when there are few unique sub-problems --- - There are $n!$ permutations of an $n$-element set; we can't store a solution for each sub-permutation - There are $2^n$ subsets of an $n$-element set, so we cannot hope to store the best solution for each - But there are only $\frac{n(n-1)}{2}$ continguous substrings of a string, so we can use it for many string problems ## When can we use DP? - DP works best on objects which are linearly ordered and can't be rearranged + Characters in a string + Matrices in a chain + Points around the boundary of a polygon + Left-to-right order of leaves in a BST ## Principle of Optimality - To use DP, the problem must observe the **principle of optimality**, that the solution to a sub-problem must be optimal with respect to only the previous sub-problems - Dijkstra's algorithm works because the closest vertex is guaranteed to be optimal + However, with negative vertices, it no longer observes this optimal behavior and therefore fails