什么是動態規划?
動態規划(Dynamic Programming)是通過組合子問題的解來解決問題的。動態規划是用於求解包含重疊子問題的最優化問題的方法。其基本思想是,將原問題分解為相似的子問題。在求解的過程中通過子問題的解求出原問題的解。
動態規划的分類:
1. 線性規划:攔截導彈,合唱隊形,挖地雷等。
2. 區域規划:石子合並,加分二叉樹,統計單詞個數等。
3. 樹形動規:貪吃的九頭龍,二分查找樹,聚會的歡樂等。
4. 背包問題:01背包問題,完全背包問題,分組背包問題,裝箱問題,擠牛奶等
5. 除此之外還有插頭DP,按位DP,狀態壓縮DP等等。
入門題目:數字三角形
題目描述:給出了一個數字三角形。從三角形的頂部到底部有很多條不同的路徑。對於
每條路徑,把路徑上面的數加起來可以得到一個和,你的任務就是找到最大的和。
注意:路徑上的每一步只能從一個數走到下一層上和它最近的左邊的那個數或者右邊的那個數。
如:
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
分析一下為什么不能使用貪心算法,即每一次走都走下一行的兩個數值較大的。因為有可能在某一步的時候數值小沒走到,但是該路徑下面有較大的數值,就不會走到,就得不到最大的值。
1.朴素DFS搜索算法
可以采用朴素的深度優先搜索算法,即朴素DFS。每一次走都分為兩步,第一步求出走下一步時走的最大值,然后加上此步的數值。
int dfs(int x,int y) //表示從第x行,第y個數往下走可以得到的最大價值和,dfs(0,0)即為本題解。 { if(x > numCount - 1) return 0; return max(dfs(x+1,y),dfs(x+1,y+1)) + num[x][y]; }
完整的代碼示例為:
#include using namespace std; int num[5][5] = {7,0,0,0,0, 3,8,0,0,0, 8,1,0,0,0, 2,7,4,4,0, 4,5,2,6,5}; int numCount = 5; int dfs(int x,int y) { if(x > numCount - 1) return 0; return max(dfs(x+1,y),dfs(x+1,y+1)) + num[x][y]; } int main() { int maxsum = 0; maxsum = dfs(0,0); cout<<maxsum<<endl; return 1; }
這個算法效率極低,為什么呢?因為其中有大量的重復計算。通常遞歸都會有大量的重復計算。
2.DFS+記憶化搜索方法
針對於上述DFS算法的重復計算,我們可以先將dfs(x,y)的結果保存起來,等到下次需要時,直接使用。這就是記憶化搜索。
int dfs(int x,int y) { if(x > numCount - 1) return 0; if(result[x][y] != -1) return result[x][y]; return result[x][y] = max(dfs(x+1,y),dfs(x+1,y+1)) + num[x][y]; }
完整的代碼示例為:
#include using namespace std; int num[5][5] = {7,0,0,0,0, 3,8,0,0,0, 8,1,0,0,0, 2,7,4,4,0, 4,5,2,6,5}; int numCount = 5; int result[5][5]; int dfs(int x,int y) { if(x > numCount - 1) return 0; if(result[x][y] != -1) return result[x][y]; return result[x][y] = max(dfs(x+1,y),dfs(x+1,y+1)) + num[x][y]; } int main() { for(int i = 0;i < 5;i++) for(int j = 0; j < 5;j++) result[i][j] = -1; int maxsum = 0; maxsum = dfs(0,0); cout<<maxsum<<endl; return 1; }
不論是DFS還是記憶化DFS都是基於遞歸的思想進行計算。總結一下實際操作的特點。
(1)搜索的參數只有(x,y),每一對(x,y)確定一個狀態。
(2)搜搜的轉移是從(x+1,y)和(x+1,y+1)到(x,y)的,意思就是我們通過(x+1,y)和(x+1,y+1)的解去求(x,y)。
於是,設想:如果不用DFS,直接用數組保存狀態,在狀態與狀態之間實現轉移。
for(int i = numCount - 1; i >= 0; i--) for(int j = 0;j <= i; j++) result[i][j] = max(result[i+1][j],result[i+1][j+1]) + num[i][j];
完整的程序示例為:
#include using namespace std; int num[5][5] = {7,0,0,0,0, 3,8,0,0,0, 8,1,0,0,0, 2,7,4,4,0, 4,5,2,6,5}; int numCount = 5; int result[6][6]; int main() { for(int i = 0;i < 6;i++) for(int j = 0; j < 6;j++) result[i][j] = 0; for(int i = numCount - 1; i >= 0; i--) for(int j = 0;j <= i; j++) result[i][j] = max(result[i+1][j],result[i+1][j+1]) + num[i][j]; int maxsum = 0; maxsum = result[0][0]; cout<<maxsum<<endl; return 1; }
總結:搜索的參數只有(x,y)。每一對(x,y)確定一個狀態。我們通過(x+1,y)和(x+1,y+1)的解去求(x,y),於是我們可以設計出狀態並明確狀態的轉移,從而寫出狀態轉移方程。這就是動態規划!!!!
摘自:http://blog.sina.com.cn/s/blog_8eac84090102vt0y.html
作者:慕雪