最短路算法——Floyd-Warshall

        在更多的应用场景中,需要用不同的算法来解决。下表总结了一些经典算法,除了贪心最优搜索之外,其他都是最优性算法,即得到的解是最短路径。表中的 m 是边的数量,n 是点的数量。

一、BFS

        BFS 也是一种很不错的最短路算法。但 BFS 只适合一种场景:任意的相邻两点之间距离相等,一般把这个距离看成 1,称为“1跳”,从起点到终点的路径长度就是多少个“跳数”。在这种场景下,查找一个起点到一个终点的最有短距离,BFS 是最优的最短路径算法,计算复杂度是 O(n),n 是图上点的数量。

二、Floyd-Warshall

        Floyd 算法是代码最最简单的最短路径算法,甚至比暴力的搜索更简单。它的效率不高,而且不能用于大图,但是在某些场景下也有自己的优势。Floyd算法是一种“多源”最短路算法,一次计算能得到图中每一对结点之间(多对多)的最短路径。而Dijkstra、Bellman-Ford、SPFA 算法都是“单源”最短路径算法(Single source shortest path algorithm),一次计算只能得到一个起点到其他所有点(一对多)的最短路径。可以理解为当其它算法求完某两点间最短路时,Floyd 已经求得了所有点之间的最短路。

1. Floyd算法思想

动态规划的思路:

        求图上两点 i、j 之间的最短距离,可以按“从小图到全图”的步骤,在逐步扩大图的过程中计算和更新最短路。想象图中的每个点是一个灯,开始时所有灯都是灭的。然后逐个点亮灯,每点亮一个灯,就重新计算 i、j 的最短路,要求路径只能经过点亮的灯。所有灯都点亮后,计算结束。在这个过程中,点亮第 k 个灯时,能用到 1∼k−1 个亮灯的结果。

        定义状态为 dp[k][i][j],i、j、k 是点的编号,范围 1∼n 。状态 dp[k][i][j] 表示在包含 1∼k 点的子图上,点对 i、j 之间的最短路。当从子图 1∼k−1 扩展到子图 1∼k 时,状态转移方程这样设计:dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j])

        计算过程如上图所示,虚线圆圈内是包含了 1∼k−1 点的子图。方程中的 dp[k−1][i][j] 是虚线子图内的点对 i、j 的最短路;dp[k−1][i][k]+dp[k−1][k][j] 是经过 k 点的新路径的长度,即这条路径从 i 出发,先到 k,再从 k 到终点 j。比较不经过 k 的最短路径 dp[k−1][i][j] 和经过 k 的新路径,较小者就是新的 dp[k][i][j]。每次扩展一个新点 k 时,都能用到 1∼k−1 的结果,从而提高了效率。这就是动态规划的方法。 

        当 k 从 1 逐步扩展到 n 时,最后得到的 dp[n][i][j] 是点对 i、j 之间的最短路径长度。由于 i 和 j 是图中所有的点对,所以能得到所有点对之间的最短路。

代码分析:

for(int k=1; k<=n; k++)  //floyd的三重循环
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)  // k循环在i、j循环外面
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);  //比较:不经过k、经过k
  • 初值 dp[0][i][j] ,若 i、j 是直连的,就是它们的边长;若不直连,赋值为无穷大
  • 把 dp[][][] 缩小成了 dp[][],这是用到了滚动数组,因为 dp[k][][] 只和 dp[k−1][][] 有关,所以可以省掉 k 这一维。由于 k 是动态规划的子问题的“阶段”,即 k 是从点 1 开始逐步扩大到 n 的,所以 kk 循环必须放在 i、j 循环的外面。
  • 三重循环,复杂度是 O(n^3),非常低下。

Floyd算法的寻路极为盲目,几乎“毫无章法”,这是它的效率低于其他算法的原因。但是,这种“毫无章法”,在某些情况下却有优势。 

 2. Floyd算法要点

1.能一次性求得所有结点之间的最短距离,这是其他最短路径算法都做不到的。

注:求所有结点之间的最短距离时,从效率上讲并不一定比其他算法强:Floyd 算法的复杂度为 O(n^3), n 是结点数量;Dijkstra 算法求所有点对之间的最短距离,复杂度是 O(mnlogn),m 是边数。不过,若图的边很稠密,m 比 n 大很多,例如在全连通图中有 m = n(n-1)/2,此时 O(mnlogn) 比 O(n^3) 还大,此时 Floyd 就有优势了。 

2. 代码极其简单,是最简单的最短路算法。三重循环结束后,所有点对之间的最短路都得到了。

3. 效率低下,计算复杂度是 O(n^3), 只能用于 n < 300 的小规模的图。

4. 存图用邻接矩阵 dp[][] 是最好最合理的,不用更省空间的邻接表。

注:这个因为 Floyd 算法计算的结果是所有点对之间的最短路,本身就需要 n×n 的空间,用矩阵存储最合适。

5. 可判断负圈

注:负圈:若图中有权值为负的边,某个经过这个负边的环路,所有边长相加的总长度也是负数,这就是负圈。在这个负圈上每绕一圈,总长度就更小,从而陷入在负圈上兜圈子的死循环。Floyd 算法很容易判断负圈,只要在算法运行过程出现任意一个 dp[i][i]<0 就说明有负圈。因为 dp[i][i] 是从 i 出发,经过其他中转点绕一圈回到自己的最短路径,如果小于零,就存在负圈。

3. Floyd 算法的应用场景

1. 图的规模小,n<300。计算复杂度 O(n^3) 限制了图的规模。不需要用其他算法,其他算法的代码更长,写起来麻烦。

2. 问题的解决和中转点有关。这是 Floyd 算法的核心思想,算法用 DP 方法遍历中转点来计算最短路。

3. 路径在“兜圈子”,一个点可能多次经过。这是 Floyd 算法的特长,其他路径算法都不行。

4. 可能多次询问不同点对之间的最短路。这是 Floyd 算法的优势。

4. 经典题目练习

点击->题目分析及参考代码

公园观景

题目描述

小明喜欢观景,于是今天他来到了公园。

已知公园有 N 个景点,景点和景点之间一共有 M 条道路。小明有 Q 个观景计划,每个计划包含一个起点 st 和一个终点 ed,表示他想从 st 去到 ed。但是小明的体力有限,对于每个计划他想走最少的路完成,你可以帮帮他吗?

输入描述

输入第一行包含三个正整数 N,M,Q

第 2 到 M + 1 行每行包含三个正整数 u,v,w 表示 uv 之间存在一条距离为 w 的路。

第 M+2 到 M + Q-1 行每行包含两个正整数 st,ed,其含义如题所述。

(1 ≤ ≤ 400,1 ≤ ≤ N×(N−1)​/2,≤ 10^3,1 ≤ u,v,st,e≤ n,1 ≤ ≤ 10^9)

输出描述

输出共 Q 行,对应输入数据中的查询。

若无法从 st 到达 ed 则输出 -1。

样例输入

3 3 3
1 2 1
1 3 5
2 3 2
1 2
1 3
2 3

样例输出

1
3
2

 打印路径

题目描述

王国有 N 个城市,任意两城市间有直通的路或没有路。每条路有过路费,并且经过每个城市都要交税。定义从 a 城到 b 城,其花费为路径长度之和,再加上除 a 与 b 外所有城市的过路费之和。

现给定若干对城市,请你打印它们之间最小花费的路径。如果有多条路经符合,则输出字典序最小的路径。

输入描述

第一行给定一个 N 表示城市的数量,若 N=0 表示结束。

接下来 N 行,第 i 行有 N 个数,​ a_{i,1} \sim a_{i,n}a _{i,j } 表示第 i 个城市到第 j 个城市的直通路过路费,若 a _{i,j } ​= −1 表示没有直通路。

接下来一行有 N 个数,第 i 个数表示第 i 个城市的税。再后面有很多行,每行有两个数,表示起点和终点城市,若两个数是 -1,结束。

输出描述

对给定的每两个城市,输出最便宜的路径经过哪些点,以及最少费用。

样例输入

3
0 2 -1
2 0 5
-1 5 0
1 2 3
1 3
2 3
-1 -1
0

样例输出

From 1 to 3 :
Path: 1-->2-->3
Total cost : 9

From 2 to 3 :
Path: 2-->3
Total cost : 5

指数移动 

题目描述

一个图有 n 个点,有 m 个边连接这些点,边长都是 1 千米。小明的移动能力很奇怪,他一秒能跑 2^t 千米,t 是任意自然整数。问小明从点 1 到点 n,最少需要几秒。

输入描述

第一行两个整数 n,m,表示点的个数和边的个数。

接下来 m 行每行两个数字 a,b,表示一条 a 到 b 的边。

(1 ≤ ≤ 50,1 ≤ ≤ 10000,最优路径长度 ≤ 2^32。)

输出描述

输出一个整数,表示答案。

样例输入

5 5
1 2
2 2
3 4
3 5
2 3

样例输出

1

如有错误和需要改进完善之处,欢迎大家纠正指教。

  • 15
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值