动态规划基本思路
动态规划(dynamic Programming,DP)是一种非常基础而且重要的算法,是运筹学的一个分支,主要运用在决策过程当中的最优化过程。
离散数学中,求解传递闭包时候采用的warshall算法就是DP问题的一个典型应用,在之后的博客当中会更新此部分的内容
引入
先来看数字三角形这一条题目
数字三角形
-
总时间限制:
1000ms
-
内存限制:
65536kB
-
描述
73 88 1 02 7 4 44 5 2 6 5(图1)
图1给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。 注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的那个数或者右边的那个数。 -
输入
输入的是一行是一个整数N (1 < N <= 100),给出三角形的行数。下面的N行给出数字三角形。数字三角形上的数的范围都在0和100之间。
-
输出
输出最大的和。
-
样例输入
5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5
-
样例输出
30
分析
一眼看过去,显然这是一个递归的问题,只需要把每个位置到底部的最长距离通过递归的方式算出来,就完事大吉了。实现代码也非常简单。
但仔细分析上面的递归过程,很显然,我们会有非常多的重复计算,O(2^n)导致会超时
再次仔细分析,我们冲最后的一行开始往上递推,很显然可以写出递推公式
maxSum[i-1][j]=max(maxSum[i][j+1],maxSum[i][j])+D[i][j];
最后,再经过一些空间上的优化就OK了。
时间复杂度也降为O(n^2)
//数字三角形
#include <iostream>
#include <cmath>
#include <algorithm>
#define MAX 101
using namespace std;
int *maxSum;
int D[MAX][MAX];
int N;
int main()
{
cin>>N;
for(int i=1;i<=N;++i)
for(int j=1;j<=i;++j)
cin>>D[i][j];
maxSum=D[N];//指向第N行先
for(int i=N-1;i>=1;--i)
for(int j=1;j<=i;++j)
maxSum[j]=max(maxSum[j+1],maxSum[j])+D[i][j];
cout<<maxSum[1]<<endl;
return 0;
}
动态规划的思路
碰到DP的题目,一开始都比较显然可以想到递归的程序。但往往采用递归会出现一些问题,但与DP上思路上相似。
递归转化为DP的一般方法
- 递归函数有n个参数,就一定要定义n维数组,数组的下标就是递归函数的参数取值范围,数组元素的数值为递归函数的返回值,这样子就可以从边界值开始,足部填充数组,相当于计算递归函数值的逆过程。
DP的一般思路
- 原问题分解成为子问题(和递归很相似)
- 子问题一旦求解出来,我们就要保存,避免重复计算
- 确定状态(如上面题目当中的数字)
- 一般为多维数组来进行存放
- 确定一些初始状态(边界状态值)
- 写出状态转移方程
动态规划解决问题的特点
并不是所有问题都是可以DP解决的
- 问题有最优子结构
- 无后效性
- 当前状态一旦确定下来,此后的演变只和这几个状态值有关(与演变的路径无关)