F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall算法,简称 F l o y d Floyd Floyd算法
特点:
- 多源最短路算法,能一次性求得所有节点之间的最短距离。
- 效率不高,不能用于大图(算法复杂度为 O ( n 3 ) O(n ^ 3) O(n3),一般用于)。
- 代码简单
- 可以判断负环(之后解释)
- 存图时一般用临接矩阵存图。
F l o y d Floyd Floyd算法的原理:
从小图扩张到全图。
很容易看出这是动态规划的思想,定义 d p [ k ] [ i ] [ j ] dp[k][i][j] dp[k][i][j]:以点 k k k为中继点(如果有更优则不做中继点)的点对 ( i , j ) (i, j) (i,j)间的最短路径。
得出动态转移方程: d p [ k ] [ i ] [ j ] = m i n ( d p [ k − 1 ] [ i ] [ j ] , d p [ k − 1 ] [ i ] [ k ] + d p [ k − 1 ] [ k ] [ j ] ) dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]) dp[k][i][j]=min(dp[k−1][i][j],dp[k−1][i][k]+dp[k−1][k][j])
因为是以 k k k为中继点,所以以点 i i i到点 k k k的距离加上点 k k k到点 j j j与更新至现在点 i i i到 j j j的最短距离作比较,更新出更新的最短路径。
代码也是相当简洁:
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
dp[k][i][j] = min(dp[k - 1][i][j],
dp[k - 1][i][k] + dp[k - 1][k][j]);
由于第 k k k个状态只与第 k − 1 k - 1 k−1个状态有关,故可以将 k k k这一维优化掉。
最终代码:
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
判断负环是什么原理呢,就是看更新完成之后,存不存在 d p [ i ] [ j ] dp[i][j] dp[i][j](其中 i = j i = j i=j,也就是从自己走到自己)的距离为负,因为正常自己走到自己最短是不走,出现负路径只能是存在负环了。
一起看看例题:无穷背【模板】最短路(3) - StarryCoding | 踏出编程第一步
题目描述
给定一个 n n n个点、 m m m条边的有向图。
再给出 q q q次询问,每个询问为两个整数 u i u_i ui, v i v_i vi,你需要回答从 u i u_i ui到 v i v_i vi的最短距离。
输入格式
第一行:三个整数 n , m , q n,m,q n,m,q。 ( 1 ≤ n ≤ 300 , 1 ≤ m , q ≤ 1 0 5 ) (1 \le n \le 300,1 \le m,q \le 10^5) (1≤n≤300,1≤m,q≤105)
接下来 m m m行:每行三个整数 u i , v i , w i u_i,v_i,w_i ui,vi,wi,表示存在一条从 u i u_i ui到 v i v_i vi,权值为 w i w_i wi的有向边。 ( 1 ≤ u i , v i ≤ n , 0 ≤ w i ≤ 1 0 6 ) (1 \le u_i,v_i \le n,0 \le w_i \le 10^6) (1≤ui,vi≤n,0≤wi≤106)可能存在重边和自环。
再接下来 q q q行,每行两个整数 u i , v i u_i,v_i ui,vi,表示查询从 u i u_i ui到 v i v_i vi的最短距离。
输出格式
共 q q q行:每行一个整数,表示查询的最短距离;若不存在路径,则输出 − 1 −1 −1。
模板题就不细讲了,把上面的 F l o y d Floyd Floyd代码用进去就行了。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 309;
const ll inf = 1e16;
ll dp[N][N];
void solve()
{
int n, m, q; cin >> n >> m >> q;
//不要忘记初始化
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= n; ++j)
{
dp[i][j] = inf;
if(i == j) dp[i][j] = 0; //单独处理自己走到自己的情况,调用Floyd算法后dp[i][i]不等于0
}
}
for(int i = 1; i <= m; ++i)
{
int u, v;
ll w; cin >> u >> v >> w;
dp[u][v] = min(dp[u][v], w); //处理重边
}
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
while(q--)
{
int x, y; cin >> x >> y;
cout << (dp[x][y] == inf ? -1 : dp[x][y]) << '\n';
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _ = 1;
while(_--) solve();
return 0;
}
实际应用:小图全原最短路。