求解三角形最小路径问题
一、【问题描述】:
给定 高度为n的一个整数三角形,找出从顶部到底部的最小路径和,只能向先移动相邻的结点。首先输入n,接下来的1~n行,第i行输入i个整数,输出分为2行,第一行为最小路径,第2行 为最小路径和。
例如,下图是一个n=4的三角形,输出的路径是2,3, 5, 3最小路径是13。
2
3 4
6 5 7
8 3 9 2
二、【问题求解】:
将三角形采用二维数组a存放
2
3 4
6 5 7
8 3 9 2
从顶部到底部查找最小路径,那么节点(i,j)的前驱结点只有(i-1,j-1)和(i-1, j)两个
使dp[i][j]表示从顶部a[0][0]查找到(i,j)节点时候的最小路径和,显然这里有两个边界,即第一列和对角线,达到它们中结点的路径只有一条而不是常规的两条。这个结论不能从我们构造的二维数组中观察而是要从示例中才能观察得出结论。
可得状态转移方程为:
dp[0][0]=a[0][0](顶部边界)
dp[i][0]=dp[i-1][0]+a[0][0] (1<=i<=n-1)
dp[i][i]=dp[i-1][i-1]+a[i][i](1<=i<=n-1)
dp[i][j]=min{dp[i-1][j-1],dp[i-1]}+a[i][j]
最后求出来的最小路径和为ans=min(dp[n-1][j]),以及对应的列号k
由dp求出一条路径的方法:
用pre[i][j]表示查找到(i,j)结点时最小路径上的前驱结点,由于前驱结点只有两个,即(i-1,j-1)和(i-1,j),用pre[i][j]记录前驱结点的列号即可。
在求出ans之后,通过pre[n-1][k]反推求出方向路径,最后正向输出该路径。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int a[100][100];
int n;
int dp[100][100];
int pre[100][100];
int ans=0;
int search(){ //求解最小路径和ans
int i,j;
dp[0][0]=a[0][0];
for(i=1;i<n;i++){ //考虑第1列的边界
dp[i][0]=dp[i-1][0]+a[i][0];
pre[i][0]=i-1;
}
for(i=1;i<n;i++){ //考虑对角线的边界
dp[i][i]=a[i][i]+dp[i-1][i-1];
pre[i][i]=i-1;
}
for(i=2;i<n;i++){ //考虑其他有两条达到路径的结点
for(j=1;j<i;j++){
if(dp[i-1][j-1]<dp[i-1][j]){
pre[i][j]=j-1;
dp[i][j]=a[i][j]+dp[i-1][j-1];
}
else{
pre[i][j]=j;
dp[i][j]=a[i][j]+dp[i-1][j];
}
}
}
ans=dp[n-1][0];
int k=0;
for(j=1;j<n;j++){ //求出最小ans和对应的列号k
if(ans>dp[n-1][j]){
ans=dp[n-1][j];
k=j;
}
}
return k;
}
void Dispath(int k) //输出最小和路径
{
int i = n - 1;
vector<int> path;//存放逆路径向量path
while (i >= 0) //从(n-1,k)结点反推求出反向路径
{
path.push_back(a[i][k]);
k = pre[i][k]; //最小路径在前一行中的列号
i--; //在前一行寻找
}
vector<int>::reverse_iterator it; //定义反向迭代器
for (it = path.rbegin(); it != path.rend(); ++it)
cout << *it << " "; // 反向输出构成正向路径
}
int main(){
int k;
memset(pre,0,sizeof(pre));
memset(dp,0,sizeof(dp));
scanf("%d",&n);
for(int i=0;i<n;i++)
for(int j=0;j<=i;j++)
scanf("%d",&a[i][j]);
k=search();
Dispath(k);
printf("最小路径和为:")
printf("%d\n",ans);
return 0;
}
参考自《算法设计与分析》第二版 李春葆