**
动态规划算法总结
**
前言
动态规划的内容在各种算法比赛或大厂面试中占据的不少的部分,所以对动态规划算法的学习显得尤为重要。于是,在B站学习了九章算法的动态规划讲解,决定以此为基础对学习内容进行整理与总结
一、概念理解
我的理解为:将一个大的问题化成一个子问题,并将子问题化成更小的子问题,直到问题变成可以简单解决的问题。每一个子问题的输出结论,是更大问题的输入。
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
对比于分治和递归,动态规划算法更快,有更高的效率。
二、题目特点
题目的特点也就是题目类型,有以下几种:- 计数问题
- 最值问题
- 存在性问题
三、解题步骤
1.确定状态:
(1)开数组
(2)数组每个元素的含义
(3)两个意识:最后一步和子问题
2.转移方程
3.初始条件和边界情况
(1)初始条件:用转移方程算不出来的,需要手工定义
(2)注意不要数组越界
4.计算顺序
要计算F[x]时,右侧的状态必须已知
四、例题练习
1.硬币选择
解题思路:
- 可以知道无论怎样选择,总会有这样的情况:已经选择了n-1枚硬币,且为最少的情况,只需要挑选最后一枚硬币使硬币数最少。
- 可以得到转移方程:F[x]=min{F[x-2]+1,F[x-5]+1,F[x-7]+1}
- 初始条件:F[0]=0
- 对于自变量为负的时候,将F的值设置为正无穷
代码如下:
#include <stdio.h>
#define N 27/*求解27块钱*/
#define MAX 0x3f3f3f3f/*表示正无穷*/
int min(int x,int y,int z)//求最小值
{
int ret;
if(x<=y)
{
if(x<=z) ret=x;
else ret=z;
}
else{
if(x<=z) ret=y;
else{
if(y<=z) ret=y;
else ret=z;
}
}
return ret;
}
int main()
{
int i,f[N+1];//开数组
f[0]=0;//初始条件
for(i=1;i<N+1;i++)
{
/*如果越界,则赋值正无穷*/
if(i-2<0) f[i-2]=MAX;
if(i-5<0) f[i-5]=MAX;
if(i-7<0) f[i-7]=MAX;
/*利用转移方程进行求解*/
f[i]=min(f[i-2]+1,f[i-5]+1,f[i-7]+1);//子问题
}
printf("%d",f[N]);
return 0;
}
以上即为对最值型动态规划问题的求解过程
2.走方格问题
解题思路:
- 首先可以知道在到达第(i,j)的格子(右下角)之前只有两种可能,即:在(i-1,j)和(i,j-1)的位置上
- 所以得到转移方程为:F[i][j]=F[i-1][j]+F[i][j-1]
- 初始条件为F[0][0]=1
- 边界情况为:在i=0或者j=0的情况下都只有一种走法
代码如下:
#include <stdio.h>
/*7乘7的方格*/
#define M 7
#define N 7
int main()
{
int f[M][N];//开数组
int i,j;
for(i=0;i<M;++i)//行
{
for(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];//转移方程
}
}
printf("%d",f[M-1][N-1]);
return 0;
}
以上为对计数型动态规划问题的求解过程
3.青蛙跳石头问题
解题思路:
- 首先青蛙在跳到第i个石头时,它先能跳到第j个石头上,且a[i]的值要大于等于j的值
- 即转移方程为:F[i]=OR0<i<=j(F[j] AND i+a[i]>=j)
- 初始条件为:F[0]=1
- 令True:1 False:0
代码如下:
#include <stdio.h>
#define N 100
int main()
{
int i,j,n;
int a[N];
int f[N];
/*输入数据*/
scanf("%d",&n);
for(i=0;i<n;i++) scanf("%d",&a[i]);
/*计算(true:1 false:0)*/
f[0]=1;//赋初值
for(j=1;j<n;j++)
{
f[j]=0;//假设跳不了
for(i=0;i<j;i++)
{
if(f[i]&&i+a[i]>=j)
{
f[j]=1;
break;
}
}
}
if(f[n-1]==1) printf("True");
else if(f[n-1]==0) printf("False");
return 0;
}
以上为对存在型动态规划问题的求解过程
五、总结提升
- 确定状态:(1) 研究最优策略的最后一步 (2) 将问题化为子问题
- 转移方程:根据子问题的定义直接得到
- 初始条件和边界情况:细心,考虑周全
- 计算顺序:利用之前的计算结果
有问题欢迎各位大佬指出
算法系列将持续更新,欢迎关注,一起学习