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 */
1011package 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
1513import static java .lang .Double .NEGATIVE_INFINITY ;
1614import static java .lang .Double .POSITIVE_INFINITY ;
1715
1816import java .util .ArrayList ;
17+ import java .util .Arrays ;
1918import java .util .List ;
19+ import java .util .stream .Collectors ;
2020
2121public 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