浅谈动态规划

1 动态规划的概念:
          把问题转变成状态(计算机的本质就是一个状态机,内存里的各种数据构成了当前的状态,CPU只能利用当前的状态去计算下一个状态),并且将状态作为缓存进行存储,当求第 i 个阶段的最优解时,可由前 i-1 个阶段的最优解得到。动态规划的方程是:
                 
2 动态规划的理解: 
     先“记忆”之前的某些事情(状态),在求最终目标(最优状态)时,直接使用直接缓存的“记忆”。(“记忆”通过递归的方式存储)
 
3 动态规划的特点:
  • 最优子结构:当子问题最优时,母问题通过一定的选择判断就一定能有最优解的情况。
  • 子问题重叠:当母问题和子问题本质上是一个问题的情况。(子问题之间的参数传递是重点!也就是状态转移方程)
  • 边界:当某个子问题不再需要提出子子问题就可以得到答案的情况,这个答案就是边界。
 
4 动态规划和分治的共同点和不同点:
  • 共同点:都是把原问题划为若干个子问题,求得子问题的解后,再把子问题的解组合来求得原问题的解。
  • 不同点
    • 分治用递归的方式求解子问题的解,重复计算子问题解;动态规划则用有记忆功能的递归式(递推),提升了效率。
    • 分治的子问题都是独立的(没有公共子问题);动态规划则允许子问题之间有联系,有交叠。       
5 解题思路:
          当确定问题具有属性:当前状态和之前的状态有关,即母问题可以由最优子问题得到,且子问题是重叠的。可以确定为动态规划问题。
          如果问题是要维护一张二维表,首先要确定二维矩阵的行和列各代表什么(背包问题中,行表示物品,列表示包承重)
          此时,解题的重点是找到状态转移方程。(说白了要就是递推关系式的确立)
  
6 背包问题:
        问题阐述    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);
}
posted @ 2018-02-25 15:37  IvanB.G.Liu  阅读(314)  评论(0编辑  收藏  举报