|
|
|
@ -106,21 +106,21 @@ $floyd$ 算法求最短路(边权可为负)很优美,四行代码就搞定
|
|
|
|
|
开一个 $path$数组,$path[i][j]$ 表示:更新从 $i$ 到 $j$ 的最短路径时,经过的一个中转点。
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
void floyd(){
|
|
|
|
|
for(int k=1;k<=n;k++)
|
|
|
|
|
for(int i=1;i<=n;i++)
|
|
|
|
|
for(int j=1;j<=n;j++)
|
|
|
|
|
if(dis[i][j]>dis[i][k]+dis[k][j]){
|
|
|
|
|
dis[i][j]=dis[i][k]+dis[k][j];
|
|
|
|
|
path[i][j]=k;
|
|
|
|
|
}
|
|
|
|
|
void floyd() {
|
|
|
|
|
for (int k = 1; k <= n; k++)
|
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
|
|
for (int j = 1; j <= n; j++)
|
|
|
|
|
if (dis[i][j] > dis[i][k] + dis[k][j]) {
|
|
|
|
|
dis[i][j] = dis[i][k] + dis[k][j];
|
|
|
|
|
path[i][j] = k; //记录i->j是通过k转移的
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
在这个图中,很容易看出,从 $1$ 到 $6$ 之间的最短路径是标红的那几条边。
|
|
|
|
|
|
|
|
|
|
二重循环所有点,我们输出 $path$ 数组:
|
|
|
|
|
二重循环所有点,输出 $path$ 数组:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
0 0 0 3 4 5
|
|
|
|
|
0 0 0 0 4 5
|
|
|
|
@ -129,7 +129,7 @@ void floyd(){
|
|
|
|
|
0 0 0 0 0 0
|
|
|
|
|
0 0 0 0 0 0
|
|
|
|
|
```
|
|
|
|
|
可以看出,$path[1][6]$ 是 $5$,$path[1][5]$ 是 $4$,$path[1][4]$ 是 $3$。那么,最终 $path[i][j]$ 中存的就是 **从$i$到$j$的最短路径中的最后一个点**。
|
|
|
|
|
可以看出,$path[1][6]$ 是 $5$,$path[1][5]$ 是 $4$,$path[1][4]$ 是 $3$。那么,最终 $path[i][j]$ 中存的就是 **从$i$到$j$的最短路径中的靠近$j$的最后一个点**。
|
|
|
|
|
|
|
|
|
|
而我们最终输出路径的思路就是,不断分段最短路径! 最后输出所有的点。
|
|
|
|
|
> **原理:由 $i$ 到 $j$ 的最短路径中的一点 $k$,将最短路径分段为从 $i$ 到 $k$ 的最短路径 和 从 $k$ 到 $j$ 的最短路径,最短路径就为从$i$到$k$的最短路径+从$k$到$j$的最短路径,一直分段,直到分到 $i$ 和 $j$ 为同一点,停止**
|
|
|
|
@ -137,31 +137,32 @@ void floyd(){
|
|
|
|
|
可能现在你有些迷糊,我们直接看代码吧!
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
void get_path(int i,int j){//path[i][j]:从i到j最短路径中经过的一点k
|
|
|
|
|
if(i==j) return; //分段到同一点,停止
|
|
|
|
|
if(path[i][j]==0) cout<<i<<" "<<j<<endl;//i和j直接相连,则就是最短路径中的边(i到j最短路径不经过任何点)
|
|
|
|
|
else{
|
|
|
|
|
get_path(i,path[i][j]); //分段到从i到k的最短路径,
|
|
|
|
|
//输出从i到k最短路径中的所有点(一定都在从i到j的最短路径中)
|
|
|
|
|
get_path(path[i][j],j); //分段到从k到j的最短路径,
|
|
|
|
|
//输出从j到k最短路径中的所有的点
|
|
|
|
|
}
|
|
|
|
|
void print(int i, int j) { // path[i][j]:从i到j最短路径中经过的一点k
|
|
|
|
|
if (i == j) return; // 分段到同一点,递归结束
|
|
|
|
|
if (path[i][j] == 0) // i和j直接相连,就是i到j最短路径不经过任何点
|
|
|
|
|
cout << i << " " << j << endl;
|
|
|
|
|
else {
|
|
|
|
|
print(i, path[i][j]); // 分段输出从i到k的最短路径
|
|
|
|
|
// 输出从i到k最短路径中的所有点(一定都在从i到j的最短路径中)
|
|
|
|
|
print(path[i][j], j); // 分段输出从k到j的最短路径
|
|
|
|
|
// 输出从j到k最短路径中的所有的点
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
就是一个递归的过程。
|
|
|
|
|
就是一个 **递归** 的过程。
|
|
|
|
|
|
|
|
|
|
我们用上面的图模拟一下:
|
|
|
|
|
首先,从 1 到 6 的最短路径,pass[1][6]中存的是:该最短路径中的最后一个节点 5,即,k = 5。
|
|
|
|
|
首先,从 $1$ 到 $6$ 的最短路径,$path[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$到$5$的最短路 和 从$5$到$6$的最短路。
|
|
|
|
|
- 从$1$到$5$的最短路,$path[1][5]=4$,则又分段为从$1$到$4$的最短路和从$4$到$5$的最短路。
|
|
|
|
|
- 从$1$到$4$的最短路,$path[1][4]=3$,则又分段为从$1$到$3$的最短路和从$3$到$4$的最短路。
|
|
|
|
|
- $path[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都是最短路中的点,输出!
|
|
|
|
|
**回溯**:
|
|
|
|
|
- 从$3$到$4$的最短路,$path[3][4]=0$!$3$和$4$直接相连,$3$和$4$都是最短路中的点,输出!
|
|
|
|
|
- 从$4$到$5$的最短路,$path[4][5]=0$!$4$和$5$直接相连,$4$和$5$都是最短路中的点,输出!
|
|
|
|
|
- 从$5$到$6$的最短路,$path[5][6]=0$!$5$和$6$直接相连,$5$和$6$都是最短路中的点,输出!
|
|
|
|
|
|
|
|
|
|
所以,最终输出的就是:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
@ -174,40 +175,38 @@ void get_path(int i,int j){//path[i][j]:从i到j最短路径中经过的一点k
|
|
|
|
|
|
|
|
|
|
**总体代码**
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
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 floyd() {
|
|
|
|
|
for (int k = 1; k <= n; k++)
|
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
|
|
for (int j = 1; j <= n; j++)
|
|
|
|
|
if (dis[i][j] > dis[i][k] + dis[k][j]) {
|
|
|
|
|
dis[i][j] = dis[i][k] + dis[k][j];
|
|
|
|
|
path[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);
|
|
|
|
|
}
|
|
|
|
|
void print(int i, int j) {
|
|
|
|
|
if (i == j) return;
|
|
|
|
|
if (path[i][j] == 0)
|
|
|
|
|
cout << i << " " << j << endl;
|
|
|
|
|
else {
|
|
|
|
|
print(i, path[i][j]);
|
|
|
|
|
print(path[i][j], j);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(){
|
|
|
|
|
··· //一顿初始化,输入数据
|
|
|
|
|
|
|
|
|
|
floyd();
|
|
|
|
|
print(1,n); //输出从1到n的最短路径中的所有点(边)
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
int main() {
|
|
|
|
|
··· //一顿初始化,输入数据
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
练习题:
|
|
|
|
|
floyd();
|
|
|
|
|
print(1, n); // 输出从1到n的最短路径中的所有点(边)
|
|
|
|
|
|
|
|
|
|
#### [$P2176$ $RoadBlock$ $S$](https://www.luogu.com.cn/problem/P2176)
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
**练习题**:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### [$HDU-1385$ $Minimum$ $Transport$ $Cost$](http://acm.hdu.edu.cn/showproblem.php?pid=1385)
|
|
|
|
|
|
|
|
|
|