弗洛伊德算法
弗洛伊德算法(Floyd-Warshall Algorithm),跟克鲁斯卡尔算法一样是为了解决给定加权图中某一个顶点到其他顶点间的最短距离,可以处理有向图或负权的最短路径问题,同时也被用于在计算有向图的传递闭关。该算法已创始人之一,1978年图领奖获得者,斯坦福大学计算机教授罗伯特·弗洛伊德。
适用范围:无负权回路即可,边权正负都可以,运行一次算法即可得到任意两点之间的最短路径。
优缺点
Floyd算法适用于APSP(AllPairsShortestPaths),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率高于Dijkstra算法。
- 优点: 容易理解,可以算出任意两个节点之间的最短距离,代码编写简单
- 缺点: 时间复杂度高O(n3),不适合计算大量数据
核心思想
- 任意节点i到j的最短路径只能有两种
- 直接从i到j
- 从i经由若干节点k到j
- dis(i,j)的表示i到j的最短路径,取决于上述两种情况中小的一个,即min(dis(i,j),dis(i,k)+dis(j,k)),这里就体现了动态规划的思想
实现步骤
-
初始化dis邻接矩阵和前驱节点矩阵,前驱节点初始化的时候在还未访问任何节点的时候前驱节点为自身 如A点在还未访问BCDEFG的时候前驱节点都是A
-
三层for循环交叉,分别假定为起点,中间节点,终点遍历所有情况
例如以A为起点的情况下,经过分别以ABCDEFG为中间节点,到终点ABCEDFG的情况都考虑到
起点 A,B,C,D,E,F,G 中间节点 A,B,C,D,E,F,G 终点 A,B,C,D,E,F,G -
完成所有循环之后即可得到每一个点到其他另外所有点的最短距离,循环的过程中不断更新dis数组,可以不断更新原本不直连的距离,如原本AD节点原本不连接,在A循环之后计算出了AB的距离,在以B为中间节点时可以到达D,可以计算出BD距离,从而可以计算出AD的距离,再不断的循环中可以有会AD线路更短的距离会继续更新
题目
1.战争时期,胜利乡有 7 个村庄(A, B, C, D, E, F, G) ,现在有六个邮差,从 G 点出发,需要分别把邮件分别送到
A, B, C , D, E, F 六个村庄
2.各个村庄的距离用边线表示(权) ,比如 A – B 距离 5 公里
3.计算从任意一个村庄出发到其它村庄的最短距离是多少?
代码示例
package com.corn.algorithm.floyd;
import java.util.Arrays;
public class FloydAlgorithm {
public static void main(String[] args) {
// 测试看看图是否创建成功
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int[][] matrix = new int[vertex.length][vertex.length];
final int N = Integer.MAX_VALUE;
matrix[0] = new int[]{0, 5, 7, N, N, N, 2};
matrix[1] = new int[]{5, 0, N, 9, N, N, 3};
matrix[2] = new int[]{7, N, 0, N, 8, N, N};
matrix[3] = new int[]{N, 9, N, 0, N, 4, N};
matrix[4] = new int[]{N, N, 8, N, 0, 5, 4};
matrix[5] = new int[]{N, N, N, 4, 5, 0, 6};
matrix[6] = new int[]{2, 3, N, N, 4, 6, 0};
//创建 Graph 对象
FloydGraph graph = new FloydGraph(vertex, matrix);
graph.floyd();
graph.show();
}
}
class FloydGraph {
char[] vertexes;
int[][] dis;
int[][] pre;
public FloydGraph(char[] vertexes, int[][] dis) {
this.vertexes = vertexes;
this.dis = dis;
pre = new int[vertexes.length][vertexes.length];
// 初始化
for (int i = 0; i < pre.length; i++) {
Arrays.fill(pre[i], i);
}
}
public void floyd() {
// 三层 for循环
int len = 0;
// 起始顶点 i
for (int i = 0; i < dis.length; i++) {
// 中间顶点 k
for (int k = 0; k < dis.length; k++) {
// 终点顶点 j
for (int j = 0; j < dis.length; j++) {
len = dis[i][k] + dis[j][k]; // 从 i 点出发经过中间顶点 k的距离 加上从中间顶点k 到终点j的距离
if (len > 0 && dis[i][j] > len) {
dis[i][j] = len;
pre[i][j] = pre[k][j];
}
}
}
}
}
// 显示pre数组和dis数组
public void show() {
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
for (int k = 0; k < dis.length; k++) {
// 先将pre数组输出的一行
for (int i = 0; i < dis.length; i++) {
System.out.print(vertex[pre[k][i]] + " ");
}
System.out.println();
// 输出dis数组的一行数据
for (int i = 0; i < dis.length; i++) {
System.out.print("(" + vertex[k] + "到" + vertex[i] + "的最短路径是" + dis[k][i] + ") ");
}
System.out.println();
System.out.println();
}
}
}