<style> .red{ color:red } .blue{ color:blue } .green{ color:green } </style> ## :speech_balloon: 介紹 Dynamic Programming動態規劃用於解決<span class="blue">**最佳化問題(optimization problems)**</span>常用的演算法之一,透過將大問題不斷<span class="blue">**分割為相關子問題**</span>,並<span class="blue">**紀錄子問題最佳解**</span>回推原始問題的optimal solution即具有<span class="blue">**全域解**</span>的特性。 :::success :bell: 基本上沒有硬性的先備知識,Dynamic Programming為一種解題方法與思想,可直接閱讀 ::: * 先備知識(建議) * 基本演算法 * 資料結構 * 文章分類 (參考) * 難度:★★★ * 重要程度:★★★ * 適合閱讀的人 * 打算刷題 * 增進自身程式能力 :::warning :heavy_exclamation_mark:本篇先注重在初步理解Dynamic Programming解題思維,故未包含嚴謹的定義與證明 ::: ## :book: 觀念 --- ### **一、** Dynamic Programming 與 Divide-and-conquer 之差異 Dynamic Programming與Divide-and-conquer兩者概念相似,皆是將大問題分解成小問題再組合成原問題的解,但兩者仍有區別。 * ### Dynamic Programming #### 將問題分割為<span class="blue">互有連貫性的子問題</span>,以Fibonacci為例 \begin{align} &F(n)=\;\left\{ \begin{matrix} F(0)=0\\ F(1)=1\\ F(n) = F(n-1) + F(n-2)\; \text{, n} \in \mathbb N \cap \text{n>1}\\ \end{matrix} \right.\\ \end{align} >若要求得F(4)則需將問題分解為F(3)、F(2),其中F(3)與F(2)並<span class="red">**非獨立**</span>子問題,F(3)必須知道F(2)與F(1)的值才<span class="red">**可計算**</span> >![](https://hackmd.io/_uploads/ByY7h-w02.png =500x400) * ### Divide-and-Conquer #### 將問題分割為<span class="blue">無關聯的子問題</span>,以Merge Sort為例 >若將一陣列[3,1,4,2]利用merge sort 進行排序,會先將陣列分割成[3,1]、[4,2]子陣列(即子問題),其中<span class="red">[3,1]排序過程皆與[4,2]無關</span>,故我們知道子問題間彼此<span class="red">獨立</span>。 >![](https://hackmd.io/_uploads/BJcW4fw02.png) --- ### **二、** 從Fibonacci理解Dynamic Programming >已知Fibonacci關係式如下,請撰寫一程式計算F(n) \begin{align} &F(n)=\;\left\{ \begin{matrix} F(0)=0\\ F(1)=1\\ F(n) = F(n-1) + F(n-2)\; \text{, n} \in \mathbb N \cap \text{n>1}\\ \end{matrix} \right.\\ \end{align} 遇到Fibonacci想必各位腦中浮現的方法是遞迴呼叫,便能馬上寫出以下程式 ```cpp= #include <iostream> using namespace std; int Fibonacci(int n) { if (n <= 1) { return n; //F(0)=0 F(1)=1 } return Fibonacci(n-1) + Fibonacci(n-2); //F(n)=F(n-1)+F(b-2) } int main() { int n; cin >> n; cout << "F(" << n << ") = "<< Fibonacci(n) << endl; return 0; } ``` #### 但上面程式碼似乎有些問題,我們來觀察Fibonacci呼叫過程 ![](https://hackmd.io/_uploads/rkECMKOCn.png =300x400) :::warning :warning: 時間複雜度$T(n)=$ $O(\varphi^n) ,\varphi \approx \text{1.6180}$,遞迴呼叫過程中,不斷重複計算Fibonacci次數遠大於實際需要計算的次數,隨著$n$增加,計算次數將指數成長 ::: ### Dynamic Programming如何解決這個問題 仔細觀察遞迴呼叫過程中計算過的內容重複出現,是否能夠透過紀錄方式,將問題只<span class="blue">計算一次,</span>當要用到時能夠<span class="blue">重複使用</span>。Dynamic Programming正是基於以上想法,所衍伸出以下分支與方法 ![](https://hackmd.io/_uploads/Sk7RYY_Cn.png) * ### Top Down Approach (Memoization) ### ==想法 (大問題->小問題)== 將原始問題分解成更小的子問題,如果子問題已經解決過了那麼直接回傳子問題解答,若沒有則持續分割為更小子問題並解決他。 :::success 此方法兼具遞迴的特性,並使用額外的空間紀錄遞迴解答,節省計算次數 ::: ### ==程式碼== ```cpp= #include <iostream> using namespace std; int Memoization[1000]; int Fibonacci(int n) { if (n <= 1) { return n; //F(0)=0 F(1)=1 Base Case } if (Memoization[n] != 0) { return Memoization[n]; //若已經計算過了直接回傳 } return Memoization[n] = Fibonacci(n-1) + Fibonacci(n-2); //F(n)=F(n-1)+F(b-2) } int main() { int n; cin >> n; cout << "F(" << n << ") = "<< Fibonacci(n) << endl; return 0; } ``` :::warning :warning: 利用空間換取時間,透過大量的記憶體空間獲取更快的執行速度 ::: * ### Bottom Up Approach (Tabulation) ### ==想法 (小問題->大問題)== 既然已經知道大問題由許多小問題組成,那麼直接從小問題開始計算,最終可以組成大問題的解答 :::success 此方法利用迭代的特性,將解答不斷迭代,最終可獲得原始問題答案,其改善Memoization空間使用過度的問題 ::: ### ==程式碼== ```cpp= #include <iostream> using namespace std; int Fibonacci(int n) { if(n <= 1) return n; //Base Case直接回傳 int a = 0, b = 1, temp; //a代表temp前兩項,b代表temp前一項,temp代表目前Fibonacci temp = 0; for(int i = 2; i <= n; i++) { temp = a + b; //F(i) = F(i-1) + F(i-2) 透過迭代慢慢得出解答 a = b; b = temp; } return temp; } int main() { int n; cin >> n; cout << "F(" << n << ") = "<< Fibonacci(n) << endl; return 0; } ``` :::warning :warning: 雖然解決Memoization空間問題,但並不代表Tabulation都是最佳解法,因子問題都需迭代一次,而Memoization只須查看真正用到的子問題 ::: ### **三、** 總結 Dynamic Programming 將原始問題分解成各個<span class="blue">連貫性的子問題</span>看待,並思考子問題間的關聯性進行解題(以簡單推困難),進而衍伸出兩大方法Memoization、Tabulation解決最佳化問題(Optimal Solution),其中兩大方法比較如下 | | Tabulation | Memoization | | ---- | ------------------------------ | -------------------------- | |程式碼|較複雜|具備遞迴特性,內容簡單易讀| |速度|快,直接迭代答案|慢,需要等待遞迴回傳| |特性|迭代|遞迴| |子問題解決情況|所有子問題都需計算一次|只需計算所需子問題| ## :link: 參考 --- * [Dynamic Programming (DP) Tutorial with Problems](https://www.geeksforgeeks.org/introduction-to-dynamic-programming-data-structures-and-algorithm-tutorials/) * [MIT Introduction to Algorithms, Spring 2020](https://www.youtube.com/watch?v=r4-cftqTcdI&list=PLUl4u3cNGP63EdVPNLG3ToM6LaEUuStEY&index=24) * Introduction to algorithms 4th edition ###### tags: `Algorithm`