动态规划 之 数字三角形

北大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维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始,逐步填充数组,相当于计算递归函数值的逆过程。

下次记录下动态规划的问题。

猜你喜欢

转载自blog.csdn.net/chaowang1994/article/details/80369398