动态规划(dynamic programming)是通过组合子问题的解而解决整个问题的,这里的programming是一种规划,DP适用于子问题不是独立(俩个子问题不共享资源即是独立)的问题,即各个子问题包含公共子问题,DP只对于子问题求解一次,并将求解的值保存在一张表中,避免了重复求解相同子问题的操作。DP通常用于最优化的问题,通常是一个问题有很多的可行解,每一个解含有一个值,我们希望找到一个最优的解(最大或是最小)。
DP一般设计的4个步骤:
1.描述最优解的结构
2.递归定义最优解的值
3.按照自底向上的方式求解最优解的值
4.有计算结果构造最优解
DP性质:对于一个问题是否可以用DP解决是需要判断的,如果一个问题的最优解当中包含了子问题的最优解,即该问题具有最优子结构性质,提示我们用DP可能会解决问题。而当一个递归算法不断调用同一问题的时候,此时该最优问题包含了重叠子问题。同时满足二者,此时我们可以用DP去求解问题。
下面针对于一个简单的DP小例子进行描述:
数字三角形问题,问题描述:
对于输入一个数字三角形,每行的整数个数对应其行数,在三角形中需找一条从顶到底的路径,使得路径上的数字之和最大,输出最大值,三角形行数小于100,路径上的每一步只可以向下或右下去走。给出一个问题的样例:
求出上述例子的最大解?
分析:
上述数据可以用一个二维数组进行描述,即
D[i][j] 第i行第j个数 从D[i][j]下一步走D[i+1][j]或D[i+1][j+1] Max[i][j] 表示从D[i][j]到底端的路径中最佳路径数字之和。
如果只有一行的时候 Max[i][j] = D[i][j],否则Max[i][j] = max(Max[i+1][j],Max[i+1][j+1])+D[i][j];
下面给出递推型DP:
#include <iostream> #include <algorithm> #include <cstdio> using namespace std; const int num = 101; int D[num][num]; int n; int Max[num][num]; int main() { int i,j; cin>>n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin>>D[i][j]; for(int k=1;k<=n;k++) Max[n][k] = D[n][k]; for(int i=n-1;i>=1;i--){ for(int j=1;j<=i;j++) Max[i][j] = max(Max[i+1][j],Max[i+1][j+1])+D[i][j]; } cout<<Max[1][1]<<endl; return 0; }
输出的结果即为:30
上述算法时间复杂度o(n^2),空间复杂度o(n^2),有图表可知,最后的有很大的内存空间是浪费的,并不会全部利用上,当行数很大的时候浪费明显,我们可以将计算出来的每个子问题的解存放在一个一维数组中,覆盖性的写入,从而最后的解即为数组的头元素。空间复杂度即为o(n).
演示如下图:
即为所求解。
#include <iostream> #include <algorithm> #include <cstdio> using namespace std; const int num = 101; int D[num][num]; int n; int *Max; int main() { int i,j; cin>>n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin>>D[i][j]; Max = D[n]; for(int i=n-1;i>=1;i--){ for(int j=1;j<=i;j++) Max[j] = max(Max[j],Max[j+1])+D[i][j]; } cout<<Max[1]<<endl; return 0; }
最长公共子串:
俩个字符串s1,s2,用maxlen(i,j)表示s1左边i个字符,s2左边j个字符的最长公共子序列长度。(解的状态)
如果俩个子串任何一个为空,公共子串的长度为0;代码如下:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> char s1[100]; char s2[100]; int maxlen[100][100]; using namespace std; int main() { while(cin>>s1>>s2){ int len1 = strlen(s1); int len2 = strlen(s2); int i,j; for(i=0;i<=len1;i++) maxlen[i][0] = 0; for(j=0;j<=len2;j++) maxlen[0][j] = 0; for(i=1;i<=len1;i++){ for(j=1;j<=len2;j++){ if(s1[i-1]==s2[j-1]) maxlen[i][j] = maxlen[i-1][j-1]+1; else maxlen[i][j] = max(maxlen[i-1][j],maxlen[i][j-1]); } } cout << maxlen[len1][len2] << endl; } return 0; }
最长公共子串并不要求,子串是连续的。
动态规划 以及相应的实例