经典解法(动态规划-最短路线问题)

引题

 首先,我们看一下这道题(此题目来源于北大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;
}

好了这就是算法动态规划中有关于最短路之一的解决方法;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值