diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPaths.java index 04e33e8fc00..324f2551bb2 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPaths.java +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPaths.java @@ -58,12 +58,28 @@ public class FloydWarshallShortestPaths private Object[][] backtrace = null; private Object[][] lastHopMatrix = null; + // Should the algorithm attempt to find negative cycles + private final boolean detectNegativeCycles; + /** * Create a new instance of the Floyd-Warshall all-pairs shortest path algorithm. * * @param graph the input graph */ public FloydWarshallShortestPaths(Graph graph) + { + this(graph, false); + } + + /** + * Create a new instance of the Floyd-Warshall all-pairs shortest path algorithm. + * + * Optionally, detect negative cycles by disabling iteration optimizations. + * + * @param graph the input graph + * @param detectNegativeCycles optionally detect negative cycles + */ + public FloydWarshallShortestPaths(Graph graph, boolean detectNegativeCycles) { super(graph); @@ -99,6 +115,7 @@ public FloydWarshallShortestPaths(Graph graph) } this.minDegreeOne = minDegreeOne; this.minDegreeTwo = minDegreeTwo; + this.detectNegativeCycles = detectNegativeCycles; } /** @@ -241,6 +258,24 @@ public V getLastHop(V a, V b) } } + /** + * Return whether the graph contains a negative cycle. + * This runs in linear time by checking the self-loops of the adjacency matrix. + * @return true if a negative cycle is detected, otherwise false + * + */ + public boolean containsNegativeCycle() + { + lazyCalculateMatrix(); + int n = vertices.size(); + for (int i = 0; i < n; i++) { + if (d[i][i] < 0.0) { + return true; + } + } + return false; + } + /** * Calculates the matrix of all shortest paths, but does not populate the last hops matrix. */ @@ -305,11 +340,11 @@ private void lazyCalculateMatrix() // run fw alg for (int k = minDegreeTwo; k < n; k++) { for (int i = minDegreeOne; i < n; i++) { - if (i == k) { + if (!detectNegativeCycles && i == k) { continue; } for (int j = minDegreeOne; j < n; j++) { - if (i == j || j == k) { + if (!detectNegativeCycles && (i == j || j == k)) { continue; } diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPathsTest.java index 2680a3f3988..5d0f7937e7c 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPathsTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPathsTest.java @@ -162,4 +162,18 @@ public void testWeightedEdges() assertEquals(fw.getLastHop("a", "b"), vertexPath.get(vertexPath.size() - 2)); assertNull(fw.getPath("b", "a")); } + + @Test + public void testNegativeCycleDetection() + { + DefaultDirectedWeightedGraph negativeCycle = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + negativeCycle.addVertex("a"); + negativeCycle.addVertex("b"); + negativeCycle.setEdgeWeight(negativeCycle.addEdge("a", "b"), 0.0); + negativeCycle.setEdgeWeight(negativeCycle.addEdge("b", "a"), -1.0); + FloydWarshallShortestPaths fw = + new FloydWarshallShortestPaths<>(negativeCycle, true); + assertTrue(fw.containsNegativeCycle()); + } }