Skip to content

Commit 5f53462

Browse files
williamfisetclaude
andauthored
Refactor FloydWarshallSolver: clean up, add docs, simplify examples (#1306)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7336ab4 commit 5f53462

1 file changed

Lines changed: 79 additions & 107 deletions

File tree

Lines changed: 79 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,54 @@
11
/**
2-
* This file contains an implementation of the Floyd-Warshall algorithm to find all pairs of
3-
* shortest paths between nodes in a graph. We also demonstrate how to detect negative cycles and
4-
* reconstruct the shortest path.
2+
* Implementation of the Floyd-Warshall algorithm to find all pairs of shortest paths between nodes
3+
* in a graph. Also demonstrates how to detect negative cycles and reconstruct the shortest path.
54
*
6-
* <p>Time Complexity: O(V^3)
5+
* <p>Time: O(V^3)
6+
*
7+
* <p>Space: O(V^2)
78
*
89
* @author Micah Stairs, William Fiset
910
*/
1011
package com.williamfiset.algorithms.graphtheory;
1112

12-
// Import Java's special constants ∞ and -∞ which behave
13-
// as you expect them to when you do arithmetic. For example,
14-
// ∞ + ∞ = ∞, ∞ + x = ∞, -∞ + x = -∞ and ∞ + -∞ = Nan
1513
import static java.lang.Double.NEGATIVE_INFINITY;
1614
import static java.lang.Double.POSITIVE_INFINITY;
1715

1816
import java.util.ArrayList;
17+
import java.util.Arrays;
1918
import java.util.List;
19+
import java.util.stream.Collectors;
2020

2121
public class FloydWarshallSolver {
2222

23-
private int n;
23+
private final int n;
2424
private boolean solved;
2525
private double[][] dp;
2626
private Integer[][] next;
2727

2828
private static final int REACHES_NEGATIVE_CYCLE = -1;
2929

3030
/**
31-
* As input, this class takes an adjacency matrix with edge weights between nodes, where
32-
* POSITIVE_INFINITY is used to indicate that two nodes are not connected.
31+
* Creates a Floyd-Warshall solver from an adjacency matrix with edge weights between nodes, where
32+
* POSITIVE_INFINITY indicates that two nodes are not connected.
3333
*
3434
* <p>NOTE: Usually the diagonal of the adjacency matrix is all zeros (i.e. matrix[i][i] = 0 for
35-
* all i) since there is typically no cost to go from a node to itself, but this may depend on
36-
* your graph and the problem you are trying to solve.
35+
* all i) since there is typically no cost to go from a node to itself, but this may depend on the
36+
* graph and the problem being solved.
37+
*
38+
* @param matrix an n x n adjacency matrix of edge weights.
39+
* @throws IllegalArgumentException if the matrix is null or empty.
3740
*/
3841
public FloydWarshallSolver(double[][] matrix) {
42+
if (matrix == null || matrix.length == 0)
43+
throw new IllegalArgumentException("Matrix cannot be null or empty.");
3944
n = matrix.length;
4045
dp = new double[n][n];
4146
next = new Integer[n][n];
4247

43-
// Copy input matrix and setup 'next' matrix for path reconstruction.
4448
for (int i = 0; i < n; i++) {
4549
for (int j = 0; j < n; j++) {
46-
if (matrix[i][j] != POSITIVE_INFINITY) next[i][j] = j;
50+
if (matrix[i][j] != POSITIVE_INFINITY)
51+
next[i][j] = j;
4752
dp[i][j] = matrix[i][j];
4853
}
4954
}
@@ -52,30 +57,28 @@ public FloydWarshallSolver(double[][] matrix) {
5257
/**
5358
* Runs Floyd-Warshall to compute the shortest distance between every pair of nodes.
5459
*
55-
* @return The solved All Pairs Shortest Path (APSP) matrix.
60+
* @return the solved All Pairs Shortest Path (APSP) matrix.
5661
*/
5762
public double[][] getApspMatrix() {
5863
solve();
5964
return dp;
6065
}
6166

62-
// Executes the Floyd-Warshall algorithm.
67+
/** Executes the Floyd-Warshall algorithm. */
6368
public void solve() {
64-
if (solved) return;
69+
if (solved)
70+
return;
6571

6672
// Compute all pairs shortest paths.
67-
for (int k = 0; k < n; k++) {
68-
for (int i = 0; i < n; i++) {
69-
for (int j = 0; j < n; j++) {
73+
for (int k = 0; k < n; k++)
74+
for (int i = 0; i < n; i++)
75+
for (int j = 0; j < n; j++)
7076
if (dp[i][k] + dp[k][j] < dp[i][j]) {
7177
dp[i][j] = dp[i][k] + dp[k][j];
7278
next[i][j] = next[i][k];
7379
}
74-
}
75-
}
76-
}
7780

78-
// Identify negative cycles by propagating the value 'NEGATIVE_INFINITY'
81+
// Identify negative cycles by propagating NEGATIVE_INFINITY
7982
// to every edge that is part of or reaches into a negative cycle.
8083
for (int k = 0; k < n; k++)
8184
for (int i = 0; i < n; i++)
@@ -91,117 +94,86 @@ public void solve() {
9194
/**
9295
* Reconstructs the shortest path (of nodes) from 'start' to 'end' inclusive.
9396
*
94-
* @return An array of nodes indexes of the shortest path from 'start' to 'end'. If 'start' and
95-
* 'end' are not connected return an empty array. If the shortest path from 'start' to 'end'
96-
* are reachable by a negative cycle return -1.
97+
* @return an array of node indexes of the shortest path from 'start' to 'end'. If 'start' and
98+
* 'end' are not connected return an empty list. If the shortest path from 'start' to 'end'
99+
* reaches a negative cycle return null.
97100
*/
98101
public List<Integer> reconstructShortestPath(int start, int end) {
99102
solve();
100103
List<Integer> path = new ArrayList<>();
101-
if (dp[start][end] == POSITIVE_INFINITY) return path;
104+
if (dp[start][end] == POSITIVE_INFINITY)
105+
return path;
102106
int at = start;
103107
for (; at != end; at = next[at][end]) {
104-
// Return null since there are an infinite number of shortest paths.
105-
if (at == REACHES_NEGATIVE_CYCLE) return null;
108+
if (at == REACHES_NEGATIVE_CYCLE)
109+
return null;
106110
path.add(at);
107111
}
108-
// Return null since there are an infinite number of shortest paths.
109-
if (next[at][end] == REACHES_NEGATIVE_CYCLE) return null;
112+
if (next[at][end] == REACHES_NEGATIVE_CYCLE)
113+
return null;
110114
path.add(end);
111115
return path;
112116
}
113117

114-
/* Example usage. */
115-
116-
// Creates a graph with n nodes. The adjacency matrix is constructed
117-
// such that the value of going from a node to itself is 0.
118+
/** Creates an n x n adjacency matrix initialized with POSITIVE_INFINITY and zero diagonal. */
118119
public static double[][] createGraph(int n) {
119120
double[][] matrix = new double[n][n];
120121
for (int i = 0; i < n; i++) {
121-
java.util.Arrays.fill(matrix[i], POSITIVE_INFINITY);
122+
Arrays.fill(matrix[i], POSITIVE_INFINITY);
122123
matrix[i][i] = 0;
123124
}
124125
return matrix;
125126
}
126127

127128
public static void main(String[] args) {
128-
// Construct graph.
129-
int n = 7;
130-
double[][] m = createGraph(n);
129+
exampleWithNegativeCycle();
130+
System.out.println();
131+
exampleSimpleGraph();
132+
}
131133

132-
// Add some edge values.
133-
m[0][1] = 2;
134-
m[0][2] = 5;
135-
m[0][6] = 10;
136-
m[1][2] = 2;
137-
m[1][4] = 11;
138-
m[2][6] = 2;
139-
m[6][5] = 11;
140-
m[4][5] = 1;
141-
m[5][4] = -2;
134+
// Example 1: 4-node graph with a negative cycle between nodes 2 and 3.
135+
private static void exampleWithNegativeCycle() {
136+
int n = 4;
137+
double[][] m = createGraph(n);
138+
m[0][1] = 4;
139+
m[1][2] = 1;
140+
m[2][3] = 2;
141+
m[3][2] = -5; // Creates negative cycle: 2 -> 3 -> 2 (net cost -3).
142142

143143
FloydWarshallSolver solver = new FloydWarshallSolver(m);
144144
double[][] dist = solver.getApspMatrix();
145145

146-
for (int i = 0; i < n; i++)
147-
for (int j = 0; j < n; j++)
148-
System.out.printf("This shortest path from node %d to node %d is %.3f\n", i, j, dist[i][j]);
149-
150-
// Prints:
151-
// This shortest path from node 0 to node 0 is 0.000
152-
// This shortest path from node 0 to node 1 is 2.000
153-
// This shortest path from node 0 to node 2 is 4.000
154-
// This shortest path from node 0 to node 3 is Infinity
155-
// This shortest path from node 0 to node 4 is -Infinity
156-
// This shortest path from node 0 to node 5 is -Infinity
157-
// This shortest path from node 0 to node 6 is 6.000
158-
// This shortest path from node 1 to node 0 is Infinity
159-
// This shortest path from node 1 to node 1 is 0.000
160-
// This shortest path from node 1 to node 2 is 2.000
161-
// This shortest path from node 1 to node 3 is Infinity
162-
// ...
146+
System.out.println("=== Example 1: Negative cycle ===");
147+
System.out.printf("dist(0, 1) = %.0f\n", dist[0][1]); // 4
148+
System.out.printf("dist(0, 2) = %.0f\n", dist[0][2]); // -Infinity (reaches negative cycle)
149+
System.out.printf("path(0, 2) = %s\n", formatPath(solver.reconstructShortestPath(0, 2), 0, 2));
150+
System.out.printf("path(0, 1) = %s\n", formatPath(solver.reconstructShortestPath(0, 1), 0, 1));
151+
}
163152

164-
System.out.println();
153+
// Example 2: 4-node directed graph with no negative cycles.
154+
private static void exampleSimpleGraph() {
155+
int n = 4;
156+
double[][] m = createGraph(n);
157+
m[0][1] = 1;
158+
m[1][2] = 3;
159+
m[1][3] = 10;
160+
m[2][3] = 2;
165161

166-
// Reconstructs the shortest paths from all nodes to every other nodes.
167-
for (int i = 0; i < n; i++) {
168-
for (int j = 0; j < n; j++) {
169-
List<Integer> path = solver.reconstructShortestPath(i, j);
170-
String str;
171-
if (path == null) {
172-
str = "HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)";
173-
} else if (path.size() == 0) {
174-
str = String.format("DOES NOT EXIST (node %d doesn't reach node %d)", i, j);
175-
} else {
176-
str =
177-
String.join(
178-
" -> ",
179-
path.stream()
180-
.map(Object::toString)
181-
.collect(java.util.stream.Collectors.toList()));
182-
str = "is: [" + str + "]";
183-
}
184-
185-
System.out.printf("The shortest path from node %d to node %d %s\n", i, j, str);
186-
}
187-
}
162+
FloydWarshallSolver solver = new FloydWarshallSolver(m);
163+
double[][] dist = solver.getApspMatrix();
164+
165+
System.out.println("=== Example 2: Simple directed graph ===");
166+
// Shortest distance from 0 to 3 is 6 (0 -> 1 -> 2 -> 3), not 11 (0 -> 1 -> 3).
167+
System.out.printf("dist(0, 3) = %.0f\n", dist[0][3]);
168+
System.out.printf("path(0, 3) = %s\n", formatPath(solver.reconstructShortestPath(0, 3), 0, 3));
169+
System.out.printf("path(3, 0) = %s\n", formatPath(solver.reconstructShortestPath(3, 0), 3, 0));
170+
}
188171

189-
// Prints:
190-
// The shortest path from node 0 to node 0 is: [0]
191-
// The shortest path from node 0 to node 1 is: [0 -> 1]
192-
// The shortest path from node 0 to node 2 is: [0 -> 1 -> 2]
193-
// The shortest path from node 0 to node 3 DOES NOT EXIST (node 0 doesn't reach node 3)
194-
// The shortest path from node 0 to node 4 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
195-
// The shortest path from node 0 to node 5 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
196-
// The shortest path from node 0 to node 6 is: [0 -> 1 -> 2 -> 6]
197-
// The shortest path from node 1 to node 0 DOES NOT EXIST (node 1 doesn't reach node 0)
198-
// The shortest path from node 1 to node 1 is: [1]
199-
// The shortest path from node 1 to node 2 is: [1 -> 2]
200-
// The shortest path from node 1 to node 3 DOES NOT EXIST (node 1 doesn't reach node 3)
201-
// The shortest path from node 1 to node 4 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
202-
// The shortest path from node 1 to node 5 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
203-
// The shortest path from node 1 to node 6 is: [1 -> 2 -> 6]
204-
// The shortest path from node 2 to node 0 DOES NOT EXIST (node 2 doesn't reach node 0)
205-
// ...
172+
private static String formatPath(List<Integer> path, int start, int end) {
173+
if (path == null)
174+
return "NEGATIVE CYCLE";
175+
if (path.isEmpty())
176+
return String.format("NO PATH (%d doesn't reach %d)", start, end);
177+
return path.stream().map(Object::toString).collect(Collectors.joining(" -> "));
206178
}
207179
}

0 commit comments

Comments
 (0)