动态规划BP
定义:动态规划是求解决策最优解的过程。
当我们使用暴力算法模拟所有结果的时候,可能会对于同一情况多次计算,复杂度会很大。使用动态规划进行记忆化搜索可以保存每一次情况模拟的子结果,从而降低复杂度。
例如下图递归算法模拟了所有的情况,其中就出现了重复计算的问题,这里我们只要把第一次(3,2)的情况记录下来,在第二次遇到相同的情况就可以直接使用之前已经计算的答案,也就是使用子问题的答案。
分析方法:
例题:
目前有2元硬币,5元硬币,7元硬币若干,如何使用最少的硬币正好组成27块钱?
**分析:**在贪心思想下,我们可以选取最多数量的7元硬币,再选取其他的硬币,得出来的就是7+7+7+2+2+2为6枚硬币,但是最优解则为7+5+5+5+5为5枚硬币,局部最优解产生不了全局最优解,所以不能使用贪心思想计算。在数据量小的时候这道题可以使用暴力来进行求解,但是如果数据量大了就要用动态规划算法进行复杂度的优化。
第一步:按时间或空间将大问题划分为子问题
动态规划的思想在于把大问题分解成子问题,将子问题的结果记录下来,最后组成大问题的答案。
在这题中有2元,5元,7元硬币若干,在我们组成27元钱的过程中,一枚一枚地选取,肯定存在最后一枚硬币。假如我们最后选取7元硬币,那么问题就变成了如何用最少的硬币正好组成20块钱;同理如果我们选取了5元硬币,那么问题就变成了如何用最少的硬币组成22元钱。最后算出来子问题的答案加一枚就是我们大问题的答案。
第二步:确定状态与状态变量
确定状态的目的是为了找到状态和决策之间的关系。
在这道题中刚刚分析到了我们的子问题就是如何用最少的硬币拼出来剩余的钱,所以我们可以设置状态为用最少用多少枚硬币来拼出X。
第三步:写出转移方程
通过状态的确认和决策的不同可以写出来转移方程。
定义:状态转移方程,是动态规划中本阶段的状态往往是上一阶段状态和上一阶段决策的结果。
我们刚刚已经找出来状态最少用多少枚硬币来拼出X,也分析出来了当大问题的答案为最优时,子问题的答案也为最优。这时就可以从子问题入手。
int dp[28],coin[3]={2,5,7};
//dp数组下标对应x元钱,其中要储存的值为最少用多少枚硬币来拼出x
//coin数组中储存3枚硬币的面值,分别为2元,5元,7元
我们要分析出子问题的答案来组成大问题的答案。
dp[27]=min{dp[27-2]+1,dp[27-5]+1,dp[27-7]+1}
//伪代码,计算27的最优答案,则寻找子问题中最优的答案。
//这就是状态转移方程(子问题答案+1就是加上状态改变的这一枚)
dp[x]=min{dp[x-coin[0]]+1,dp[x-coin[1]]+1,dp[x-coin[2]]+1}
//↑伪代码
for(int i=0;i<3;i++){
dp[x]=min(dp[x],dp[x-coin[i]]+1);
}
//通过循环来进行三次比较,找到问题的最优解。
第四步:寻找边界条件
通过边界条件的寻找来终止条件。
通过刚刚分析,我们要解决大问题要找到子问题的答案,但是子问题是存在边界的,所以我们需要找到边界,也就是最小的子问题。
例如:
//当x等于1时,子问题已经是负数,所以我们需要考虑负数的问题
dp[1]=min{dp[1-2]+1,dp[1-5]+1,dp[1-7]+1}
//使用if判断x和coin[i]的关系
for(int i=0;i<3;i++){
if(x>=coin[i]){
dp[x]=min(dp[x],dp[x-coin[i]]+1);
}
}
这里要注意一点:需要找到问题的最小值,则在每次循环把dp数组初始化到一个极大值,dp的初状态为0,还需要手动将dp[0]设置为0作为初始条件。
const int MAX=2e7;
dp[0]=0;
for(int x=1;x<=27;x++){
dp[x]=MAX;
//先将dp[x]赋值为最大值
for(int i=0;i<3;i++){
if(x>=coin[i]&&dp[x-coin[i]]!=MAX){
//这里加一个判断条件判断dp子问题的状态是否为MAX,如果为MAX则代表无解。也方便我们之后对无解结论的判断。
//&&运算符如果前置条件错误则不会继续判断,避免第二个判断条件越界的可能。
dp[x]=min(dp[x],dp[x-coin[i]]+1);
}
}
}
//此时dp数组的状态就是其中要储存的值为最少用多少枚硬币来拼出x
//输出27就是最少用多少枚硬币来拼出27
cout<<dp[27];
总代码:
#include <bits/stdc++.h>
using namespace std;
int dp[28],coin[3]={2,5,7};
const int MAX=2e7;
int main(){
dp[0]=0;
for(int x=1;x<=27;x++){
dp[x]=MAX;
for(int i=0;i<3;i++){
if(x>=coin[i]&&dp[x-coin[i]]!=MAX){
dp[x]=min(dp[x],dp[x-coin[i]]+1);
}
}
}
cout<<dp[27];
}
总结:
动态规划先把大问题分解成子问题,当大问题产生最优解的情况,子问题肯定同时产生最优解。所以可以通过子问题最优解来组成大问题最优解的答案。
如有错误或者建议,欢迎大佬指正!
借鉴:https://www.bilibili.com/video/BV1xb411e7ww?from=search&seid=4061765853735783106&spm_id_from=333.337.0.0