北大C++教程
例题一、数字三角形(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)到底边的各条路径中,
最佳路径的数字之和。
问题:求 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);
//取下一行相邻两个点最大值再加上改点的值D(r,j)
数字三角形的递归程序:
#include <iostream>
#include <set>
#include <string>
#include <algorithm>
using namespace std;
#define MAX 101
int D[MAX][MAX];
int n;
int maxSum[MAX][MAX];
int MaxSum(int i, int j)
{
if (i == n)//第n行,即最后一行
return D[i][j];
return maxSum[i][j] = max(MaxSum(i+1, j+1), MaxSum(i+1,j)) + D[i][j];
}
int main(){
cin >> n;
for (int i = 0; i < MAX; i++)
{
for (int j = 0; j < MAX; j++)
{
cin >> D[i][j];
maxSum[i][j] = -1;
}
cout << MaxSum(1, 1) << endl;
}
return 0;
}
AC不了。。。因为重复计算多。导致时间超时
如果采用递规的方法,深度遍历每条路径,存在大量重复计算。则时间复杂度为 2^n,对于 n = 100行,肯定超时。
关键是:避免重复计算
改进:
如果每算出一个MaxSum(r,j)就保存起来,下次用到其值的时候直接取用,则可免去重复计算。那么可以用O(n^2)时间完成计算。因为三角形的数字总数是 n(n+1)/2
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX 101
int D[MAX][MAX];
int n;
int maxSum[MAX][MAX];
int MaxSum(int i, int j)
{
if (maxSum[i][j] != -1) //判断在maxSum里是否存在计算过的数,避免重复计算
return maxSum[i][j];
if (i == n)//第n行,即最后一行
return 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]; //maxSum里不存在的数就先存起来待用
}
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;//把maxSum初始化为-1,这样只有时-1就代表没有计算过
}
}
cout << MaxSum(1, 1) << endl;
return 0;
}
递归特别大的时候可能导致堆栈溢出,考虑把递归转成递推
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
从下往上考虑,先求出最后一行的maxSum==>4 5 2 6 5
再往上一层==> 7 12 10 10
….一直算到最顶端即可
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX 101
int D[MAX][MAX]; int n;
int maxSum[MAX][MAX];
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 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;
}
//“人人为我”递推型动归程序
接着考虑空间优化:
没必要用二维maxSum数组存储每一个MaxSum(r,j),只要从底层一行行向上递推,那么只要一维数组maxSum[100]即可,即只要存储一行的MaxSum值就可以。
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX 101
int D[MAX][MAX];
int n; int * maxSum;
int main(){
int i,j;
cin >> n;
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
cin >> D[i][j];
maxSum = D[n]; //maxSum指向第n行
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;
}
递归到动规的一般转化方法
递归函数有n个参数,就定义一个n维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始,逐步填充数组,相当于计算递归函数值的逆过程。
下次记录下动态规划的问题。