在更多的应用场景中,需要用不同的算法来解决。下表总结了一些经典算法,除了贪心最优搜索之外,其他都是最优性算法,即得到的解是最短路径。表中的 m 是边的数量,n 是点的数量。
BFS 也是一种很不错的最短路算法。但 BFS 只适合一种场景:任意的相邻两点之间距离相等,一般把这个距离看成 1,称为“1跳”,从起点到终点的路径长度就是多少个“跳数”。在这种场景下,查找一个起点到一个终点的最有短距离,BFS 是最优的最短路径算法,计算复杂度是 O(n),n 是图上点的数量。
Floyd 算法是代码最最简单的最短路径算法,甚至比暴力的搜索更简单。它的效率不高,而且不能用于大图,但是在某些场景下也有自己的优势。Floyd算法是一种“多源”最短路算法,一次计算能得到图中每一对结点之间(多对多)的最短路径。而Dijkstra、Bellman-Ford、SPFA 算法都是“单源”最短路径算法(Single source shortest path algorithm),一次计算只能得到一个起点到其他所有点(一对多)的最短路径。可以理解为当其它算法求完某两点间最短路时,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
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 出发,经过其他中转点绕一圈回到自己的最短路径,如果小于零,就存在负圈。
1. 图的规模小,n<300。计算复杂度 O(n^3) 限制了图的规模。不需要用其他算法,其他算法的代码更长,写起来麻烦。
2. 问题的解决和中转点有关。这是 Floyd 算法的核心思想,算法用 DP 方法遍历中转点来计算最短路。
3. 路径在“兜圈子”,一个点可能多次经过。这是 Floyd 算法的特长,其他路径算法都不行。
4. 可能多次询问不同点对之间的最短路。这是 Floyd 算法的优势。
题目描述
小明喜欢观景,于是今天他来到了公园。
已知公园有 N 个景点,景点和景点之间一共有 M 条道路。小明有 Q 个观景计划,每个计划包含一个起点 st 和一个终点 ed,表示他想从 st 去到 ed。但是小明的体力有限,对于每个计划他想走最少的路完成,你可以帮帮他吗?
输入描述
输入第一行包含三个正整数 N,M,Q
第 2 到 M + 1 行每行包含三个正整数 u,v,w 表示 u↔v 之间存在一条距离为 w 的路。
第 M+2 到 M + Q-1 行每行包含两个正整数 st,ed,其含义如题所述。
(1 ≤ N ≤ 400,1 ≤ M ≤ N×(N−1)/2,Q ≤ 10^3,1 ≤ u,v,st,ed ≤ n,1 ≤ w ≤ 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 个数, , 表示第 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 ≤ n ≤ 50,1 ≤ m ≤ 10000,最优路径长度 ≤ 2^32。)
输出描述
输出一个整数,表示答案。
样例输入
5 5
1 2
2 2
3 4
3 5
2 3
样例输出
1
如有错误和需要改进完善之处,欢迎大家纠正指教。