题目描述
下图给出了一个数字三角形,请编写一个程序,计算从顶至底的某处的一条路径,使该路径所经过的数字的总和最大。
(1)每一步可沿左斜线向下或右斜线向下
(2)1 < 三角形行数 < 100
(3)三角形数字为0,1,…99
输入描述 Input Description
有很多个测试案例,对于每一个测试案例, 通过键盘逐行输入,第1行是输入整数(如果该整数是0,就表示结束,不需要再处理),表示三角形行数n,然后是n行数
输出最大值。
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
- 1
- 2
- 3
- 4
- 5
- 6
样例输出 Sample Output
30
这道题目可以用递归的方法解决
#include <stdio.h>
#define MAX_NUM 100
int D[MAX_NUM + 10][MAX_NUM + 10];
int N; //三角形行数
int MaxSum( int r, int j)
{
if( r == N )
return D[r][j];
int nSum1 = MaxSum(r+1, j);
int nSum2 = MaxSum(r+1, j+1);
if( nSum1 > nSum2 )
return nSum1+D[r][j];
return nSum2+D[r][j];
}
int main(void)
{
int m;
int i,j;
scanf("%d", &N);
for( i = 1; i <= N; i ++ )
for( j = 1; j <= i; j ++ )
scanf("%d", &D[i][j]);
printf("%d", MaxSum(1, 1));
}
这种将一个问题分解为子问题递归求解,并且将中间结果保存以避免重复计算的办法,
就叫做“动态规划”。动态规划通常用来求最优解,能用动态规划解决的求最优解问题,必
须满足,最优解的每个局部解也都是最优的。以上题为例,最佳路径上面的每个数字到底部
的那一段路径,都是从该数字出发到达到底部的最佳路径。
#include <stdio.h>
#include <memory.h>
#define MAX_NUM 100
int D[MAX_NUM + 10][MAX_NUM + 10];
int N; //三角形行数
int aMaxSum[MAX_NUM + 10][MAX_NUM + 10];
int MaxSum( int r, int j)
{
if( r == N )
return D[r][j];
if( aMaxSum[r+1][j] == -1 ) //如果 MaxSum(r+1, j)没有计算过
aMaxSum[r+1][j] = MaxSum(r+1, j);
if( aMaxSum[r+1][j+1] == -1) //如果 MaxSum(r+1, j+1)没有计算过
aMaxSum[r+1][j+1] = MaxSum(r+1, j+1);
if( aMaxSum[r+1][j] > aMaxSum[r+1][j+1] )
return aMaxSum[r+1][j] +D[r][j];
return aMaxSum[r+1][j+1] + D[r][j];
}
int main(void)
{
int m;
int i,j;
scanf("%d", & N);
//将 aMaxSum全部置成-1, 表示开始所有的 MaxSum(r, j)都没有算过
memset(aMaxSum, -1, sizeof(aMaxSum));
for( i = 1; i <= N; i ++ )
for( j = 1; j <= i; j ++ )
scanf("%d", & D[i][j]);
printf("%d", MaxSum(1, 1));
return 0;
}
解题思路
从底向上递推,出最后一行外,每一行的每个点的最大值等于自身加上下面一行对应左右两个点的最大值,从下往上递推,最顶部的即所求。比如下图所示。首先最后一行的最大值就是它本身。倒数第二行第一个数7就是输入的倒二行的第一个数2 + 4 和 2 +5 取最大值 。逐步递推到顶部。
代码实现
#include<stdio.h>
#define Max 101
int D[Max][Max];
int MaxSum(int num){
int i, j;
for(i = num - 1; i >= 1; i --)
for(j = 1; j <= i; j ++){
D[i][j] = (D[i+1][j] > D[i+1][j+1] ? D[i+1][j] : D[i+1][j+1]) + D[i][j];
}
return D[1][1];
}
int main(void)
{
int i, j,num;
printf("请输入三角形的行数: ");
scanf("%d",&num);
printf("请输入三角形%d行数据:\n",num);
for(i = 1; i <= num; i ++)
for(j = 1; j <= i; j ++)
scanf("%d",&D[i][j]);
printf("\n");
printf("%d\n",MaxSum(num));
printf("修改后的矩阵: \n");
for(i = 1; i <= num; i ++){
for(j = 1; j <= i; j ++)
printf("%d ",D[i][j]);
printf("\n");
}
return 0;
}
动态规划规解题的一般思路
1、将原问题分解为子问题
- 把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)
- 子问题的解一旦求出就会被保存,所以每个子问题只需求解一次。
2、确定状态
在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题,所谓某个“状态”下的“值”,就是这个“状态”所对应的子问题的解。
3、确定一些初始状态(边界状态)的值
以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。
4、确定状态转移方程
定义出什么是“状态”,以及在该 “状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的“状态”,求出另一个“状态”的“值” 。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。
能用动规解决的问题的特点
- 问题具有最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。
- 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。