作为一个菜鸡,研究了几天的DP,把经典例题研究了几遍,现在,我在这发表一下自己的菜鸡见解,记录下我对DP的理解。
DP里面少不了递归,当然也能混在搜索里面构成记忆化搜索作为优化,也可以用递推来动态规划。
具体你要我说动态规划是个什么东西,我也只能说说自己的理解:
满足条件:
- 最优子结构情况 (一个问题可以拆分成子问题来解决。很多的DP都涉及到了01背包问题这种思想,比如对待这个状态的解决方法,他的下一个状态是,得到与抛弃两种情况的和,即为背包问题的拿与不拿)
- 无后效性(即当前情况的结果对后面的结果没有影响,也就是说后面值的改变对当前情况没有任何影响)
几种情况:(暂时就先这么写,日后等我见识了更多的dp种类,我再来改)
- 记忆化递归————作为递归代码的一种优化,减少时间复杂度。
- 记忆化搜索————在DFS这种极其暴力的情况下,爆栈的情况很容易发生,采用记忆化的情况来处理,也算是一种动态规划。
- 分支递归————这个就是背包问题的概述,取或者不取的问题,一分二,二分四,2的n次方复杂度。这时候也是要记忆化来优化
- 递推—————这种题目可以用滚动数组来做,做到不断的覆盖。
综上,这个动态规划很重要的就是记忆化的操作。
接下来是我学习DP的第一道入门题目
输入格式:
5//三角形的行数。下面是三角形
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出最大和
题目解析:
假定maxsum(i,j)表示从i,j这个点到最后一行的最大和
D(i,j)表示i行j列的数字大小
先设初始值i与j都为0,表示从(0,0)这个点开始到最后一行的最大和。
那么可以分解为(0,0)的下和右下两个点到底部的最大和,其中最大的一个数加上D(0,0)本身的数字
能够得到:maxsum(i,j)=maxsum( maxsum(i-1,j) , maxsum(i-1,j-1) )+D(i,j)
这样就能得到初步代码:
#include<iostream>
using namespace std;
#define MAX 101
int D[MAX][MAX];
int n;
int maxsum(int i,int j)
{
if(i==n)
return D[i][j];
int x=maxsum(i+1,j);
int y=maxsum(i+1,j+1);
return max(x,y)+D[i][j];
}
int main()
{
int i,j;
cin>>n;
for(i=1;i<=n;i++)
{
for(j=1;j<=i;j++)
{
cin>>D[i][j];
}
}
cout<<maxsum(1,1)<<endl;
}
上面这个代码对于n比较小的情况是可行的,但是,时间复杂度是2的n次方级别,因为其中递归调用涉及到了重复计算,比如下图的红色数字为计算次数。
那么怎样进行优化呢?
因为是重复计算,所以我们只需要对于重复计算的情况保存下来,下次要用到这个重复计算的值的时候,直接调用就可以了。这样就会大大节省时间。优化时间复杂度。
优化步骤:
- 建立数组存放已经算出来的值,并且全部初始化。
- 寻找递归终止条件
以下是优化代码
#include<iostream>
#include<algorithm>
using namespace std;
int D[105][105];
int n;
int maxarray[105][105]={0};
int MAX;
int dg(int x,int y)
{
if(maxarray[x][y])return maxarray[x][y];//如果这种情况计算过,直接返回
if(x==n)maxarray[x][y]=D[x][y];//如果当前行数就是最后一行,那么只需要把当前位置的数字赋值给最大的情况
else
{
int p=dg(x+1,y);
int q=dg(x+1,y+1);
maxarray[x][y]= max(p,q)+D[x][y];
}
return maxarray[x][y];
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
{
cin>>D[i][j];
}
}
cout<<dg(0,0);
}