動態規劃(DP)是一種用來解決一類最優化問題的算法思想。簡單來講,動態規劃將一個複雜的問題分解成若干個子問題,經過綜合子問題的最優解來獲得原問題的最優解。算法
以斐波那契(Fibonacci) 數列爲例,斐波那契數列的定義爲 F0=1,F1=1,Fn=Fn-1+Fn-2 (n≥2)。爲了避免重複計算,能夠開一個一維數組 dp,用以保存已經計算過的結果。代碼以下:數組
1 int dp[maxn]; 2 // 斐波那契數列遞歸寫法 3 int F(int n) { 4 if(n == 0 || n==1) return 1; // 遞歸邊界 5 if(dp[n] != -1) return dp[n]; // 已經計算過 6 else { 7 dp[n] = F(n-1) + F(n-2); // 計算F(n),並保存 8 return dp[n]; // 返回 F(n) 結果 9 } 10 }
以經典的數塔問題爲例,以下圖所示,將一些數字排成數塔的形狀,其中第一層有一個數字,第二層有兩個數字……第 n 層有 n 個數字。如今要從第一層走到第 n 層,每次只能走向下一層鏈接的兩個數字中的一個,問:最後將路徑上全部數字相加後獲得的和最大是多少?優化
不妨令 dp[i][j] 表示從第 i 行第 j 個數字出發到達最底層的全部路徑中能獲得的最大和,在定義了這個數組後,dp[1][1] 就是最終想要的答案。spa
若是想求出 dp[i][j],那麼必定要先求出它的兩個子問題「從位置 (i+1,j) 到達最底層的最大和 dp[i+1][j]」和「從位置 (i+1,j+1) 到達最底層的最大和 dp[i+1][j+1]」,即進行了一次決策:走左下仍是右下。寫成式子就是:code
dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + f[i][j]blog
其中 f[i][j] 存放第 i 層的第 j 個數字。代碼以下:繼承
1 /* 2 動態規劃的遞推寫法 3 */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <math.h> 8 #include <stdlib.h> 9 #include <time.h> 10 #include <stdbool.h> 11 12 #define maxn 1000 13 int f[maxn][maxn], dp[maxn][maxn]; 14 15 // 較大值 16 int max(int a, int b) { 17 return a>b ? a : b; 18 } 19 20 int main() { 21 int n; 22 scanf("%d", &n); 23 int i, j; 24 for(i=1; i<=n; ++i) { 25 for(j=1; j<=i; ++j) { 26 scanf("%d", &f[i][j]); // 輸入數塔 27 } 28 } 29 // 邊界 30 for(j=1; j<=n; ++j) { 31 dp[n][j] = f[n][j]; 32 } 33 // 從第 n-1 層往上計算 dp[i][j] 34 for(i=n-1; i>=1; --i) { 35 for(j=1; j<=i; ++j) { 36 dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + f[i][j]; 37 } 38 } 39 printf("%d\n", dp[1][1]); // dp[1][1] 即爲須要的答案 40 41 return 0; 42 }
下面指出兩對概念的區別:遞歸
1. 分治與動態規劃。分治和動態規劃都是將問題分解爲子問題,而後合併子問題的解獲得原問題的解。可是不一樣的是,分治法分解出的子問題是不重疊的,所以分治法解決的問題不擁有重疊子問題,而動態規劃解決的問題擁有重疊子問題。另外,分治法解決的問題不必定是最優化問題,而動態規劃解決的問題必定是最優化問題。ci
2. 貪心與動態規劃。貪心和動態規劃都要求原問題必須擁有最優子結構。兩者的區別在於,貪心法經過一種策略直接選擇一個子問題去求解,沒被選擇的子問題就不去求解了,直接拋棄。也就是說,它老是隻在上一步選擇的基礎上繼續選擇,所以整個過程以一種單鏈的流水方式進行。而動態規劃老是從邊界開始向上獲得目標問題的解。也就是說,它老是會考慮全部子問題,並選擇繼承能獲得最優結果的那個,對暫時沒被繼承的子問題,因爲重疊子問題的存在,後期可能會再次考慮它們,所以還有機會成爲全局最優的一部分,不須要放棄。 string