題目要求:
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)確定狀態轉移方程。就是從一個狀態轉移到另一個狀態的過程。
轉載請注明出處。新人求關注。