(从这里开始,我们要学点新东西啦,动态规划,仍然边说例子,边说思想,从案例中锻炼算法思想。
例题:数字三角形
题目描述
在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。三角形的行数大于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) 到底边的各条路径中,最佳路径的数字之和。
问题:求MaxSum(1,1)
这是一个典型的递归问题。
从 D(r,j) 出发,下一步只能走左下方 D(r+1,j)或者右下方 D(r+1, j+1)。故对于 N 行的三角形:
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<iostream>
#include<algorithm>
using namespace std;
#define MAX 101
int n;
int a[MAX][MAX];
//MaxSum(i,j)表示从第i行第j个元素到最后一行路径,经过的数字之和最大
int maxsum(int i, int j)
{
if (i == n)
return a[i][j];
int x = maxsum(i + 1, j);
int y = maxsum(i + 1, j + 1);
return max(x, y) + a[i][j];
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
cout << maxsum(1, 1)<<endl;
return 0;
}
这个问题看似解决了,但在提交的时候一定会超时,为什么呢,因为存在大量的重复计算。我们继续向下看。
如图所示的三角形,黑色的数字表示该位置的元素,红色数字表示该位置的最大路径计算了几次。
要计算从顶元素7开始的最大路径和,要计算左下方以3为顶的最大路径和,右下角以8为顶的最大路径和。在计算以3为顶的最大路径和时,计算了以1为顶的路径,计算以8为顶的最大路径和时,又计算了以1为顶的路径,计算了两次,再向下计算,又出现了更多了重复。
第1行计算次数:1
第2行计算次数:2
第3行计算次数:4
第4行计算次数:8
……
第n行计算次数:2n-1
则总的时间复杂度为2n,对于n = 100 行,肯定超时。
改进方法
如果每算出一个 MaxSum(r,j) 就保存起来,下次用到其值的时候直接取用,则可免去重复计算。那么可以用O(n2)时间完成计算。因为三角形的数字总数是n(n+1)/2
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX 101
int D[MAX][MAX];
int maxsum[MAX][MAX];
int n;
//MaxSum(i,j)表示从第i行第j个元素到最后一行路径,经过的数字之和最大
int MaxSum(int i, int j)
{
//如果maxsum[i][j]为-1,表示从该节点出发的路径还没算过
//如果不是-1,已经算过了,直接取用就行了
if (maxsum[i][j] != -1)。
return maxsum[i][j];
if (i == n)
maxsum[i][j]=D[i][j];
else
{
int x = MaxSum(i + 1, j);
int y = MaxSum(i + 1, j + 1);
maxsum[i][j] = max(x, y) + D[i][j];
}
return maxsum[i][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 << MaxSum(1, 1) << endl;
}