---
# System prepended metadata

title: Dynamic programming / Weighted Interval Scheduling
tags: [演算法(交大OCW)]

---

---
title: Dynamic programming / Weighted Interval Scheduling
tags: 演算法(交大OCW)
---
:::success
Dynamic programming 的 programming，不是編寫程式的 programming
而是來自於「Mathematical programming」，又叫「Mathematical optimization」
也就是數學中的「最佳化」，應用數學的一個分支，參考 [維基](https://zh.wikipedia.org/zh-tw/%E6%9C%80%E4%BC%98%E5%8C%96)
:::
:::warning
*Richard E. Bellman 1953*
:::
:::info
跟 DAC 有點不一樣。雖然兩者都是從小問題兜出大問題，但是 DAC 會把每個小問題全部展開，DP 則不會這麼做，而是會 Implicitly 的展開小問題，而且對這些小問題的順序也很講究
:::

# Weighted Interval Scheduling

一樣是給你很多工作，有開始跟結束的時間，不過這次多了「權重」，或者又叫「value」，這次的目標是要取得最大的「value」

如下面的例子，就是選擇權重分別為 26 跟 16 的這兩份工作

![](https://drive.google.com/uc?id=1ocrl4hpt7OcdBabbQ_qzk_S9XIprNbHK&export=download)

原本 Greedy 的地方講到的是沒有「權重」的，或者說權重都是 1 ，又叫 unit-weighted

因此就是做越多事情越好；但是有了權重後就不再是這樣了，像下面有了權重後，如果根據 當初得到的 Greedy Rule ，選擇最早結束的，會選到權重都是 1 的那兩份工作

但是很顯然的，最後權重最高的是選擇中間的工作

![](https://drive.google.com/uc?id=1mxRD5bt2Es_27nBclXALMYkvKfYYpySF&export=download)

# Induction Base

DP 最重要的一步就是，在一開始決定要以甚麼作為 Induction 的 Base
也就是每一步要以甚麼作為依據推進

先給一堆工作，並**以結束時間排序**

![](https://drive.google.com/uc?id=1eCUoNnwFmjYdoUbzJ1v3qOykJZzwRLpW&export=download)



## Induction on Time

:::danger
這裡要重新編寫
:::


既然這裡是排程問題，那就不妨選擇「時間」

### 出問題

但是這會遇到一個問題，在假設P(n=k)的時候，如果以時間當作 k 的話，不同的 k 會有不同的區間

像下面的兩張圖說明了 t' 取在不同的位置，則「假設 P(n=t') 成立」這個步驟，結果會是不同的

下圖中 P(t') 代表了紅色圈圈的權重最大值

![](https://drive.google.com/uc?id=11GSCzjbr6EJDgCNZMKklLG0JBEmkDzb3&export=download)

下圖中 P(t') 代表了紫色圈圈的權重最大值

![](https://drive.google.com/uc?id=1ifRct6VHuNv1YTBlNkCluXWqPOdgJoWq&export=download)

所以 Induction on Time 不是個好選擇

## Induction on Interval Index

改成以每個工作的 index 作為 Induction Base 就不會有上面的問題了
不過要記得是排序過後的 index，才不會有上面的問題

:::info
整體來說 DP 最重要且困難的步驟，就是在決定 Induction Base
以及為了找到合適的 Base，把資料做適當的處裡；像這裡就是把工作以結束時間排序
:::

為了要寫成歸納法的形式，也就是下一項跟前面的結果有關
所以下圖用了一個技巧，就是讓每個 index 紀錄沒有衝突的工作數量

![](https://drive.google.com/uc?id=1HKn2-pY0MzxnhVajPZ2Bi5s3w1OFYtAJ&export=download)

### Pseudo Code

Compute-Opt 就有用到代入上面的函數 p ，這樣就可以很方便的寫出歸納的形式

程式碼只有 2 行，非常簡潔

![](https://drive.google.com/uc?id=14iDWT1cmXYVqCHLRQT8Wqx8LEWmbuoJn&export=download)

下面就是說明有 6 個工作時的最佳選擇要怎麼找

要嘛就是不包含第 6 個工作，而這等同於 5 個工作時的最佳選擇

![](https://drive.google.com/uc?id=1RxUUkKlDuD3KdX0T4sx45Lq_t4Wpbnoe&export=download)

要嘛就是包含第 6 個工作，這等同於第 6 個工作的權重加上 3 個工作的最佳選擇

![](https://drive.google.com/uc?id=1fd3kJJBxI3eliP8Vg41fTP-vOZtF2V1j&export=download)

當我們把整個程式碼展開，會得到下面的流程

但是可以發現，紅色的部分是重複的內容，如果這些也給他執行下去，那時間花費可不得了

![](https://drive.google.com/uc?id=1gL0kwrenfRlezi3hWW2UgJI3G4Ncubjb&export=download)

所以我們就有了 DP 的一個技巧 Memoization

## Memoization：Top-Down

就是把計算過的內容給存起來，下次就可以直接取值不用重算一次

![](https://drive.google.com/uc?id=1efuh_eptxP615oFgJ0J8BeMQj4SUJe70&export=download)

重新改寫的程式碼，就是多了判斷的一行，以及把算好的存起來

![](https://drive.google.com/uc?id=1rOa0VX4jMjnHMVIwPv2xTNK6eJ0Bj2IT&export=download)

因此最後的流程就會變成下圖，時間只要 O(n)

![](https://drive.google.com/uc?id=1z2zO7umfN3D3AobLxul7Kky55OjmGgeq&export=download)

## Iteration：Bottom-Up

不過有了「存起來」的想法，代表我們其實也可以從頭開始做，時間也是 O(n)
只要記得定義好起點，像下面就是M[0]

![](https://drive.google.com/uc?id=1H4eo3XMNO9LyEOogfwaeKxf9GFe-hFuZ&export=download)

### 總結

- 要記得 Iteration 對於「順序」很重要
    - 這樣才能確確保後面的內容一定可以被算出來，迴圈可以正常運作
- 由於 Memoization 只有需要時，才會執行部分的小部件，也就是說不是所有的小部件都用到
    - 所以有時會比 Iteration 快一點點
- 由於跟 DAC 都師出同門，所以通常會先以 DAC 的角度去想如何執行遞迴的部分，然後再以 DP 執行
- 執行時間跟空間高度取決於「紀錄」這項步驟時所花的時間
    - 也就是把資料存起來的這項步驟
    - 那個存好的東西又叫 table

![](https://drive.google.com/uc?id=1IsjmdpioAKJAsI9X3kJ0sj84dvYJbONB&export=download)


---

# DP 的一些「重點」

當一個問題有下面這三個屬性，就可以用 DP

- 子問題數量是 polynomial 而不是 exponential
    - 老師說子問題的數量就是 table 的大小，如果是 exponential 會很花時間
    - 那就算用了 DP 一樣很慢
- 主要的問題可以由子問題的結果求出
    - 也就是遞迴的部分
- 子問題會自然的「從小到大」，並伴隨著很好計算的遞迴形式
    - 老師說這是他覺得最重要的部分
    - 
:::info
老師說當你發現這個問題存在一個不變的「Order」的話，那你有很大的機會可以使用 DP
:::
![](https://drive.google.com/uc?id=1V_ebu_n1ccG2kiDCv5Jwh5SHzTKCeDzX&export=download)


---

另外老師參考演算法的聖經 Introduction to Algorithm 裡面有提到的

如果一個問題可以用 DP 求解，會具備兩個特性

- Optimal Structure：問題的解一定是由子問題的解兜出來的
    - 老師說這個會需要簡單敘述的證明，也就是說明是如何遞迴的
- Overlapping subproblem：在上面提到的的例子中，或者下面的圖，就是紅色重複的部分

還有就是

- DP 通常處理最佳化問題
    - 這也是他名字的由來
- DP 最適合那些有不變線性序列的問題
    - 跟上面的『不變的「Order」』是同一件事

![](https://drive.google.com/uc?id=1P3JIbQDrGWAIqks8udUrBtI1pfJKLwNF&export=download)


---

然後老師也參考了另一個教演算法的教授，有提到使用 DP 的步驟

1. 寫出遞迴的形式
    - 可以從 DAC 的角度開始想，要怎麼拆 
3. 確認子問題的總數是 Polynimial
4. 確認子問題運算的順序
    - 這就是為甚麼需要『不變的「Order」』

![](https://drive.google.com/uc?id=1SMKzH9GgZnzD22ySb2OKbl0BDplRUjo5&export=download)


---

# 回顧

下面回顧了目前學到的三大技巧

![](https://drive.google.com/uc?id=1PizYpQaI73uTEbC2LuVlx699OcoT7TJy&export=download)
:::info
- 老師說有空來提一下 Quick Sort，他也是用 DAC ，不過他跟 Merge Sort 不一樣，不是用拆成兩個同樣大小，而是拆成部一樣大小的兩半
- Reuccrence relation 可以說是自己定義自己
- 如果發現子問題沒有 Overlapping，告訴了你不要用 DP 要用 DAC
:::

---

# 例子 Fibonacci Sequence

:::warning
雖然這不是一個最佳化問題，但是這個序列的結構跟 DP 的思考方式是一樣的
:::

$$
F_{n}=F_{n-1}+F_{n-2},\ \ \ F_{0}=0,\ F_{1}=1
$$

如果我就直接給他遞迴下去，會出現下面的問題

![](https://drive.google.com/uc?id=1zcN8h_34aRLqqwht6e2PSfBseSbqbu68&export=download)

沒錯，Overlapping；一旦去除 Overlapping，整棵樹會變成下圖

![](https://drive.google.com/uc?id=1kYdul103UHq5F6Z1GsVckx_SRGe25TVi&export=download)

## Memoization

![](https://drive.google.com/uc?id=1vU_A6m1d2HOnMAb3Cp2aaPhUU1Tfy1uj&export=download)

## Iteration

![](https://drive.google.com/uc?id=1Cub51RXJ28AwsrnFtuzhAgLaY1vclR82&export=download)

---

# 例子 Maze Routing

:::info
實際上這跟 VLSI 的繞線問題一樣
:::

兩點，只能走直線，會有障礙物

![](https://drive.google.com/uc?id=1l3X8Zfxr2kF33RG3XR7HbgnVGXSQkv4X&export=download)

# Lee's Algorithm

- 先以 Iteration，從起點向外擴散出每格的最短路徑；又叫 Wave Propogation
    - 有最短距離的格子就不用執行
- 直到碰到終點就停止

![](https://drive.google.com/uc?id=1oppGk0MGKuIuGSARMGcX0Bp3yyYyJlgh&export=download)










