1. 前言
Floyd-Warshall算法是一种在有向图或无向图中寻找所有顶点对之间的最短路径的动态规划算法。该算法可以处理带权重的边,并且能够正确处理负权重的边(但不包括负权重循环),不过它不能处理包含负权重循环的情况,因为这种情况下最短路径是没有定义的。
2. 基本原理
Floyd-Warshall算法的核心思想是逐步扩展中间顶点的集合来计算最短路径。算法通过考虑所有可能的中间顶点来更新每一对顶点之间的最短路径长度。
3. 算法步骤
1. 初始化:
- 创建一个二维数组 `D`,其中 `D[i][j]` 表示从顶点 `i` 到顶点 `j` 的初始距离(如果 `i` 和 `j` 之间没有直接相连,则使用无穷大表示)。
- 对于所有的直接相连的边 `(i, j)`,将 `D[i][j]` 设置为这条边的权重。
- 对于所有的顶点 `i`,将 `D[i][i]` 设置为 0。
2. 迭代:
- 遍历所有可能的顶点 `k` 作为中间顶点。
- 对于每一对顶点 `(i, j)`:检查是否存在一条经过顶点 `k` 的更短路径,即检查 `D[i][k] + D[k][j]` 是否小于 `D[i][j]`。
- 如果是,则更新 `D[i][j]` 为 `D[i][k] + D[k][j]`。
3. 检测负权重循环:
- 在完成上述迭代之后,如果存在某个顶点 `i` 使得 `D[i][i] < 0`,则说明图中存在至少一个负权重循环。在这种情况下,算法会报告错误或者返回一个特殊值表示无法找到最短路径。
复杂度:
- 时间复杂度:O(V^3),其中 V 是顶点的数量。
- 空间复杂度:O(V^2),需要存储每个顶点对之间的最短路径长度。
4. 主要代码
顶点图:
public class FloydWarshall {
public static void main(String[] args) {
Vertex v1 = new Vertex("1");
Vertex v2 = new Vertex("2");
Vertex v3 = new Vertex("3");
Vertex v4 = new Vertex("4");
v1.edges = new ArrayList<>();
v1.edges.add(new Edge(v3, -2));
v2.edges = new ArrayList<>();
v2.edges.add(new Edge(v1, 4));
v2.edges.add(new Edge(v3, 3));
v3.edges = new ArrayList<>();
v3.edges.add(new Edge(v4, 2));
v4.edges = new ArrayList<>();
v4.edges.add(new Edge(v2, -1));
List<Vertex> graph = new ArrayList<>();
graph.add(v1);
graph.add(v2);
graph.add(v3);
graph.add(v4);
floydwarshall(graph);
}
public static void floydwarshall(List<Vertex> graph) {
int size = graph.size();
int[][] dist = new int[size][size];
// 邻接矩阵初始化
for (int i = 0; i < size; i++) {
Vertex vi = graph.get(i);
// 哈希表用来判断vi顶点的邻接点集合是否包含了vj顶点
// 如果包含了,则dist[i][j]用边的权重表示
// 否则用无穷大表示
Map<Vertex, Integer> map =
vi.edges.stream().collect(Collectors.toMap(e -> e.linked, e -> e.weight));
for(int j = 0; j < size; j++) {
Vertex vj = graph.get(j);
if(vi == vj){
dist[i][j] = 0;
} else {
dist[i][j] = map.getOrDefault(vj, Integer.MAX_VALUE);
}
}
}
// print(dist);
// 看是否能够借路到其他顶点
// k = 0时,其他顶点接路v1到达其他顶点
// k = 1时,其他顶点接路v2到达其他顶点...
// 即i的行,借助k的顶点,是否可以到达j列顶点
for(int k = 0; k < size; k++){
// 处理dist数组
for(int i = 0; i < size; i++){
for(int j = 0; j < size; j++){
// 假设这里的k=0, i = 1,
// 意思就是,v2顶点借助v1顶点是否可以到达其他顶点(v1,v2,v3,v4)
if(dist[i][k] != Integer.MAX_VALUE &&
dist[k][j] != Integer.MAX_VALUE &&
dist[i][k] + dist[k][j] < dist[i][j]){
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
print(dist);
System.out.println("----------");
}
}
public static void print(int[][] dist) {
for(int i = 0; i < 4; i++) {
System.out.print("[");
for(int j = 0; j < 4; j++){
System.out.print(dist[i][j] + " ");
}
System.out.print("]");
System.out.println();
}
}
}
输出为:
[0 2147483647 -2 2147483647 ]
[4 0 2 2147483647 ]
[2147483647 2147483647 0 2 ]
[2147483647 -1 2147483647 0 ]
----------
[0 2147483647 -2 2147483647 ]
[4 0 2 2147483647 ]
[2147483647 2147483647 0 2 ]
[3 -1 1 0 ]
----------
[0 2147483647 -2 0 ]
[4 0 2 4 ]
[2147483647 2147483647 0 2 ]
[3 -1 1 0 ]
----------
[0 -1 -2 0 ]
[4 0 2 4 ]
[5 1 0 2 ]
[3 -1 1 0 ]
----------