学习算法,个人觉得还是对照着例题来理解效果做好,算法概念太学术味太浓,个人不太喜欢。今天介绍一个动态规划入门例题。这道题来自北大的POJ。题目如下:
数字三角形(POJ1163)
在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99
输入格式:
5 //表示三角形的行数 接下来输入三角形
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
要求输出最大和
我们来分析一下这道题该怎么做
首先肯定要一个二维数组来存放三角形
然后我们用D( r, j) 来表示第 r 行第 j 个数字(r , j从1开始算)
我们用maxSum(r,j)表示D(r,j)到底边各条路径的最大值
最后就是要求D(1,1)
我们可以用递归来求解
从D(r,j)出发,下一步只能走D(r+1,j)或D(r+1,j+1) ,可以得到如下递归式
if ( r == N)
MaxSum(r,j) = D(r,j)
else
MaxSum( r, j) = Max{ MaxSum(r+1,j), MaxSum(r+1,j+1) } + D(r,j)
由上述递归式可以得到如下的代码:
#include
#include
using namespace std;
int D[50][50];
int n;
int dp(int r, int j) {
if (r == n) {
return D[r][j];
}
int x = dp(r + 1, j);
int y = dp(r + 1, j + 1);
return max(x, y) + D[r][j];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> D[i][j];
}
}
cout << dp(1, 1) << endl;
system("pause");
return 0;
}
不过这段代码中有很多重复计算的地方,看图
当计算7的时候,要往下依次递归计算3、8,计算3的时候要递归计算8、1,计算8(7下面右边 那个)的时候又要计算一次1,其它的以此类推。这要就导致了很多不必要的重复计算,我们可以保留这些计算结果作为判断是否计算过的依据,
改进过后的代码如下:
#include
#include
using namespace std;
int D[50][50];
int maxSum[50][50];
int n;
int dp(int r, int j) {
if (maxSum[r][j] != -1) {
return maxSum[r][j];
}
if (r == n) {
maxSum[r][j] = D[r][j];
}
else {
int x = dp(r + 1, j);
int y = dp(r + 1, j + 1);
maxSum[r][j] = max(x, y) + D[r][j];
}
return maxSum[r][j];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> D[i][j];
maxSum[i][j] = -1;
}
}
cout << dp(1, 1) << endl;
system("pause");
return 0;
}
因为递归总是需要使用大量堆栈上的空间,很容易造成栈溢出,我们现在就要考虑如何把递归转换为递推,让我们一步一步来完成这个过程。
代码如下:
#include
#include
using namespace std;
int D[50][50];
int maxSum[50][50];
int n;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> D[i][j];
}
}
for (int i = 1; i <= n; i++) {
maxSum[n][i] = D[n][i];
}
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
maxSum[i][j] = max(maxSum[i + 1][j], maxSum[i + 1][j + 1]) + D[i][j];
}
}
cout << maxSum[1][1] << endl;
system("pause");
return 0;
}
到这里是不是觉得已经到极致了?错,还可以继续优化,不过不是时间上的优化,是空间上的优化,我们可以只用一个一维数组来记录已经得到的值。只要从底层一行行向上递推,那么只要一维数组maxSum[100]即可,即只要存储一行的MaxSum值就可以。看图。
依照上面的方式,我们可以写出如下代码:
#include
#include
using namespace std;
int D[50][50];
int maxSum[50];
int n;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> D[i][j];
}
}
for (int i = 1; i <= n; i++) {
maxSum[i] = D[n][i];
}
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
maxSum[j] = max(maxSum[j], maxSum[j + 1]) + D[i][j];
}
}
cout << maxSum[1] << endl;
system("pause");
return 0;
}
至此,该问题已经是最优了。
如有错误,请指正。