引题
首先,我们看一下这道题(此题目来源于北大POJ):
问:在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 – 99
输入格式:
5 //输入数据的行数
7 //输入(下三角)数据
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出格式:
30 //输出路线的最大值
分析
数据存放:二维数组
考虑到需要储存数据且为分行列存储数据,首先想到二维数组
我们这里使用num[i][j]来储存数据;cnt[i][j]来储存求和之后的数据(后面会讲到)
ans来储存最后的最大值;n为所输入的行数;
索引
利用行列去标记每两个索引标记的数据(这里我们使用i,j来表示行和列)
解题
解题法一(顺推法)
由数据
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
我们可以得到
7=========>7
3 8========>10 15
8 1 0=======>18 16 15
2 7 4 4======>20 25 20 19
4 5 2 6 5=====>24 30 27 26 24
显然左边的数据就是我们的num[i][j];右边的则是cnt[i][j];
所以我们就能得到我们的递推式:
cnt[i][j] = num[i][j] + max (cnt[i-1][j-1] , cnt[i-1][j]);
根据上面这个递推式,我们就能很快地得出代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll num[1001][1001],n,cnt[1001][1001],ans=0;
int main()
{
std::ios::sync_with_stdio(false);
memset(num,0,sizeof(num));
memset(num,0,sizeof(cnt));
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>num[i][j];
}
}
cnt[1][1]=num[1][1];
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
cnt[i][j]=num[i][j]+max(cnt[i-1][j],cnt[i-1][j-1]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if(ans<cnt[i][j]){
ans=cnt[i][j];
}
}
}
cout<<ans<<endl;
}
解法二(逆推法)
7=========>30
3 8========>23 21
8 1 0=======> 20 13 10
2 7 4 4======>7 12 10 10
4 5 2 6 5=====>4 5 2 6 5
逆推,是在顺推的基础上进行方向的颠倒:
for(int i=n-1;i>=1;i--)//从后行向前行推进;
{
for(int j=1;j<=i;j++)
cnt[i]][j]=num[i][j]+max(cnt[i-1][j-1],cnt[i-1][j]);
}
cout<<cnt[1][1]<<endl;//故最后的最大值是在cnt[1][1];
解法三(递归)
从num(i, j)出发,下一步只能走num(i+1,j)或者num(i+1, j+1)。故对于n行的下三角形,我们得出递归式:
if ( i == n)
cnt(i,j) = D(i,j) ;
else
cnt(i, j) = max{ cnt(i+1,j), cnt(i+1,j+1) } + cum(i,j);
因此我们能够得到完整程序:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll num[1001][1001], n, cnt[1001][1001], ans = 0;
ll Cnt(int i, int j) {
if (cnt[i][j] != -1)
return cnt[i][j];
if (i == n) {
cnt[i][j]=num[i][j];
}
else {
cnt[i][j]=max(Cnt(i + 1, j), Cnt(i + 1, j+1)) + num[i][j];
}
return cnt[i][j];
}
int main() {
int i, j;
cin >> n;
for (i = 1; i <= n; i++)
for (j = 1; j <= i; j++) {
cin >> num[i][j];
cnt[i][j] = -1;
}
cout << Cnt(1, 1) << endl;
}
优化
写程序过程中我们不难发现,求和cnt[i][j]可以改成一维数组cnt[i]来节省空间;
我们以逆推为例子:
7=========> 30 21 10 10 5
3 8========> 23 21 10 10 5
8 1 0=======> 20 13 10 10 5
2 7 4 4======> 7 12 10 10 5
4 5 2 6 5=====>4 5 2 6 5
这样的话,我们只需要把后半部分改成:
for(int i=1;i<=n;i++)//先将最后一组数据赋给cnt
{ cnt[i]=num[n][i];
}
for(int i=n-1;i>=1;i--){
for(int j=1;j<=i;j++){
cnt[j]=num[i][j]+max(cnt[j],cnt[j+1]);
}
}
cout<<cnt[1]<<endl;
最后就是我们优化后的程序:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,num[1001][1001],cnt[1001],ans;
int main()
{
std::ios::sync_with_stdio(false);
memset(num,0,sizeof(num));
memset(cnt,0,sizeof(cnt));
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>num[i][j];
for(int i=1;i<=n;i++)
cnt[i]=num[n][i];
for(int i=n-1;i>=1;i--)
for(int j=1;j<=i;j++)
cnt[j]=num[i][j]+max(cnt[j],cnt[j+1]);
cout<<cnt[1];
return 0;
}
好了这就是算法动态规划中有关于最短路之一的解决方法;