弗洛伊德算法求最短路径

简介: 弗洛伊德算法用于寻找加权图中各顶点间的最短路径,适用于无向图和有向图。算法基于动态规划思想,通过枚举中间顶点来更新路径,确保最终得到最短路径。该算法要求路径权值非负,否则可能出错。

在一个加权图中,如果想找到各个顶点之间的最短路径,可以考虑使用弗洛伊德算法。


弗洛伊德算法既适用于无向加权图,也适用于有向加权图。使用弗洛伊德算法查找最短路径时,只允许环路的权值为负数,其它路径的权值必须为非负数,否则算法执行过程会出错。

弗洛伊德算法的实现思路

弗洛伊德算法是基于动态规划算法实现的,接下来我们以在图 1 所示的有向加权图中查找各个顶点之间的最短路径为例,讲解弗洛伊德算法的实现思路。


图 1 有向加权图

图 1 中不存在环路,且所有路径(边)的权值都为正数,因此可以使用弗洛伊德算法。

弗洛伊德算法查找图 1 中各个顶点之间的最短路径,实现过程如下:

1) 建立一张表格,记录每个顶点直达其它所有顶点的权值:


表 1 各个顶点直达路径的权值

  目标顶点
1 2 3 4
起始顶点 1 0 3 5
2 2 0 4
3 1 0
4 2 0

起始顶点指的是从哪个顶点出发,目标顶点指的是要达到的顶点,例如 2->1 路径的权值是 2,顶点 2 是起始顶点,顶点 1 是目标顶点。此外,∞ 表示无穷大的数,即顶点之间不存在直达的路径。

2) 在表 1 的基础上,将顶点 1 作为 "中间顶点",计算从各个顶点出发途径顶点 1 再到达其它顶点的权值,如果比表 1 中记录的权值更小,证明两个顶点之间存在更短的路径,对表 1 进行更新。


从各个顶点出发,途径顶点 1 再到达其它顶点的路径以及对应的权值分别是:

  • 2-1-3:权值为 2 + ∞ = ∞,表 1 中记录的 2-3 的权值也是 ∞;
  • 2-1-4:权值为 2 + 5 = 7,表 1 中记录的 2-4 的权值是 4;
  • 3-1-2:权值为 ∞ + 3,表 1 中记录的 3-2 的权值是 1;
  • 3-1-4:权值为 ∞ + 5,表 1 中记录的 3-4 的权值是 ∞;
  • 4-1-2:权值为 ∞ + 3,表 1 中记录的 4-2 的权值是 ∞;
  • 4-1-3:权值为 ∞ + ∞,表 1 中记录的 4-3 的权值是 2。


以上所有的路径中,没有比表 1 中记录的权值最小的路径,所以不需要对表 1 进行更新。


3) 在表 1 的基础上,以顶点 2 作为 "中间顶点",计算从各个顶点出发途径顶点 2 再到达其它顶点的权值:

  • 1-2-3:权值为 3 + ∞,表 1 中记录的 1-3 的权值为 ∞;
  • 1-2-4:权值为 3 + 4 = 7,表 1 中 1-4 的权值为 5;
  • 3-2-1:权值为 1 + 2 = 3,表 1 中 3-1 的权值为 ∞,3 < ∞;
  • 3-2-4:权值为 1 + 4 = 5,表 1 中 3-4 的权值为 ∞,5 < ∞;
  • 4-2-1:权值为 ∞ + 2,表 1 中 4-1 的权值为 ∞;
  • 4-2-3:权值为 ∞ + ∞,表 1 中 4-3 的权值为 2。


以顶点 2 作为 "中间顶点",我们找到了比 3-1、3-4 更短的路径,对表 1 进行更新:


表 2 更新后的 "表 1"

  目标顶点
1 2 3 4
起始顶点 1 0 3 5
2 2 0 4
3 3(3-2-1) 1 0 5(3-2-4)
4 2 0


4) 在表 2 的基础上,将顶点 3 作为 "中间顶点",计算从各个顶点出发途径顶点 3 再到达其它顶点的权值:

  • 1-3-2 权值为 ∞ + 1,表 2 中 1-2 的权值为 3;
  • 1-3-4 权值为 ∞ + 5,表 2 中 1-4 的权值为 5;
  • 2-3-1 权值为 ∞ + 3,表 2 中 2-1 的权值为 2;
  • 2-3-4 权值为 ∞ + 5,表 2 中 2-4 的权值为 4;
  • 4-3-1 权值为 2 + 3 = 5,表 2 中 4-1 的权值为 ∞,5 < ∞;
  • 4-3-2 权值为 2 + 1 = 3,表 2 中 4-2 的权值为 ∞,3 < ∞;


以顶点 3 作为 "中间顶点",我们找到了比 4-1、4-2 更短的路径,对表 2 进行更新:


表 3 更新后的 "表 2"

  目标顶点
1 2 3 4
起始顶点 1 0 3 5
2 2 0 4
3 3(3-2-1) 1 0 5(3-2-4)
4 5(4-3-2-1) 3(4-3-2) 2 0


5) 在表 3 的基础上,将顶点 4 作为 "中间顶点",计算从各个顶点出发途径顶点 4 再到达其它顶点的权值:

  • 1-4-2 权值为 5 + 3 = 8,表 3 中 1-2 的权值为 3;
  • 1-4-3 权值为 5 + 2 = 7,表 3 中 1-3 的权值为 ∞,7 < ∞;
  • 2-4-1 权值为 4 + 5 = 9,表 3 中 2-1 的权值为 2;
  • 2-4-3 权值为 4 + 2 = 6,表 3 中 2-3 的权值为 ∞,6 < ∞;
  • 3-4-1 权值为 4 + 5 = 9,表 3 中 3-1 的权值为 3;
  • 3-4-2 权值为 5 + 5 = 10 ,表 3 中 3-2 的权值为 1。


以顶点 4 作为 "中间顶点",我们找到了比 1-3、2-3 更短的路径,对表 3 进行更新:


表 4 更新后的 "表 3"

  目标顶点
1 2 3 4
起始顶点 1 0 3 7(1-4-3) 5
2 2 0 6(2-4-3) 4
3 3(3-2-1) 1 0 5(3-2-4)
4 5(4-3-2-1) 3(4-3-2) 2 0


通过将所有的顶点分别作为“中间顶点”,最终得到的表 4 就记录了各个顶点之间的最短路径。例如,4-1 的最短路径为 4-3-2-1。

弗洛伊德算法的具体实现

了解了弗洛伊德算法查找最短路径的实现思路后,接下来仍以图 1 为例,分别编写 C、Java、Python 程序实现弗洛伊德算法。


如下是用弗洛伊德算法查找图 1 中各顶点之间最短路径的 C 语言程序:

  1. #include <stdio.h>
  2. #define V 4    //设定图中的顶点数
  3. #define INF 65535   // 设置一个最大值
  4. int P[V][V] = { 0 }; //记录各个顶点之间的最短路径
  5. void printMatrix(int matrix[][V]);  //输出各个顶点之间的最短路径
  6. void printPath(int i, int j); // 递归输出各个顶点之间最短路径的具体线路
  7. void floydWarshall(int graph[][V]); // 实现弗洛伊德算法

  8. int main() {
  9. // 有向加权图中各个顶点之间的路径信息
  10. int graph[V][V] = { {0, 3, INF, 5},
  11. {2, 0, INF, 4},
  12. {INF, 1, 0, INF},
  13. {INF, INF, 2, 0} };
  14. floydWarshall(graph);
  15. }
  16. // 中序递归输出各个顶点之间最短路径的具体线路
  17. void printPath(int i, int j)
  18. {
  19. int k = P[i][j];
  20. if (k == 0)
  21. return;
  22. printPath(i, k);
  23. printf("%d-", k + 1);
  24. printPath(k, j);
  25. }
  26. // 输出各个顶点之间的最短路径
  27. void printMatrix(int graph[][V]) {
  28. int i, j;
  29. for (i = 0; i < V; i++) {
  30. for (j = 0; j < V; j++) {
  31. if (j == i) {
  32. continue;
  33. }
  34. printf("%d - %d: 最短路径为:", i + 1, j + 1);
  35. if (graph[i][j] == INF)
  36. printf("%s\n", "INF");
  37. else {
  38. printf("%d", graph[i][j]);
  39. printf(",依次经过:%d-", i + 1);
  40. //调用递归函数
  41. printPath(i, j);
  42. printf("%d\n", j + 1);
  43. }
  44. }
  45. }
  46. }
  47. // 实现弗洛伊德算法,graph[][V] 为有向加权图
  48. void floydWarshall(int graph[][V]) {
  49. int  i, j, k;
  50. //遍历每个顶点,将其作为其它顶点之间的中间顶点,更新 graph 数组
  51. for (k = 0; k < V; k++) {
  52. for (i = 0; i < V; i++) {
  53. for (j = 0; j < V; j++) {
  54. //如果新的路径比之前记录的更短,则更新 graph 数组
  55. if (graph[i][k] + graph[k][j] < graph[i][j]) {
  56.                    graph[i][j] = graph[i][k] + graph[k][j];
  57. //记录此路径
  58.                    P[i][j] = k;
  59. }
  60. }
  61. }
  62. }
  63. // 输出各个顶点之间的最短路径
  64. printMatrix(graph);
  65. }


如下是用弗洛伊德算法查找图 1 中各顶点之间最短路径的 Java 程序:

  1. public class Floyd {
  2. static int V = 4; // 顶点的个数
  3. static int[][] P = new int[V][V]; // 记录各个顶点之间的最短路径
  4. static int INF = 65535; // 设置一个最大值

  5. // 中序递归输出各个顶点之间最短路径的具体线路
  6. public static void printPath(int i, int j) {
  7. int k = P[i][j];
  8. if (k == 0)
  9. return;
  10. printPath(i, k);
  11.        System.out.print((k + 1) + "-");
  12. printPath(k, j);
  13. }

  14. // 输出各个顶点之间的最短路径
  15. public static void printMatrix(int[][] graph) {
  16. for (int i = 0; i < V; i++) {
  17. for (int j = 0; j < V; j++) {
  18. if (j == i) {
  19. continue;
  20. }
  21.                System.out.print((i + 1) + " - " + (j + 1) + ":最短路径为:");
  22. if (graph[i][j] == INF)
  23.                    System.out.println("INF");
  24. else {
  25.                    System.out.print(graph[i][j]);
  26.                    System.out.print(",依次经过:" + (i + 1) + "-");
  27. // 调用递归函数
  28. printPath(i, j);
  29.                    System.out.println((j + 1));
  30. }
  31. }
  32. }
  33. }

  34. // 实现弗洛伊德算法,graph[][V] 为有向加权图
  35. public static void floydWarshall(int[][] graph) {
  36. int i, j, k;
  37. // 遍历每个顶点,将其作为其它顶点之间的中间顶点,更新 graph 数组
  38. for (k = 0; k < V; k++) {
  39. for (i = 0; i < V; i++) {
  40. for (j = 0; j < V; j++) {
  41. // 如果新的路径比之前记录的更短,则更新 graph 数组
  42. if (graph[i][k] + graph[k][j] < graph[i][j]) {
  43.                        graph[i][j] = graph[i][k] + graph[k][j];
  44. // 记录此路径
  45.                        P[i][j] = k;
  46. }
  47. }
  48. }
  49. }
  50. // 输出各个顶点之间的最短路径
  51. printMatrix(graph);
  52. }

  53. public static void main(String[] args) {
  54. // 有向加权图中各个顶点之间的路径信息
  55. int[][] graph = new int[][] { { 0, 3, INF, 5 }, { 2, 0, INF, 4 }, { INF, 1, 0, INF }, { INF, INF, 2, 0 } };
  56. floydWarshall(graph);
  57. }
  58. }


如下是用弗洛伊德算法查找图 1 中各顶点之间最短路径的 Python 程序:

  1. V = 4   # 顶点的个数
  2. INF = 65535    # 设定一个最大值
  3. P = [[0]*V for i in range(V)] # 记录各个顶点之间的最短路径
  4. # 有向加权图中各个顶点之间的路径信息
  5. graph = [[0, 3, INF, 5],
  6. [2, 0, INF, 4],
  7. [INF, 1, 0, INF],
  8. [INF, INF, 2, 0]]
  9. # 中序递归输出各个顶点之间最短路径的具体线路
  10. def printPath(i,j):
  11.    k = P[i][j]
  12. if k == 0:
  13. return;
  14. printPath(i , k)
  15. print("%d-" % (k + 1) , end='')
  16. printPath(k , j)
  17. # 输出各个顶点之间的最短路径
  18. def printMatrix(graph):
  19. for i in range(V):
  20. for j in range(V):
  21. if j == i:
  22. continue
  23. print("%d - %d: 最短路径为:"%(i + 1, j + 1) , end='')
  24. if graph[i][j] == INF:
  25. print("INF")
  26. else:
  27. print(graph[i][j] , end='')
  28. print(",依次经过:%d-"%(i+1) , end='')
  29. # 调用递归函数
  30. printPath(i , j)
  31. print(j + 1)
  32. # 实现弗洛伊德算法,graph[][V] 为有向加权图
  33. def floydWarshall(graph):
  34. # 遍历每个顶点,将其作为其它顶点之间的中间顶点,更新 graph 数组
  35. for k in range(V):
  36. for i in range(V):
  37. for j in range(V):
  38. # 如果新的路径比之前记录的更短,则更新 graph 数组
  39. if graph[i][k] + graph[k][j] < graph[i][j]:
  40.                    graph[i][j] = graph[i][k] + graph[k][j]
  41. # 记录此路径
  42.                    P[i][j] = k
  43. # 输出各个顶点之间的最短路径
  44. printMatrix(graph)

  45. floydWarshall(graph)


以上程序的输出结果均为:

1 - 2: 最短路径为:3,依次经过:1-2

1 - 3: 最短路径为:7,依次经过:1-4-3

1 - 4: 最短路径为:5,依次经过:1-4

2 - 1: 最短路径为:2,依次经过:2-1

2 - 3: 最短路径为:6,依次经过:2-4-3

2 - 4: 最短路径为:4,依次经过:2-4

3 - 1: 最短路径为:3,依次经过:3-2-1

3 - 2: 最短路径为:1,依次经过:3-2

3 - 4: 最短路径为:5,依次经过:3-2-4

4 - 1: 最短路径为:5,依次经过:4-3-2-1

4 - 2: 最短路径为:3,依次经过:4-3-2

4 - 3: 最短路径为:2,依次经过:4-3

相关文章
|
4月前
|
存储 算法
最小生成树的概念与思想
数据结构中的图存储结构包含顶点和边,分为连通图与非连通图。生成树是包含所有顶点、任意两点间仅有一条通路的极小连通图。最小生成树则是权值总和最小的生成树,常用于解决道路建设等实际问题,常用算法有普里姆算法和克鲁斯卡尔算法。
105 0
|
4月前
|
算法
回溯算法的基本思想
本节介绍回溯算法,通过图1中从A到K的路径查找示例,说明其与穷举法的异同。回溯算法通过“回退”机制高效试探各种路径,适用于决策、优化和枚举问题。
113 0
|
4月前
|
存储 算法 Java
组合问题
组合问题是从给定的N个数中找出任意K个数的所有组合。由于组合内元素无顺序,因此[1,2]和[2,1]视为同一组合。解决组合问题常用的方法包括嵌套循环和回溯算法。嵌套循环适用于K值较小的情况,而回溯算法更适合K值较大的情况,能有效避免多层循环带来的代码复杂性和低效率问题。回溯算法通过递归实现,能动态选择元素并逐步构建组合,最终输出所有符合条件的组合结果。
161 0
|
4月前
|
存储 算法 Java
寻找图中是否存在路径
本节介绍了如何使用并查集判断图中两个顶点之间是否存在有效路径。通过将图的边信息存储到并查集中,同一连通区域的顶点会处于同一集合。只需比较两个顶点的根节点是否相同,即可判断它们是否连通。文中提供了C语言、Java和Python的实现代码,并通过示例验证了算法的正确性。
117 0
|
5月前
|
存储 NoSQL 关系型数据库
1-MongoDB相关概念
MongoDB 是一种高性能、无模式的文档型数据库,适合需要灵活数据模型、高扩展性和大规模数据存储的应用场景。适用于新项目快速开发、高并发读写、海量数据存储及地理文本查询等需求,且支持类似 JSON 的 BSON 数据格式,灵活易扩展。
101 0
|
10月前
|
存储 算法 测试技术
【狂热算法篇】探秘图论之 Floyd 算法:解锁最短路径的神秘密码(通俗易懂版)
【狂热算法篇】探秘图论之 Floyd 算法:解锁最短路径的神秘密码(通俗易懂版)
|
4月前
|
存储 搜索推荐 算法
归并排序算法
归并排序是一种基于分治思想的高效排序算法,通过将序列不断划分为不可再分的子序列,再两两合并完成排序。其时间复杂度为O(nlogn),适用于对序列进行升序或降序排列。
272 0
|
4月前
|
存储 算法 Java
求数组中的最大值和最小值
本文介绍了在程序中如何查找数组中的最大值和最小值,重点讲解了两种算法:普通算法和分治算法。普通算法通过遍历数组直接比较元素大小,找出最值;而分治算法则通过递归将数组划分成更小的部分,分别找出各部分的最大值,最终合并结果得到整个数组的最大值。文章以 {3,7,2,1} 为例,详细演示了两种算法的实现过程,并提供了 C、Java 和 Python 的代码示例。
267 0
|
4月前
|
算法
贪心算法的基本思想
贪心算法是一种简单且常用的算法,每一步选择当前最优解,追求局部最优。文章通过纸币拼凑实例说明其原理,并指出贪心算法并不总能得到全局最优解,最后介绍了其在部分背包问题和经典算法中的应用。
119 0