C++中如何實現動態規劃算法_動態規劃問題解析

C++中如何實現動態規劃算法_動態規劃問題解析

動態規劃,說白了,就是把一個復雜問題拆解成一更小的、相互關聯的子問題,然后解決這些子問題,最后把它們的答案組合起來,得到原始問題的答案。關鍵在于,子問題之間不是獨立的,它們會互相重疊,動態規劃就是用來避免重復計算這些重疊的子問題。

C++中如何實現動態規劃算法_動態規劃問題解析

c++中實現動態規劃,主要就是兩招:記憶化搜索和遞推。

C++中如何實現動態規劃算法_動態規劃問題解析

解決方案

C++中如何實現動態規劃算法_動態規劃問題解析

具體來說,我們通常會創建一個表格(比如二維數組vector> dp),用來存儲子問題的解。這個表格的維度取決于問題的性質。然后,根據問題的狀態轉移方程,來填充這個表格。

立即學習C++免費學習筆記(深入)”;

  • 記憶化搜索:本質上是遞歸,但加了一個緩存,避免重復計算。 比如,計算dp[i][j]時,先檢查它是否已經被計算過。如果dp[i][j]已經有值了,直接返回;否則,根據狀態轉移方程計算dp[i][j],并存入表格。

    #include <iostream> #include <vector>  using namespace std;  int solve(int i, int j, vector<vector<int>>& dp) {     if (i == 0 || j == 0) {         return 0; // 邊界條件     }      if (dp[i][j] != -1) {         return dp[i][j]; // 已經計算過,直接返回     }      // 狀態轉移方程 (這里只是一個例子,具體問題具體分析)     dp[i][j] = solve(i - 1, j, dp) + solve(i, j - 1, dp) + 1;     return dp[i][j]; }  int main() {     int n = 5, m = 6;     vector<vector<int>> dp(n + 1, vector<int>(m + 1, -1)); // 初始化為-1,表示未計算     cout << solve(n, m, dp) << endl;     return 0; }
  • 遞推:從最小的子問題開始,逐步計算更大的子問題,直到得到原始問題的解。 關鍵是確定計算順序,保證在計算dp[i][j]時,它所依賴的子問題(比如dp[i-1][j]和dp[i][j-1])已經被計算過了。

    #include <iostream> #include <vector>  using namespace std;  int main() {     int n = 5, m = 6;     vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0)); // 初始化為0      // 遞推計算     for (int i = 1; i <= n; ++i) {         for (int j = 1; j <= m; ++j) {             // 狀態轉移方程 (這里只是一個例子,具體問題具體分析)             dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + 1;         }     }      cout << dp[n][m] << endl;     return 0; }

動態規劃的難點在于:

  1. 確定狀態:狀態就是子問題,需要仔細分析問題,找到能夠表示子問題的變量。
  2. 狀態轉移方程:狀態轉移方程描述了子問題之間的關系,是動態規劃的核心。
  3. 邊界條件:邊界條件是最小的子問題,可以直接求解。
  4. 計算順序:對于遞推,需要確定合適的計算順序,保證子問題在被使用之前已經計算出來。

如何選擇記憶化搜索還是遞推?

這其實沒有絕對的答案,取決于個人偏好和問題的特點。

  • 記憶化搜索:代碼更簡潔,更容易理解,特別是對于狀態比較復雜的問題。 缺點是可能會有遞歸調用的開銷,在某些情況下可能會溢出(可以通過手動擴展棧空間來解決)。
  • 遞推:效率更高,沒有遞歸調用的開銷。缺點是代碼可能更復雜,需要仔細考慮計算順序。

一般來說,如果問題比較復雜,狀態比較多,我會傾向于使用記憶化搜索。如果問題比較簡單,狀態比較少,我會傾向于使用遞推。

動態規劃和貪心算法有什么區別

動態規劃和貪心算法都是用來解決優化問題的。但它們之間有很大的區別

  • 動態規劃:考慮所有可能的解,選擇最優的解。它通過將問題分解為子問題,并保存子問題的解,避免重復計算,從而提高效率。 動態規劃通常適用于具有最優子結構和重疊子問題的問題。
  • 貪心算法:每一步都選擇當前看起來最好的解,希望最終得到全局最優解。貪心算法不保證得到最優解,但通常可以得到一個近似最優解。 貪心算法通常適用于具有貪心選擇性質的問題。

舉個例子,假設我們要找從A到B的最短路徑。

  • 動態規劃:會考慮所有可能的路徑,并選擇最短的路徑。
  • 貪心算法:每一步都選擇離B最近的節點,希望最終得到最短路徑。但如果中間某個節點選擇錯誤,可能會導致最終的路徑不是最短的。

動態規劃有哪些常見的應用場景?

動態規劃應用非常廣泛,比如:

  • 背包問題:給定一組物品,每個物品有重量和價值,在不超過背包容量的前提下,選擇哪些物品可以使得總價值最大。
  • 最長公共子序列(LCS):給定兩個字符串,找到它們的最長公共子序列。
  • 最長遞增子序列(LIS):給定一個序列,找到它的最長遞增子序列。
  • 編輯距離:給定兩個字符串,計算將一個字符串轉換成另一個字符串所需要的最少操作次數(插入、刪除、替換)。
  • 斐波那契數列:雖然可以直接遞歸或者循環計算,但用動態規劃可以避免重復計算。
  • 路徑規劃:在圖或者網格中尋找最短路徑或者最優路徑。

總而言之,動態規劃是一種強大的算法,可以用來解決很多優化問題。理解動態規劃的思想,并掌握常見的動態規劃問題的解法,對于提高編程能力非常有幫助。

? 版權聲明
THE END
喜歡就支持一下吧
點贊14 分享