Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,49 +1,54 @@
/**
* This file contains an implementation of the Floyd-Warshall algorithm to find all pairs of
* shortest paths between nodes in a graph. We also demonstrate how to detect negative cycles and
* reconstruct the shortest path.
* Implementation of the Floyd-Warshall algorithm to find all pairs of shortest paths between nodes
* in a graph. Also demonstrates how to detect negative cycles and reconstruct the shortest path.
*
* <p>Time Complexity: O(V^3)
* <p>Time: O(V^3)
*
* <p>Space: O(V^2)
*
* @author Micah Stairs, William Fiset
*/
package com.williamfiset.algorithms.graphtheory;

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FloydWarshallSolver {

private int n;
private final int n;
private boolean solved;
private double[][] dp;
private Integer[][] next;

private static final int REACHES_NEGATIVE_CYCLE = -1;

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

// Copy input matrix and setup 'next' matrix for path reconstruction.
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] != POSITIVE_INFINITY) next[i][j] = j;
if (matrix[i][j] != POSITIVE_INFINITY)
next[i][j] = j;
dp[i][j] = matrix[i][j];
}
}
Expand All @@ -52,30 +57,28 @@ public FloydWarshallSolver(double[][] matrix) {
/**
* Runs Floyd-Warshall to compute the shortest distance between every pair of nodes.
*
* @return The solved All Pairs Shortest Path (APSP) matrix.
* @return the solved All Pairs Shortest Path (APSP) matrix.
*/
public double[][] getApspMatrix() {
solve();
return dp;
}

// Executes the Floyd-Warshall algorithm.
/** Executes the Floyd-Warshall algorithm. */
public void solve() {
if (solved) return;
if (solved)
return;

// Compute all pairs shortest paths.
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (dp[i][k] + dp[k][j] < dp[i][j]) {
dp[i][j] = dp[i][k] + dp[k][j];
next[i][j] = next[i][k];
}
}
}
}

// Identify negative cycles by propagating the value 'NEGATIVE_INFINITY'
// Identify negative cycles by propagating NEGATIVE_INFINITY
// to every edge that is part of or reaches into a negative cycle.
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
Expand All @@ -91,117 +94,86 @@ public void solve() {
/**
* Reconstructs the shortest path (of nodes) from 'start' to 'end' inclusive.
*
* @return An array of nodes indexes of the shortest path from 'start' to 'end'. If 'start' and
* 'end' are not connected return an empty array. If the shortest path from 'start' to 'end'
* are reachable by a negative cycle return -1.
* @return an array of node indexes of the shortest path from 'start' to 'end'. If 'start' and
* 'end' are not connected return an empty list. If the shortest path from 'start' to 'end'
* reaches a negative cycle return null.
*/
public List<Integer> reconstructShortestPath(int start, int end) {
solve();
List<Integer> path = new ArrayList<>();
if (dp[start][end] == POSITIVE_INFINITY) return path;
if (dp[start][end] == POSITIVE_INFINITY)
return path;
int at = start;
for (; at != end; at = next[at][end]) {
// Return null since there are an infinite number of shortest paths.
if (at == REACHES_NEGATIVE_CYCLE) return null;
if (at == REACHES_NEGATIVE_CYCLE)
return null;
path.add(at);
}
// Return null since there are an infinite number of shortest paths.
if (next[at][end] == REACHES_NEGATIVE_CYCLE) return null;
if (next[at][end] == REACHES_NEGATIVE_CYCLE)
return null;
path.add(end);
return path;
}

/* Example usage. */

// Creates a graph with n nodes. The adjacency matrix is constructed
// such that the value of going from a node to itself is 0.
/** Creates an n x n adjacency matrix initialized with POSITIVE_INFINITY and zero diagonal. */
public static double[][] createGraph(int n) {
double[][] matrix = new double[n][n];
for (int i = 0; i < n; i++) {
java.util.Arrays.fill(matrix[i], POSITIVE_INFINITY);
Arrays.fill(matrix[i], POSITIVE_INFINITY);
matrix[i][i] = 0;
}
return matrix;
}

public static void main(String[] args) {
// Construct graph.
int n = 7;
double[][] m = createGraph(n);
exampleWithNegativeCycle();
System.out.println();
exampleSimpleGraph();
}

// Add some edge values.
m[0][1] = 2;
m[0][2] = 5;
m[0][6] = 10;
m[1][2] = 2;
m[1][4] = 11;
m[2][6] = 2;
m[6][5] = 11;
m[4][5] = 1;
m[5][4] = -2;
// Example 1: 4-node graph with a negative cycle between nodes 2 and 3.
private static void exampleWithNegativeCycle() {
int n = 4;
double[][] m = createGraph(n);
m[0][1] = 4;
m[1][2] = 1;
m[2][3] = 2;
m[3][2] = -5; // Creates negative cycle: 2 -> 3 -> 2 (net cost -3).

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

for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
System.out.printf("This shortest path from node %d to node %d is %.3f\n", i, j, dist[i][j]);

// Prints:
// This shortest path from node 0 to node 0 is 0.000
// This shortest path from node 0 to node 1 is 2.000
// This shortest path from node 0 to node 2 is 4.000
// This shortest path from node 0 to node 3 is Infinity
// This shortest path from node 0 to node 4 is -Infinity
// This shortest path from node 0 to node 5 is -Infinity
// This shortest path from node 0 to node 6 is 6.000
// This shortest path from node 1 to node 0 is Infinity
// This shortest path from node 1 to node 1 is 0.000
// This shortest path from node 1 to node 2 is 2.000
// This shortest path from node 1 to node 3 is Infinity
// ...
System.out.println("=== Example 1: Negative cycle ===");
System.out.printf("dist(0, 1) = %.0f\n", dist[0][1]); // 4
System.out.printf("dist(0, 2) = %.0f\n", dist[0][2]); // -Infinity (reaches negative cycle)
System.out.printf("path(0, 2) = %s\n", formatPath(solver.reconstructShortestPath(0, 2), 0, 2));
System.out.printf("path(0, 1) = %s\n", formatPath(solver.reconstructShortestPath(0, 1), 0, 1));
}

System.out.println();
// Example 2: 4-node directed graph with no negative cycles.
private static void exampleSimpleGraph() {
int n = 4;
double[][] m = createGraph(n);
m[0][1] = 1;
m[1][2] = 3;
m[1][3] = 10;
m[2][3] = 2;

// Reconstructs the shortest paths from all nodes to every other nodes.
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
List<Integer> path = solver.reconstructShortestPath(i, j);
String str;
if (path == null) {
str = "HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)";
} else if (path.size() == 0) {
str = String.format("DOES NOT EXIST (node %d doesn't reach node %d)", i, j);
} else {
str =
String.join(
" -> ",
path.stream()
.map(Object::toString)
.collect(java.util.stream.Collectors.toList()));
str = "is: [" + str + "]";
}

System.out.printf("The shortest path from node %d to node %d %s\n", i, j, str);
}
}
FloydWarshallSolver solver = new FloydWarshallSolver(m);
double[][] dist = solver.getApspMatrix();

System.out.println("=== Example 2: Simple directed graph ===");
// Shortest distance from 0 to 3 is 6 (0 -> 1 -> 2 -> 3), not 11 (0 -> 1 -> 3).
System.out.printf("dist(0, 3) = %.0f\n", dist[0][3]);
System.out.printf("path(0, 3) = %s\n", formatPath(solver.reconstructShortestPath(0, 3), 0, 3));
System.out.printf("path(3, 0) = %s\n", formatPath(solver.reconstructShortestPath(3, 0), 3, 0));
}

// Prints:
// The shortest path from node 0 to node 0 is: [0]
// The shortest path from node 0 to node 1 is: [0 -> 1]
// The shortest path from node 0 to node 2 is: [0 -> 1 -> 2]
// The shortest path from node 0 to node 3 DOES NOT EXIST (node 0 doesn't reach node 3)
// The shortest path from node 0 to node 4 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
// The shortest path from node 0 to node 5 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
// The shortest path from node 0 to node 6 is: [0 -> 1 -> 2 -> 6]
// The shortest path from node 1 to node 0 DOES NOT EXIST (node 1 doesn't reach node 0)
// The shortest path from node 1 to node 1 is: [1]
// The shortest path from node 1 to node 2 is: [1 -> 2]
// The shortest path from node 1 to node 3 DOES NOT EXIST (node 1 doesn't reach node 3)
// The shortest path from node 1 to node 4 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
// The shortest path from node 1 to node 5 HAS AN ∞ NUMBER OF SOLUTIONS! (negative cycle case)
// The shortest path from node 1 to node 6 is: [1 -> 2 -> 6]
// The shortest path from node 2 to node 0 DOES NOT EXIST (node 2 doesn't reach node 0)
// ...
private static String formatPath(List<Integer> path, int start, int end) {
if (path == null)
return "NEGATIVE CYCLE";
if (path.isEmpty())
return String.format("NO PATH (%d doesn't reach %d)", start, end);
return path.stream().map(Object::toString).collect(Collectors.joining(" -> "));
}
}
Loading