动态规划算法

参考博文1

基本思想:
  把一个较复杂的问题按照阶段划分,分解为若干个较小的局部问题,然后按照局部问题的递推关系,依次作出一系列决策,直至整个问题达到总体最优的目标。
动态规划四部曲:

  • 确定状态:
    最后一步:最优策略中使用的最后一枚硬币值ak;
    化成子问题:最少的硬币数量拼出更小的面值M-ak;
  • 转义方程:
    f[X]=min{f[X-2]+1,f[X-5]+1,f[X-7]+1}
  • 初始条件和边界条件:
    f[0]=0,如果不能拼出Y,f[Y]=正无穷
  • 计算顺序:
    f[0],f[1],f[2]……

例题1:
现在有3种硬币,分别面值2元、5元和7元,每种硬币都有足够多
买一本书需要27元
如何用最少的硬币组合正好付清,不需要对方找钱

#include<iostream>
#include<limits.h>
using namespace std;
/*
现在有3种硬币,分别面值2元、5元和7元,每种硬币都有足够多
买一本书需要27元
如何用最少的硬币组合正好付清,不需要对方找钱

    coin[]代表硬币的种类[2,5,7]
    M代表拼凑的值 27
*/
int min(int a,int b){
    return a>b?b:a;
}
int main(){
    int coin[]={2,5,7};
    int M=27;
    int* f;
    f[0]=0;
    for(int i=1;i<=M;i++){
        f[i]=INT_MAX;//一开始就表示为无穷大,表示不能拼凑
        for(int j=0;j<3;j++){
            //这里右三种面值的硬币,需要循环一次
            if(i-coin[j]>=0 && f[i-coin[j]]!=INT_MAX){
                f[i]=min(f[i],f[i-coin[j]]+1);
            }
        }
    }
    if(f[M]==INT_MAX){
        printf("===");
    }
    cout<<f[M]<<endl;
    return 0;
}

例题2:
给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者是向右走一步。
问:有多少种不同的方式走到右下角。
解题四部曲:
一:确定状态
做后一步:

  • 最后挪动的一步要么向右,要么向下

  • 右下角的坐标设为(m-1,n-1)

  • 那么机器人的前一步一定是在(m-2,n-1)或者是(m-1,n-2)
    化成子问题:
    如果有X种方式从左上角走到(m-2,n-1),有Y种方式从左上角走到(m-1,n-2),则机器人有X+Y种方法从左上角走到(m-1,n-1),
    这样问题就可以转化为机器人有多少种方式从左上角走到(m-2,n-1)和(m-1,n-2)
    状态:设f[i][j]为机器人有多少种方式从左上角走到(i,j)
    二:确定转移方程
    对于任意一个格子(i,j)
    f[i][j] = f[i-1][j] + f[i][j-1]
    三:确定开始和边界条件:
    初始条件:f[0][0] = 1,只有一种方式到左上角
    边界条件:i=0或j=0,则前一步只能有一个方向过来:
    f[i][j] =1
    四:计算顺序

  • f[0][0]=1

  • 计算第0行:f[0][j]=1

  • 计算第1行

  • ……

  • 答案是:f[m-1][n–1]

#include<iostream>
#include<stdlib.h>
using namespace std;

/*
  给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者是向右走一步。
  问:有多少种不同的方式走到右下角。
*/
int main(){
    int m,n;
    cout<<"请先输入行数和列数:";
    cin>>m>>n;
    int **f;
    f=(int**)malloc(sizeof(int*)*m);
    for(int i=0;i<m;i++)
        f[i]=(int*)malloc(sizeof(int)*n);
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            if(i==0 || j==0){
                f[i][j]=1;
            }
            else{
                f[i][j]=f[i-1][j]+f[i][j-1];
            }
        }
    }
    //从左上角到表上的各个点的方式有多少种
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++)
            cout<<f[i][j]<<" ";
        cout<<endl;
    }
    cout<<"机器人从左上角走到右下角有"<<f[m-1][n-1]<<"条路\n";
}

例题3——存在型动态规划
有n块石头分别在x轴的0,1,2,3,……,n-1位置上
一只青蛙在石头0处,想跳到石头n-1处
如果青蛙在第i块石头上,它最多可以向右跳距离ai
问青蛙能否跳到石头n-1上

  1. 确定状态:
    最后一步:能跳到n-1位置
    则它最后从i起跳,i<n-1, n-1-i<=ai
    子问题:能跳到i位置
    状态 :设f[j]表示青蛙能不能跳到j
  2. 确定转移方程:
    f[j] = OR 0<=i<j (f[i] and i+a[i] >= j)
  3. 确定开始和边界条件:
    初始条件:f[0] = True
  4. 计算顺序:
    设f[j]表示青蛙能不能跳到j
    f[j]=OR 0<=i<j (f[i] and i+a[i] >= j)
    初始化f[0]=true
    计算f[1],f[2],……,f[n-1]
    答案是f[n-1]
#include<iostream>
#include<stdlib.h>
using namespace std;

/*
   有n块石头分别在x轴的0,1,2,3,……,n-1位置上
   一只青蛙在石头0处,想跳到石头n-1处
   如果青蛙在第i块石头上,它最多可以向右跳距离ai
   问青蛙能否跳到石头n-1上
*/

int main(){
    int n;
    cout<<"请输入n:";
    cin>>n;
    cout<<"请输入"<<n<<"个数字:";
    
    int *a;
    a=(int*)malloc(sizeof(int)*n);
    for(int i=0;i<n;i++)
        cin>>a[i];
    bool *f;
    f=(bool*)malloc(sizeof(bool)*n);
    f[0]=true;
    for(int j=1;j<n;j++){
        f[j]=false;
        for(int i=0;i<j;i++){
            if(f[i]&&a[i]+i>=j){
                f[j]=true;
                break;
            }
        }
    }
    if(f[n-1]==true)
        cout<<"可以到达";
    else cout<<"不可以到达";
    return 0;
}

例题4—最长公共子序列(LCS)与最长公共子串(DP)
公共子序列:在母串中都出现过并且出现的顺序和母串保持一致。
子串:在母串中连续出现的子序列

1.确定状态:
最后一步:判断A和B中的最后一个元素是否相同,
子问题:若A和B中的倒数第二个元素相同,则序列长度=倒数第二个元素前相同的元素个数+1
状态:c[i][j]表示A中第一个元素到第i个元素和B中第一个元素到第j个元素的最长公共子序列。
2. 确定转移方程:
在这里插入图片描述
3. 初始条件:
c[i][0]=0, c[0][j]=0

#include<iostream>
#include<stdlib.h>
using namespace std;

int main(){
    char A[]="helloworld",B[]="loop";
    int n,m;
    int **c;
   // cout<<"A、B串各需要输入多少个字符:";
    //cin>>n>>m;
    n=10;m=4;
    if(n==0 || m==0){
        cout<<"串里没有东西啊"<<endl;
        return 0;
    }
    c=(int**)malloc(sizeof(int*)*n);
    for(int i=0;i<=n;i++)
        c[i]=(int*)malloc(sizeof(int)*m);
        
    for(int i = 0 ; i <= n; i++)//初始状态
        c[i][0] = 0;
    for(int i = 0; i <= m; i++)
        c[0][i] = 0;
        
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(A[i-1]==B[j-1])
                c[i][j]=c[i-1][j-1]+1;
            else
                c[i][j]=max(c[i-1][j],c[i][j-1]);
        }
    }
    cout<<c[n][m];
    return 0;
}

最长公共子串
转移方程:在这里插入图片描述

#include<iostream>
#include<stdlib.h>
using namespace std;

int main(){
    int m=7,n=6;
    char A[]="abcbdab",B[]="bcdaba";
    int **c;
    c=(int**)malloc(sizeof(int*)*(m+1));
    for(int i=0;i<m;i++)
        c[i]=(int*)malloc(sizeof(int)*(n+1));
    for(int i = 0 ; i <= m; i++)//初始状态
        c[i][0] = 0;
    for(int i = 0; i <= n; i++)
        c[0][i] = 0;
    int res=0;
   // char *C;
    //int k=0;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(A[i-1]==B[j-1]){
               c[i][j]=c[i-1][j-1]+1;
               res=max(res,c[i][j]);
              // C[k++]=A[i-1];
             // cout<<A[i-1];
            }
            else{
                c[i][j]=0;
            }
        }
    }
    cout<<res;
   // for(int i=0;i<k;i++)
     //   cout<<C[i];
    return 0;
}

背包问题:
假设现有容量10kg的背包,另外有3个物品,分别为a1,a2,a3。物品a1重量为3kg,价值为4;物品a2重量为4kg,价值为5;物品a3重量为5kg,价值为6。将哪些物品放入背包可使得背包中的总价值最大?

  1. 确定状态:
    最后一步:最后一次装了w[i] kg
    子问题:前面i-1次最多装多少价值的东西
    状态 :
    w[i] : 第i个物体的重量;
      p[i] : 第i个物体的价值;
      c[i][m] : 前i个物体放入容量为m的背包的最大价值;
      c[i-1][m] : 前i-1个物体放入容量为m的背包的最大价值;
      c[i-1][m-w[i]] : 前i-1个物体放入容量为m-w[i]的背包的最大价值;
  2. 确定转移方程:
    c[i][m]=max{c[i-1][m-w[i]]+p[i],c[i-1][m]}
  3. 确定开始和边界条件:
    初始条件:c[i][0]=c[0][j]=0
  4. 计算顺序:
    设f[j]表示青蛙能不能跳到j
    f[j]=OR 0<=i<j (f[i] and i+a[i] >= j)
    初始化c[i][0]=c[0][j]=0
    计算c[i][j]
#include<iostream>
using namespace std;
int main(){
    int m=10;
    int n=3;
    int w[]={3,4,5};
    int p[]={4,5,6};
    
    //c[i][v]表示前i件物品恰放入一个重量为m的背包可以获得的最大价值
    int c[n+1][m+1];
    for(int i=0;i<n+1;i++)
        c[i][0]=0;
    for(int i=0;i<m+1;i++)
        c[0][i]=0;

    for(int i=1;i<n+1;i++){
        for(int j=1;j<=m+1;j++){
            //当物品为i件重量为j时,如果第i件的重量w[i-1]小于重量j时,c[i][j]为下列两种情况之一:
            //(1)物品不放入背包中,所以c[i][j]为c[i-1][j]的值
            //(2)物品i放入背包中,则背包剩余重量为j-w[i-1],所以c[i][j]为c[i-1][j]-w[i-1]]的值加上当前物品i的价值
            if(w[i-1]<=j){
                if(c[i-1][j]<(c[i-1][j-w[i-1]]+p[i-1]))
                    c[i][j] = c[i-1][j-w[i-1]]+p[i-1];
                else
                    c[i][j] = c[i-1][j];
            }else
                c[i][j] = c[i-1][j];
        }
    }

    int x[n];
    for(int i=n;i>0;i--){
        //如果c[i][m]大于c[i-1][m],说明c[i][m]这个最优值中包含了w[i-1](注意这里是i-1,因为c数组长度是n+1)
        if(c[i][m]>c[i-1][m]){
            x[i-1] = 1;
            m-=w[i-1];
        }
    }
    for(int j=0;j<n;j++){
        cout<<x[j];
    }

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值