只有五行的算法
四种最短路算法
Floyd算法
时间复杂度高,但实现容易(5行核心代码),可解决负权边,适用于数据范围小的
Dijkstra算法
不能解决负权边,但具有良好扩展性,且复杂度较低
Bellman-Ford / 队列优化Bellman-Ford
可解决负权边,且复杂度较低
暑假,小哼准备去旅游。有些城市之间有公路,有些则没有。为了节省经费,出发前需要知道任意两城市间的最短路程
上图有4个城市8条公路,公路上的数字表示公路的长短,且公路为单向的(有向图)
现在求任意两个城市之间的最短路程,也就是求任意两点间的最短路径
这个问题也被称之为“多源最短路径”问题
现在用一个数据结构来存储图的信息,我们选择4*4的矩阵(二维数组e),比如:
e[1][2] == 2表示1号城市到2号城市的距离为2
e[2][4] == ∞表示2号城市到4号城市无法到达
e[1][1] == 0表示自己到自己的距离为0
如果用dfs或bfs求“多源最短路径”,即对每两个点都进行进行依次深度或广度优先搜索,还有没有其他方法呢
根据以往的经验,要让任意两点的路程变短,需要引入第3个点作为中转,比如a -> k -> b
可能会比a -> b的路程短。甚至有时候不只通过一个点中转,还会通过多个点中转,即a -> k1 -> k2 -> ... -> b
比如e[4][3] == 12,e[4][1] + e[1][3] == 11,e[4][1] + e[1][2] + e[2][3] == 10,是不是越来越短了呢,下面我们将这个思路一般化
当任意两点之间不允许经过第三个点时,这些城市之间的最短路程就是初始路程,看下图
假设只允许经过1号中转,求任意两点的最短路程,只需判断e[i][1] + e[1][j] < e[i][j]即可
表示从 i号 到 1号 再到 j号,比 i号 直接到 j号 短
其中 i 是 1~n 循环,j 也是 1~n 循环
代码实现
//经1号中转
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j) {
if(e[i][j] > e[i][1] + e[1][j])
e[i][j] = e[i][1] + e[1][j];
}
此时,最短路更新为:
当只允许从1号和2号中转
代码实现
//经1号中转
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j) {
if(e[i][j] > e[i][1] + e[1][j])
e[i][j] = e[i][1] + e[1][j];
}
//经2号中转
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j) {
if(e[i][j] > e[i][2] + e[2][j])
e[i][j] = e[i][2] + e[2][j];
}
此时,最短路径更新为:
当只允许从1,2,3号中转,最短路径更新为:
最后,允许通过所有顶点中转,最短路径更新为:
Floyd-Warshall的过程虽然麻烦,但代码实现极其简单,核心代码只有5行
//允许从所有点中转
for(k = 1; k <= n; ++k)
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j) {
if(e[i][j] > e[i][k] + e[k][j])
e[i][j] = e[i][k] + e[k][j];
}
基本思路:从1~n号所有顶点中转,求任意两点的最短路径
一句话概括就是,从 i号 顶点到 j号 顶点,只经过前 k号 点的最短路程(一种动态规划思想
完整代码
#include<cstdio>
int main()
{
int e[10][10], k , i, j, n, m, t1, t2, t3;
int inf = 1e8; //表示infinity无穷(n.)
scanf("%d%d", &n, &m); //n个顶点, m条边
//初始化
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j) {
if(i == j) e[i][j] = 0;
else e[i][j] = inf; //无穷
}
//读入边
for(i = 1; i <= m; ++i) { //这里是m条边
scanf("%d%d%d", &t1, &t2, &t3);
e[t1][t2] = t3;
}
//Floyd-Warshall算法
for(k = 1; k <= n; ++k)
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j) {
if(e[i][j] > e[i][k] + e[k][j])
e[i][j] = e[i][k] + e[k][j];
}
//输出最终结果
for(i = 1; i <= n; ++i) {
for(j = 1; j <= n; ++j)
printf("%5d", e[i][j]);
printf("\n");
}
return 0;
}
输入第一行,n个顶点,m条边,接下来m行,每一行t1 t2 t3表示顶点 t1 到 t2 的路程是 t3
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
0 2 5 4
9 0 3 4
6 8 0 1
5 7 10 0
最终结果
Floyd-Warshall算法,时间复杂度高达O(n^3),可以解决负权边,但不能解决带有“负权回路”(负权环)的图,因为带有“负权回路”的图没有最短路径
什么是负权回路(负权环)呢?图上每条边有一个权值,所有权值之和是负数,就叫负权环
不能解决负权回路,比如
每循环一次,最短路减少1,永远找不到最短路径
但是它只有5行核心代码
实现起来非常容易,所以如果时间复杂度要求低,使用Floyd-Warshall来求指定两点间的最短路程或者指定一个点到各点的最短路也是可行的
当然,还有更快的算法,请看下一个博客《最短路之Dijkstra》
总结
基本思路:从1~n号所有顶点中转,求任意两点的最短路径
一句话概括就是,从 i号 顶点到 j号 顶点,只经过前 k号 点的最短路程(一种动态规划思想