All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.shortestpath.BellmanFordShortestPath Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2017-2023, by Dimitrios Michail 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 java.lang.reflect.*;
import java.util.*;

/**
 * The Bellman-Ford algorithm.
 *
 * 

* Computes shortest paths from a single source vertex to all other vertices in a weighted graph. * The Bellman-Ford algorithm supports negative edge weights. * *

* Negative weight cycles are not allowed and will be reported by the algorithm. This implies that * negative edge weights are not allowed in undirected graphs. In such cases the code will throw an * exception of type {@link NegativeCycleDetectedException} which will contain the detected negative * weight cycle. Note that the algorithm will not report or find negative weight cycles which are * not reachable from the source vertex. * *

* The running time is $O(|E||V|)$. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class BellmanFordShortestPath extends BaseShortestPathAlgorithm { protected final Comparator comparator; protected final int maxHops; /** * Construct a new instance. * * @param graph the input graph */ public BellmanFordShortestPath(Graph graph) { this(graph, ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Construct a new instance. * * @param graph the input graph * @param epsilon tolerance when comparing floating point values */ public BellmanFordShortestPath(Graph graph, double epsilon) { this(graph, ToleranceDoubleComparator.DEFAULT_EPSILON, Integer.MAX_VALUE); } /** * Construct a new instance. * * @param graph the input graph * @param epsilon tolerance when comparing floating point values * @param maxHops execute the algorithm for at most this many iterations. If this is smaller * than the number of vertices, then the negative cycle detection feature is disabled. * @throws IllegalArgumentException if the number of maxHops is not positive */ public BellmanFordShortestPath(Graph graph, double epsilon, int maxHops) { super(graph); this.comparator = new ToleranceDoubleComparator(epsilon); if (maxHops < 1) { throw new IllegalArgumentException("Number of hops must be positive"); } this.maxHops = maxHops; } /** * {@inheritDoc} * * @throws NegativeCycleDetectedException in case a negative weight cycle is detected */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } return getPaths(source).getPath(sink); } /** * {@inheritDoc} * * @throws NegativeCycleDetectedException in case a negative weight cycle is detected */ @Override @SuppressWarnings("unchecked") public SingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } /* * Initialize distance and predecessor. */ int n = graph.vertexSet().size(); Map distance = new HashMap<>(); Map pred = new HashMap<>(); for (V v : graph.vertexSet()) { distance.put(v, Double.POSITIVE_INFINITY); } distance.put(source, 0d); /* * Maintain two sets of vertices whose edges need relaxation. The first set is the current * set of vertices while the second is the set for the subsequent iteration. */ Set[] updated = (Set[]) Array.newInstance(Set.class, 2); updated[0] = new LinkedHashSet<>(); updated[1] = new LinkedHashSet<>(); int curUpdated = 0; updated[curUpdated].add(source); /* * Relax edges. */ for (int i = 0; i < Math.min(n - 1, maxHops); i++) { Set curVertexSet = updated[curUpdated]; Set nextVertexSet = updated[(curUpdated + 1) % 2]; for (V v : curVertexSet) { for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); double newDist = distance.get(v) + graph.getEdgeWeight(e); if (comparator.compare(newDist, distance.get(u)) < 0) { distance.put(u, newDist); pred.put(u, e); nextVertexSet.add(u); } } } // swap next with current curVertexSet.clear(); curUpdated = (curUpdated + 1) % 2; // stop if no relaxation if (nextVertexSet.isEmpty()) { break; } } /* * Check for negative cycles. The user can disable this by providing a maxHops parameter * smaller than the number of vertices. */ if (maxHops >= n) { for (V v : updated[curUpdated]) { for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); double newDist = distance.get(v) + graph.getEdgeWeight(e); if (comparator.compare(newDist, distance.get(u)) < 0) { // record update for negative cycle computation pred.put(u, e); throw new NegativeCycleDetectedException( GRAPH_CONTAINS_A_NEGATIVE_WEIGHT_CYCLE, computeNegativeCycle(e, pred)); } } } } /* * Transform result */ Map> distanceAndPredecessorMap = new HashMap<>(); for (V v : graph.vertexSet()) { distanceAndPredecessorMap.put(v, Pair.of(distance.get(v), pred.get(v))); } return new TreeSingleSourcePathsImpl<>(graph, source, distanceAndPredecessorMap); } /** * Find a path between two vertices. * * @param graph the graph to be searched * @param source the vertex at which the path should start * @param sink the vertex at which the path should end * * @param the graph vertex type * @param the graph edge type * * @return a shortest path, or null if no path exists */ public static GraphPath findPathBetween(Graph graph, V source, V sink) { return new BellmanFordShortestPath<>(graph).getPath(source, sink); } /** * Computes a negative weight cycle assuming that the algorithm has already determined that it * exists. * * @param edge an edge which we know that belongs to the negative weight cycle * @param pred the predecessor array * * @return the negative weight cycle */ private GraphPath computeNegativeCycle(E edge, Map pred) { // find a vertex of the cycle Set visited = new HashSet<>(); V start = graph.getEdgeTarget(edge); visited.add(start); V cur = Graphs.getOppositeVertex(graph, edge, start); while (!visited.contains(cur)) { visited.add(cur); E e = pred.get(cur); cur = Graphs.getOppositeVertex(graph, e, cur); } // now build the actual cycle List cycle = new ArrayList<>(); double weight = 0d; start = cur; do { E e = pred.get(cur); cycle.add(e); weight += graph.getEdgeWeight(e); cur = Graphs.getOppositeVertex(graph, e, cur); } while (cur != start); Collections.reverse(cycle); return new GraphWalk<>(graph, start, start, cycle, weight); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy