• 动态规划理解


    看了知乎用户徐凯强 Andy王勐回答动态规划提问感觉受益匪浅。在这写下自己理解。

    首先要明确,动态规划的本质是找到合适的状态的定义与找到状态转移方程递归与递推只是实现动态规划的工具。动态规划是思想,递推与递归只是方法。不能本末倒置。

    什么是状态与状态转移方程两个人的文章已经进行了详细的说明。简单来说,对于一个问题,如果你想使用动态规划来解决那么必须找到DP方程。而且你定义这个DP方程必须无后效性。找到DP方程后,通过DP方程就可以使用递归或者递推方法解决。

    找DP方程是解决动态规划问题的核心。

    王勐的总结很好

    每个阶段只有一个状态->递推;
    每个阶段的最优状态都是由上一个阶段的最优状态得到的->贪心;
    每个阶段的最优状态是由之前所有阶段的状态的组合得到的->搜索;
    每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到而不管之前这个状态是如何得到的->动态规划。
     
    拿动态规划的经典问题01背包举例,这里只讲为什么01背包是动态规划问题,而不说01背包的的DP方程是怎么来的。网上有很多很好的推理。
    问题的表述如下

    0-1 背包问题:给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi 。

    问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

    如果背包物品可以部分放入,那么这个问题变成了贪心问题。动态规划的一种特殊情况。每次只讲性价比较高的物品放入即可。

    对于0-1背包问题这里有两种思路:

    1.暴力枚举:尝试将每件物品放入背包中,直到背包容量不足。计算当前组合的价值。最后找到所有组合的最大值。这里用到的便是搜索,当前阶段需要之前所有阶段的状态组合。

    2.动态规划:当前背包状态下的最大价值等于,MAX(i-1个包容量为j时计算出最大价值,v(i) +(i-1个包容量为j-w(i)时计算的最大价值))。即求两者的最大值,求当前问题用到了子问题。且两个问题解决方法相同。

    暴力枚举代码:

     1 /*测试结果10 
     2 3 6
     3 1 3 5
     4 4 3 6
     5 */ 
     6 #include<stdio.h>
     7 int flag[100] = {0}; 
     8 int n,c;//数量,容量 
     9 int w[100],v[100];//重量,价值 
    10 int max = 0;//最大价值
    11 int b[100] = {0}; 
    12 int num = 0; 
    13 void f(int weight,int val);
    14 int main(){
    15     scanf("%d %d",&n,&c);
    16     for(int i=0;i<n;i++){
    17         scanf("%d",&w[i]);
    18     }
    19     for(int i=0;i<n;i++){
    20         scanf("%d",&v[i]);
    21     }
    22     f(0,0);
    23     printf("最大价值为:%d
    ",max);
    24 }
    25 void f(int weight,int val){
    26     for(int i=0;i<n;i++){
    27         if(flag[i]==0){            
    28             if(weight+w[i]<=c){
    29                 flag[i] = 1;
    30                 f(weight+w[i],val+v[i]);
    31                 flag[i] = 0;
    32             }else{
    33                 max = max>val?max:val;
    34             }        
    35         }
    36     }
    37 } 
    0-1背包暴力枚举

    测试结果:

    3 6
    1 3 5
    4 3 6

    对于动态规划:

    首先定义状态:方法f(i,j)或者矩阵a[i][i]表示前i个包(阶段)容量为j时(状态)计算出最大价值。

    状态转移方程:f(i,j) = max(a[i-1][j],v[i]+f(i-1,j-w[i]))。

    找到状态转移方程就可以解决动态规划问题,可以使用递归也可以使用递推:

    递归代码:

     1 #include<stdio.h>
     2 /*测试结果44 
     3 4 20
     4 8 7 6 4
     5 14 15 20 9
     6 */
     7 int n,c;//数量,容量 
     8 int w[100],v[100];//重量,价值 
     9 int max = 0;//最大价值
    10 int a[100][100]= {0}; 
    11 int f(int i,int j);
    12 //int num = 0;
    13 int main(){
    14     scanf("%d %d",&n,&c);
    15     for(int i=1;i<=n;i++){
    16         scanf("%d",&w[i]);
    17     }
    18     for(int i=1;i<=n;i++){
    19         scanf("%d",&v[i]);
    20     }
    21     max = f(n,c);
    22     printf("%d
    ",max);
    23     //printf("递归调用%d次
    ",num); 
    24     printf("递归记录矩阵:
    ");
    25     for(int i=0;i<c;i++){
    26         if(i==0) printf("   ");
    27         printf("%3d",i+1);
    28     }
    29     printf("
    ");
    30     for(int i=1;i<=n;i++){
    31         for(int j = 1;j<=c;j++){
    32             if(j==1) printf("%3d",i);
    33             printf("%3d",a[i][j]);
    34         }
    35         printf("
    ");
    36     }
    37 }
    38 int f(int i,int j){
    39     //num++;
    40     if(j-w[i]<0){
    41         return f(i-1,j);
    42     }else if(i==1){
    43         a[i][j] = v[1];
    44         return v[1];
    45     }else if(i==0){
    46         return 0;
    47     }
    48     int num1,num2;
    49     if(a[i-1][j]!=0) num1=a[i-1][j];    
    50     else num1 = f(i-1,j);
    51     if(a[i-1][j-w[i]]!=0) num2=a[i-1][j-w[i]];
    52     else num2 = v[i]+f(i-1,j-w[i]);
    53     a[i][j] = num1>num2?num1:num2;
    54     return a[i][j];    
    55 } 
    0-1背包动态规划递归

    递推代码

     1 /*测试结果10 
     2 3 6
     3 1 3 5
     4 4 3 6
     5 */ 
     6 #include<stdio.h>
     7 int n,c;//数量,容量 
     8 int w[100],v[100];//重量,价值 
     9 int a[100][100]= {0};
    10 int main(){
    11      scanf("%d %d",&n,&c);
    12     for(int i=1;i<=n;i++){
    13         scanf("%d",&w[i]);
    14     }
    15     for(int i=1;i<=n;i++){
    16         scanf("%d",&v[i]);
    17     }
    18     for(int i=1;i<=n;i++){
    19         for(int j=1;j<=c;j++){
    20             if(j-w[i]>=0)
    21                 a[i][j] = a[i-1][j]>v[i]+a[i-1][j-w[i]]?a[i-1][j]:v[i]+a[i-1][j-w[i]];
    22             else
    23                 a[i][j] = a[i-1][j];
    24         }
    25     }
    26     printf("%d
    ",a[n][c]);
    27     printf("递推记录矩阵:
    ");
    28     for(int i=0;i<c;i++){
    29         if(i==0) printf("   ");
    30         printf("%3d",i+1);
    31     }
    32     printf("
    ");
    33     for(int i=1;i<=n;i++){
    34         for(int j = 1;j<=c;j++){
    35             if(j==1) printf("%3d",i);
    36             printf("%3d",a[i][j]);
    37         }
    38         printf("
    ");
    39     } 
    40     
    41 } 
    0-1背包动态规划递推

    测试结果:

    对于测试用例

    3 6
    1 3 5
    4 3 6

     

    可以看出两种方法本质上是一样,递推记录了每一种容量状态,递归记录了需要的容量状态。

    总结:

    1. 动态规划的本质是对问题状态的定义状态转移方程的定义。
    2. 状态转移方程需用到分治的思想,找到最优子结构。
    3. 找到状态转移方程便可以使用递归或者递推方法解决问题。

        

  • 相关阅读:
    软件配置管理的作用?软件配置包括什么?
    火火恍恍惚惚
    什么是软件测试?软件测试的目的与原则
    软件生存周期及其模型是什么?
    试述软件的概念和特点?软件复用的含义?构件包括哪些?
    一台客户端有三百个客户与三百个客户端有三百个客户对服务器施压,有什么区别?
    numpy的broadcast是怎么做的
    python到底是解释型语言还是需要编译的?
    python:删除类实例,仅仅只有动态属性会被删除,类属性不会被删除
    jupyter的kernel莫名其妙找不到,莫名其妙就中断
  • 原文地址:https://www.cnblogs.com/lolybj/p/9809812.html
Copyright © 2020-2023  润新知