org.jgrapht.alg.shortestpath.FloydWarshallShortestPaths Maven / Gradle / Ivy
/*
* (C) Copyright 2009-2023, by Tom Larkworthy and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.shortestpath;
import org.jgrapht.*;
import org.jgrapht.alg.util.*;
import org.jgrapht.graph.*;
import org.jgrapht.util.*;
import java.util.*;
/**
* The Floyd-Warshall algorithm.
*
*
* The Floyd-Warshall algorithm
* finds all shortest paths (all $n^2$ of them) in $O(n^3)$ time. Note that during construction
* time, no computations are performed! All computations are performed the first time one of the
* member methods of this class is invoked. The results are stored, so all subsequent calls to the
* same method are computationally efficient.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Tom Larkworthy
* @author Soren Davidsen ([email protected])
* @author Joris Kinable
* @author Dimitrios Michail
*/
public class FloydWarshallShortestPaths
extends BaseShortestPathAlgorithm
{
private final List vertices;
private final List degrees;
private final Map vertexIndices;
// minimum vertex with degree at least 1
private final int minDegreeOne;
// minimum vertex with degree at least 2
private final int minDegreeTwo;
private double[][] d = null;
private Object[][] backtrace = null;
private Object[][] lastHopMatrix = null;
/**
* Create a new instance of the Floyd-Warshall all-pairs shortest path algorithm.
*
* @param graph the input graph
*/
public FloydWarshallShortestPaths(Graph graph)
{
super(graph);
/*
* Sort vertices by degree in ascending order and index them. Also compute the minimum
* vertex which has degree at least one and at least two.
*/
this.vertices = new ArrayList<>(graph.vertexSet());
Collections.sort(vertices, VertexDegreeComparator.of(graph));
this.degrees = new ArrayList<>();
this.vertexIndices = CollectionUtil.newHashMapWithExpectedSize(this.vertices.size());
int i = 0;
int minDegreeOne = vertices.size();
int minDegreeTwo = vertices.size();
for (V vertex : vertices) {
vertexIndices.put(vertex, i);
int degree = graph.degreeOf(vertex);
degrees.add(degree);
if (degree > 1) {
if (i < minDegreeOne) {
minDegreeOne = i;
}
if (i < minDegreeTwo) {
minDegreeTwo = i;
}
} else if (i < minDegreeOne && degree == 1) {
minDegreeOne = i;
}
++i;
}
this.minDegreeOne = minDegreeOne;
this.minDegreeTwo = minDegreeTwo;
}
/**
* Get the total number of shortest paths. Does not count the paths from a vertex to itself.
*
* @return total number of shortest paths
*/
public int getShortestPathsCount()
{
lazyCalculateMatrix();
// count shortest paths
int n = vertices.size();
int nShortestPaths = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j && Double.isFinite(d[i][j])) {
nShortestPaths++;
}
}
}
return nShortestPaths;
}
/**
* {@inheritDoc}
*/
@Override
public GraphPath getPath(V a, V b)
{
if (!graph.containsVertex(a)) {
throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX);
}
if (!graph.containsVertex(b)) {
throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX);
}
lazyCalculateMatrix();
int vA = vertexIndices.get(a);
int vB = vertexIndices.get(b);
if (backtrace[vA][vB] == null) { // No path exists
return createEmptyPath(a, b);
}
// Reconstruct the path
List edges = new ArrayList<>();
V u = a;
while (!u.equals(b)) {
int vU = vertexIndices.get(u);
E e = TypeUtil.uncheckedCast(backtrace[vU][vB]);
edges.add(e);
u = Graphs.getOppositeVertex(graph, e, u);
}
return new GraphWalk<>(graph, a, b, null, edges, d[vA][vB]);
}
/**
* {@inheritDoc}
*/
@Override
public double getPathWeight(V source, V sink)
{
if (!graph.containsVertex(source)) {
throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX);
}
if (!graph.containsVertex(sink)) {
throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX);
}
lazyCalculateMatrix();
return d[vertexIndices.get(source)][vertexIndices.get(sink)];
}
/**
* {@inheritDoc}
*/
@Override
public SingleSourcePaths getPaths(V source)
{
return new FloydWarshallSingleSourcePaths(source);
}
/**
* Returns the first hop, i.e., the second node on the shortest path from $a$ to $b$. Lookup
* time is $O(1)$. If the shortest path from $a$ to $b$ is $a,c,d,e,b$, this method returns $c$.
* If the next invocation would query the first hop on the shortest path from $c$ to $b$, vertex
* $d$ would be returned, etc. This method is computationally cheaper than calling
* {@link #getPath(Object, Object)} and then reading the first vertex.
*
* @param a source vertex
* @param b target vertex
* @return next hop on the shortest path from a to b, or null when there exists no path from $a$
* to $b$.
*/
public V getFirstHop(V a, V b)
{
lazyCalculateMatrix();
int vA = vertexIndices.get(a);
int vB = vertexIndices.get(b);
if (backtrace[vA][vB] == null) { // No path exists
return null;
} else {
E e = TypeUtil.uncheckedCast(backtrace[vA][vB]);
return Graphs.getOppositeVertex(graph, e, a);
}
}
/**
* Returns the last hop, i.e., the second to last node on the shortest path from $a$ to $b$.
* Lookup time is $O(1)$. If the shortest path from $a$ to $b$ is $a,c,d,e,b$, this method
* returns $e$. If the next invocation would query the next hop on the shortest path from $c$ to
* $e$, vertex $d$ would be returned, etc. This method is computationally cheaper than calling
* {@link #getPath(Object, Object)} and then reading the vertex. The first invocation of this
* method populates a last hop matrix.
*
* @param a source vertex
* @param b target vertex
* @return last hop on the shortest path from $a$ to $b$, or null when there exists no path from
* $a$ to $b$.
*/
public V getLastHop(V a, V b)
{
lazyCalculateMatrix();
int vA = vertexIndices.get(a);
int vB = vertexIndices.get(b);
if (backtrace[vA][vB] == null) { // No path exists
return null;
} else {
populateLastHopMatrix();
E e = TypeUtil.uncheckedCast(lastHopMatrix[vA][vB]);
return Graphs.getOppositeVertex(graph, e, b);
}
}
/**
* Calculates the matrix of all shortest paths, but does not populate the last hops matrix.
*/
private void lazyCalculateMatrix()
{
if (d != null) {
// already done
return;
}
int n = vertices.size();
// init the backtrace matrix
backtrace = new Object[n][n];
// initialize matrix, 0
d = new double[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(d[i], Double.POSITIVE_INFINITY);
}
// initialize matrix, 1
for (int i = 0; i < n; i++) {
d[i][i] = 0.0;
}
// initialize matrix, 2
if (graph.getType().isUndirected()) {
for (E edge : graph.edgeSet()) {
V source = graph.getEdgeSource(edge);
V target = graph.getEdgeTarget(edge);
if (!source.equals(target)) {
int v1 = vertexIndices.get(source);
int v2 = vertexIndices.get(target);
double edgeWeight = graph.getEdgeWeight(edge);
if (Double.compare(edgeWeight, d[v1][v2]) < 0) {
d[v1][v2] = d[v2][v1] = edgeWeight;
backtrace[v1][v2] = edge;
backtrace[v2][v1] = edge;
}
}
}
} else { // This works for both Directed and Mixed graphs! Iterating over
// the arcs and querying source/sink does not suffice for graphs
// which contain both edges and arcs
for (V v1 : graph.vertexSet()) {
int i1 = vertexIndices.get(v1);
for (E e : graph.outgoingEdgesOf(v1)) {
V v2 = Graphs.getOppositeVertex(graph, e, v1);
if (!v1.equals(v2)) {
int i2 = vertexIndices.get(v2);
double edgeWeight = graph.getEdgeWeight(e);
if (Double.compare(edgeWeight, d[i1][i2]) < 0) {
d[i1][i2] = edgeWeight;
backtrace[i1][i2] = e;
}
}
}
}
}
// run fw alg
for (int k = minDegreeTwo; k < n; k++) {
for (int i = minDegreeOne; i < n; i++) {
if (i == k) {
continue;
}
for (int j = minDegreeOne; j < n; j++) {
if (i == j || j == k) {
continue;
}
double sumIKKJ = d[i][k] + d[k][j];
if (Double.compare(sumIKKJ, d[i][j]) < 0) {
d[i][j] = sumIKKJ;
backtrace[i][j] = backtrace[i][k];
}
}
}
}
}
/**
* Populate the last hop matrix, using the earlier computed backtrace matrix.
*/
private void populateLastHopMatrix()
{
lazyCalculateMatrix();
if (lastHopMatrix != null) {
return;
}
// Initialize matrix
int n = vertices.size();
lastHopMatrix = new Object[n][n];
// Populate matrix
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j || lastHopMatrix[i][j] != null || backtrace[i][j] == null) {
continue;
}
// Reconstruct the path from i to j
V u = vertices.get(i);
V b = vertices.get(j);
while (!u.equals(b)) {
int vU = vertexIndices.get(u);
E e = TypeUtil.uncheckedCast(backtrace[vU][j]);
V other = Graphs.getOppositeVertex(graph, e, u);
lastHopMatrix[i][vertexIndices.get(other)] = e;
u = other;
}
}
}
}
class FloydWarshallSingleSourcePaths
implements SingleSourcePaths
{
private final V source;
public FloydWarshallSingleSourcePaths(V source)
{
this.source = source;
}
@Override
public Graph getGraph()
{
return graph;
}
@Override
public V getSourceVertex()
{
return source;
}
@Override
public double getWeight(V sink)
{
return FloydWarshallShortestPaths.this.getPathWeight(source, sink);
}
@Override
public GraphPath getPath(V sink)
{
return FloydWarshallShortestPaths.this.getPath(source, sink);
}
}
}