淺談動態規劃
阿新 • • 發佈:2018-02-25
重點 alt clas 裏的 cpu 遞推關系 行存儲 val 填充 1 動態規劃的概念:
把問題轉變成狀態(計算機的本質就是一個狀態機,內存裏的各種數據構成了當前的狀態,CPU只能利用當前的狀態去計算下一個狀態),並且將狀態作為緩存進行存儲,當求第 i 個階段的最優解時,可由前 i-1 個階段的最優解得到。動態規劃的方程是:
2 動態規劃的理解:
先“記憶”之前的某些事情(狀態),在求最終目標(最優狀態)時,直接使用直接緩存的“記憶”。(“記憶”通過遞歸的方式存儲)
3 動態規劃的特點:
問題闡述 n個重量為w1,w2,w3......wn,價值為v1,v2,v3......vn的物品和一個承重為W的背包,求這些物品的最大價值集(能放入背包中)。
問題分析 設F(i,j)為背包問題的最大價值,即:前i個物品放入承重為j的背包中的最大價值。F(i,j)可以看做是一個子問題,他的解可以由之前的子問題的解組合求出。由第i個物品能不能放入承重為j的背包,可以將F(i,j)的解分為兩個類別:包含第i個物品和不包含第i個物品:
當j-wi<0,F(i,j)=F(i-1,j) ps:j-wi<0表示第i個物品放不進承重為j的背包中
當j-wi>=0,F(i,j)=max( F(i-1,j), vi+F(i-1,j-wi) ) ps:F(i-1,j)不等於F(i-1,j-wi)
我們的目標就是求得F(n,W)。可以將這個問題看成是二維表的動態規劃問題,即:回溯構建一個二維表,將其填滿,表的右下角即為問題的解(自底向上的求解方法)
問題求解 采用兩種方法,分別是自底向上的求解,以及帶有記憶的自頂向下求解。
- 最優子結構:當子問題最優時,母問題通過一定的選擇判斷就一定能有最優解的情況。
- 子問題重疊:當母問題和子問題本質上是一個問題的情況。(子問題之間的參數傳遞是重點!也就是狀態轉移方程)
- 邊界:當某個子問題不再需要提出子子問題就可以得到答案的情況,這個答案就是邊界。
- 共同點:都是把原問題劃為若幹個子問題,求得子問題的解後,再把子問題的解組合來求得原問題的解。
- 不同點
- 分治用遞歸的方式求解子問題的解,重復計算子問題解;動態規劃則用有記憶功能的遞歸式(遞推),提升了效率。
- 分治的子問題都是獨立的(沒有公共子問題);動態規劃則允許子問題之間有聯系,有交疊。
- 自底向上的動態規劃算法:按子問題從小到大的順序將子問題的解填充到表中,每個子問題都只求解一次。當表填充完畢後,表的右下角的值就是問題的解。
int dp_Backpack(struct backpack bp[n], int W) { int F[n+1][W+1]; for(int i=0;i<n+1;i++) F[i][0] = 0; for(int j=0;j<W+1;j++) F[0][j] = 0; for(int i=1;i<n+1;i++) { for(int j=1;j<W+1;j++) { if(j<bp[i-1].w) F[i][j] = F[i-1][j]; else F[i][j] = max( F[i-1][j], bp[i-1].v+F[i-1][j-bp[i-1].w] ); } } return F[n][W]; }
- 帶有記憶的自頂向下動態規劃算法:自頂向下的方式求解,①除表中0行和0列的值為0外,初始化表中所有格為null(可以設為-1);②一旦需要某個格中的值,先檢查該格,若為null,則遞歸調用進行計算並記錄入表,否則直接從表中取值。ps:避免計算不必要的子問題
struct backpack bp[n] = { //初始化bp結構體數組 } int F[n+1][W+1]; void initF(int F[n+1][W+1]) { for(int i=0;i<n+1;i++) //0列為0 F[i][0] = 0; for(int j=0;j<W+1;j++) //0行為0 F[0][j] = 0; for(int i=0;i<n+1;i++) //剩余表中值初始為-1 for(int j=0;j<W+1;j++) F[i][j] = -1; } int dp_Backpack(int i, int j) { if(F[i][j] == -1) //該值需要遞歸計算(在表中從上到下的遞歸計算下來) { if(j < bp[i-1].w) F[i][j] = dp_Backpack(i-1,j); else F[i][j] = max( dp_Backpack(i-1,j) , bp[i-1].v+dp_Backpack(i-1,j-bp[i-1].w) ); } return F[i][j]; } initF(F); int max_val = dp_Backpack(n,W);完整代碼 采用以上兩種方法求解01動態規劃問題:
#include <iostream> using namespace std; #define N 4 //物品總數 #define C 5 //背包的承重 int w[N] = {2,1,3,2}; //物品重量 int v[N] = {12,10,20,15}; //物品價值 int CurWeight = 0; //當前總重量 int CurValue = 0; //當前總價值 int x[N] ={0,0,0}; //當前的背包選取情況(1表示選取,0表示不選取) int MaxValue = 0; //最大價值 int MaxPack[N] = {0,0,0}; //最大價值下的背包選取情況(1表示選取,0表示不選取) /*用動態規劃解決01背包問題*/ int dp_Backpack_1(int NN, int W) { int n = NN+1; int c = W+1; int F[n][c]; for(int i=0;i<n;i++) F[i][0] = 0; for(int j=0;j<c;j++) F[0][j] = 0; for(int i=1;i<n;i++) { for(int j=1;j<c;j++) { if(j<w[i-1]) F[i][j] = F[i-1][j]; else F[i][j] = max( F[i-1][j], v[i-1]+F[i-1][j-w[i-1]] ); } } return F[NN][W]; } /*改進的動態規劃求01背包問題*/ void initF(int F[N+1][C+1]) { for(int i=0;i<N+1;i++) F[i][0] = 0; for(int j=0;j<C+1;j++) F[0][j] = 0; for(int i=1; i<N+1; i++) for(int j=1; j<C+1; j++) F[i][j] = -1; } void printF(int F[N+1][C+1]) { for(int i=0; i<N+1; i++) { for(int j=0; j<C+1; j++) cout<<F[i][j]<<" "; cout<<endl; } } int dp_Backpack_2(int F[N+1][C+1], int i, int j) { if(F[i][j] == -1) //該值需要遞歸計算(在表中從上到下的遞歸計算下來) { if(j < w[i-1]) F[i][j] = dp_Backpack_2(F,i-1,j); else F[i][j] = max( dp_Backpack_2(F,i-1,j) , v[i-1]+dp_Backpack_2(F,i-1,j-w[i-1]) ); } return F[i][j]; } int main() { cout<<"use dp_1‘s value: "<<dp_Backpack_1(N,C)<<endl; int F[N+1][C+1]; initF(F); cout<<"use dp_2‘s value: "<<dp_Backpack_2(F,N,C)<<endl; printF(F); }
淺談動態規劃