动态规划 数字三角形(递归,递推,记忆化搜索)
题目要求:
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的数字三角形中寻找在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。
三角形的行数大于1小于等于100,数字为 0 - 99
输入格式:
5 //三角形行数。下面是三角形
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
解题思路:
用二维数组存放数字三角形
D[r][j] //表示第i行第j个元素的数值;
MaxSum(i,j) //表示从根部到第i行最大路径的,所有数值的最大和;
用递归的思想,在D(i,j)位置,下一个能走的位置为D(i+1,j)和D(i+1,j+1),进行递归
MaxSum(i,j)=max(MaxSum(i+1,j),MaxSum(i+1,j+1))+D[i][j];
递归代码如下:
1 #include "stdio.h" 2 #define MAX 100 3 4 int n; 5 int D[MAX][MAX]; 6 7 int main(){ 8 9 int i,j; 10 11 printf("Input N:\n"); 12 scanf("%d",&n); 13 14 for(i=1;i<=n;i++) 15 { 16 for(j=1;j<=i;j++) 17 { 18 scanf("%d",&D[i][j]); 19 } 20 printf("\n"); 21 } 22 23 printf("Max is %d",MaxSum(1,1)); 24 } 25 26 int MaxSum(int i,int j) 27 { 28 if(i==n) 29 return D[i][j]; 30 int x=MaxSum(i+1,j); 31 int y=MaxSum(i+1,j+1); 32 return max(x,y)+D[i][j]; 33 } 34 35 int max(int m,int n){ 36 return m>n?m:n; 37 }
算法评价,因为采用的是递归的算法,深度遍历每条路径,存在大量的重复运算,效率较低,如果共用n行,时间复杂度O(n)=2的n次方。
如:在计算时d(3,2)被重复调用
d(2,1) 的计算会调用--> d(3,1) , d(3,2)
d(2,2) 的计算会调用--> d(3,2) , d(3,3)
所以我们对代码进行了改进
记忆化搜索,记忆型动态规划程序,引入一个maxSum[i][j]用来存放每一个MaxSum(i,j)的值,等到需要用的时候,记忆化搜索使用。这时的时间复杂度O(n)= n(n+1)/2
动态化代码如下:
1 #include "stdio.h" 2 #define MAX 100 3 4 int n; 5 int D[MAX][MAX]; 6 int maxsum[MAX][MAX]; 7 8 int main(){ 9 10 int i,j; 11 12 printf("Input N:\n"); 13 scanf("%d",&n); 14 15 for(i=1;i<=n;i++) 16 { 17 for(j=1;j<=i;j++) 18 { 19 scanf("%d",&D[i][j]); 20 maxsum[i][j]=-1; 21 } 22 } 23 24 printf("Max is %d",MaxSum(1,1)); 25 } 26 27 int MaxSum(int i,int j) 28 { 29 if(maxsum[i][j]!=-1) 30 return maxsum[i][j]; 31 int x=MaxSum(i+1,j); 32 int y=MaxSum(i+1,j+1); 33 maxsum[i][j]=max(x,y)+D[i][j];//用来存储到(i,j)位置的值。 34 return maxsum[i][j]; 35 } 36 37 int max(int m,int n){ 38 return m>n?m:n; 39 }
递推方法:
主要思路,从最后一行开始向上递推,判断下方,和右下谁的数大,选择之后加到上一行,生成一个新的数。
如
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
最后一行,4,5比较,将5+2=7,放在第四行的第一个位置,生成新的一行。
7 12 10 10
4 5 2 6 5
最终得到的数字三角形为:
30
23 21
20 13 10
7 12 10 10
4 5 2 6 5。最后得到最大数30。
代码如下
1 #include "stdio.h" 2 #define MAX 100 3 4 int n; 5 int D[MAX][MAX]; 6 int maxsum[MAX][MAX]; 7 8 int main(){ 9 10 int i,j; 11 12 printf("Input N:\n"); 13 scanf("%d",&n); 14 15 for(i=1;i<=n;i++) 16 { 17 for(j=1;j<=i;j++) 18 { 19 scanf("%d",&D[i][j]); 20 maxsum[i][j]=-1; 21 } 22 } 23 24 for(i=1;i<=n;i++) 25 maxsum[n][i]=D[n][i]; 26 for(i=n;i>=1;i--) 27 for(j=1;j<=i;j++) 28 maxsum[i][j]=max(maxsum[i+1][j],maxsum[i+1][j+1])+D[i][j]; 29 30 printf("Max is %d",maxsum[1][1]); 31 } 32 33 int max(int m,int n){ 34 return m>n?m:n; 35 }
递推优化:
1.对递推方法还可以进行空间优化,将二维数组maxsum[i][j]该为一维数组maxsum[i]用来存放每次叠加后的一行,节省了空间,时间复杂度不变。
2.直接将加过之后的数存放在D数组的n行,减少了一个自定义的数组。最后输出D[n][1]即可。
总结:
1.递归到动态的一般转换方法:
递归函数有n个参数,就定义一个n维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始,逐步填充数组,相当于计算递归函数值的逆过程。
2.动规解题的一般思路:
1)将原问题分解成若干个子问题。子问题和原问题结构一样,只是规模变小,而每个子问题解决之后,将结果保存起来。
2)确定状态。我们往往将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题,所谓某个“状态”下的“值”,就是这个“状态”所对应的子问题的解。所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。
3)确定一些初始状态(边界状态)的值。在“三角形问题”中,初试状态就是底边状态,初始状态的值就是底边的值。
4)确定状态转移方程。就是从一个状态转移到另一个状态的过程。
转载请注明出处。新人求关注。