前言:floyd 算法求最短路(边权可为负)很优美,四行代码就搞定了。今天做了一个题,可以用 floyd 做,但是要最短路的路径。在网上搜了一阵,代码到是有,但是没有解释,为何是这样的?于是,手推了一遍,写了这篇博客。
Floyd 如何记录最短路的路径并输出呢?
不像 dijkstra 和 spfa,是一个点一个点加进去的,直接 pre 数组往前倒,倒至起点就行了。floyd 是基于动态规划,这怎么记录路径呢?
开一个 pass数组,pass[i][j]
表示:更新从 i 到 j 的最短路径时,经过的一个中转点。
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dist[i][j]>dist[i][k]+dist[k][j]){
dist[i][j]=dist[i][k]+dist[k][j];
pass[i][j]=k;
}
}
在这个图中,很容易看出,从 1 到 6 之间的最短路径是标红的那几条边。
二重循环所有点,我们输出 pass 数组:
0 0 0 3 4 5
0 0 0 0 4 5
0 0 0 0 4 5
0 0 0 0 0 5
0 0 0 0 0 0
0 0 0 0 0 0
可以看出,pass[1][6] 是 5,pass[1][5] 是 4,pass[1][4] 是 3。那么,最终 pass[i][j]
中存的就是从i到j的最短路径中的最后一个点。
而我们最终输出路径的思路就是,不断分段最短路径! 最后输出所有的点。
原理:由 i 到 j 的最短路径中的一点 k,将最短路径分段为从 i 到 k 的最短路径 和 从 k 到 j 的最短路径,最短路径就为
从i到k的最短路径
+从k到j的最短路径
,一直分段,直到分到 i 和 j 为同一点,停止
可能现在你有些迷糊,我们直接看代码吧!
//pass[i][j]:从i到j最短路径中经过的一点k
void print(int i,int j)
{
if(i==j) return; //分段到同一点,停止
if(pass[i][j]==0) cout<<i<<" "<<j<<endl;//i和j直接相连,则就是最短路径中的边 (i到j最短路径不经过任何点)
else{
print(i,pass[i][j]); //分段到从i到k的最短路径,
//输出从i到k最短路径中的所有点(一定都在从i到j的最短路径中)
print(pass[i][j],j); //分段到从k到j的最短路径,
//输出从j到k最短路径中的所有的点
}
}
就是一个递归的过程。
我们用上面的图模拟一下:
-
首先,从 1 到 6 的最短路径,
pass[1][6]
中存的是:该最短路径中的最后一个节点 5,即,k = 5。 -
那么,递归(分段) 到,从 1到5的最短路 和 从5到6的最短路。
1、从1到5的最短路,pass[1][5]=4
,则又分段为从1到4的最短路和从4到5的最短路。
2、从1到4的最短路,pass[1][4]=3
,则又分段为从1到3的最短路和从3到4的最短路。
3、pass[1][3]=0
!如图,1和3直接相连!那么1和3都是最短路中的点,输出就行了! -
回溯:
1、从3到4的最短路,pass[3][4]=0
!3和4直接相连,3和4都是最短路中的点,输出!
2、从4到5的最短路,pass[4][5]=0
!4和5直接相连,4和5都是最短路中的点,输出!
3、从5到6的最短路,pass[5][6]=0
!5和6直接相连,5和6都是最短路中的点,输出!
所以,最终输出的就是:
1 3
3 4
4 5
5 6
依次连接就是从1到6的最短路径了!
总体代码:
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dist[i][j]>dist[i][k]+dist[k][j]){
dist[i][j]=dist[i][k]+dist[k][j];
pass[i][j]=k;
}
}
void print(int i,int j)
{
if(i==j) return;
if(pass[i][j]==0) cout<<i<<" "<<j<<endl;
else{
print(i,pass[i][j]);
print(pass[i][j],j);
}
}
int main(){
··· //一顿初始化,输入数据
floyd();
print(1,n); //输出从1到n的最短路径中的所有点(边)
return 0;
}
最后,附上我今天做的这道题:RoadBlock S 。