org.jgrapht.alg.shortestpath.DeltaSteppingShortestPath Maven / Gradle / Ivy
/*
* (C) Copyright 2018-2023, by Semen Chudakov 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.util.*;
import org.jgrapht.alg.util.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;
/**
* Parallel implementation of a single-source shortest path algorithm: the delta-stepping algorithm.
* The algorithm computes single source shortest paths in a graphs with non-negative edge weights.
* When using multiple threads, this implementation typically outperforms
* {@link DijkstraShortestPath} and {@link BellmanFordShortestPath}.
*
* The delta-stepping algorithm is described in the paper: U. Meyer, P. Sanders, $\Delta$-stepping:
* a parallelizable shortest path algorithm, Journal of Algorithms, Volume 49, Issue 1, 2003, Pages
* 114-152, ISSN 0196-6774.
*
* The $\Delta$-stepping algorithm takes as input a weighted graph $G(V,E)$, a source node $s$ and a
* parameter $\Delta > 0$. Let $tent[v]$ be the best known shortest distance from $s$ to vertex
* $v\in V$. At the start of the algorithm, $tent[s]=0$, $tent[v]=\infty$ for $v\in V\setminus
* \{s\}$. The algorithm partitions vertices in a series of buckets $B=(B_0, B_1, B_2, \dots)$,
* where a vertex $v\in V$ is placed in bucket $B_{\lfloor\frac{tent[v]}{\Delta}\rfloor}$. During
* the execution of the algorithm, vertices in bucket $B_i$, for $i=0,1,2,\dots$, are removed
* one-by-one. For each removed vertex $v$, and for all its outgoing edges $(v,w)$, the algorithm
* checks whether $tent[v]+c(v,w) < tent[w]$. If so, $w$ is removed from its current bucket,
* $tent[w]$ is updated ($tent[w]=tent[v]+c(v,w)$), and $w$ is placed into bucket
* $B_{\lfloor\frac{tent[w]}{\Delta}\rfloor}$. Parallelism is achieved by processing all vertices
* belonging to the same bucket concurrently. The algorithm terminates when all buckets are empty.
* At this stage the array $tent$ contains the minimal cost from $s$ to every vertex $v \in V$. For
* a more detailed description of the algorithm, refer to the aforementioned paper.
*
*
* For a given graph $G(V,E)$ and parameter $\Delta$, let a $\Delta$-path be a path of total weight
* at most $\Delta$ with no repeated edges. The time complexity of the algorithm is $O(\frac{(|V| +
* |E| + n_{\Delta} + m_{\Delta})}{p} + \frac{L}{\Delta}\cdot d\cdot l_{\Delta}\cdot \log n)$, where
*
* - $n_{\Delta}$ - number of vertex pairs $(u,v)$, where $u$ and $v$ are connected by some
* $\Delta$-path.
* - $m_{\Delta}$ - number of vertex triples $(u,v^{\prime},v)$, where $u$ and $v^{\prime}$ are
* connected by some $\Delta$-path and edge $(v^{\prime},v)$ has weight at most $\Delta$.
* - $L$ - maximum weight of a shortest path from selected source to any sink.
* - $d$ - maximum vertex degree.
* - $l_{\Delta}$ - maximum number of edges in a $\Delta$-path $+1$.
*
*
*
* For parallelization, this implementation relies on the {@link ThreadPoolExecutor} which is
* supplied to this algorithm from outside.
*
* @param the graph vertex type
* @param the graph edge type
* @author Semen Chudakov
* @since January 2018
*/
public class DeltaSteppingShortestPath
extends BaseShortestPathAlgorithm
{
/**
* Error message for reporting the existence of an edge with negative weight.
*/
private static final String NEGATIVE_EDGE_WEIGHT_NOT_ALLOWED =
"Negative edge weight not allowed";
/**
* Error message for reporting that delta must be positive.
*/
private static final String DELTA_MUST_BE_NON_NEGATIVE = "Delta must be non-negative";
/**
* Default value for {@link #parallelism}.
*/
private static final int DEFAULT_PARALLELISM = Runtime.getRuntime().availableProcessors();
/**
* Empirically computed amount of tasks per worker thread in the {@link ForkJoinPool} that
* yields good performance.
*/
private static final int TASKS_TO_THREADS_RATIO = 20;
/**
* The bucket width. A bucket with index $i$ therefore stores a vertex v if and only if v is
* queued and tentative distance to v $\in[i\cdot\Delta,(i+1)\cdot\Delta]$
*/
private double delta;
/**
* Maximum number of threads used in the computations.
*/
private int parallelism;
/**
* Number of buckets in the bucket structure.
*/
private int numOfBuckets;
/**
* Maximum edge weight in the {@link #graph}.
*/
private double maxEdgeWeight;
/**
* Map to store predecessor for each vertex in the shortest path tree.
*/
private Map> distanceAndPredecessorMap;
/**
* Buckets structure.
*/
private List> bucketStructure;
/**
* Comparator for vertices in the graph which is used to create {@code ConcurrentSkipListSet}
* instances for the {@code bucketStructure}.
*/
private Comparator vertexComparator;
/**
* Supplier of the buckets for the {@code bucketStructure}.
*/
private Supplier> bucketsSupplier;
/**
* Decorator for {@link ThreadPoolExecutor} supplied to this algorithm that enables to keep
* track of when all submitted tasks are finished.
*/
private ExecutorCompletionService completionService;
/**
* Queue of vertices which edges should be relaxed on current iteration.
*/
private Queue verticesQueue;
/**
* Task for light edges relaxation.
*/
private Runnable lightRelaxTask;
/**
* Task for light edges relaxation.
*/
private Runnable heavyRelaxTask;
/**
* Indicates when all the vertices are been added to the {@link #verticesQueue} on each
* iteration.
*/
private volatile boolean allVerticesAdded;
/**
* Constructs a new instance of the algorithm for a given graph and {@code executor}. It is up
* to a user of this algorithm to handle the creation and termination of the provided
* {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see
* {@link ConcurrencyUtil}.
*
* @param graph graph
* @param executor executor which will be used for parallelization
*/
public DeltaSteppingShortestPath(Graph graph, ThreadPoolExecutor executor)
{
this(graph, 0.0, executor);
}
/**
* Constructs a new instance of the algorithm for a given {@code graph}, {@code executor} and
* {@code vertexComparator}. It is up to a user of this algorithm to handle the creation and
* termination of the provided {@code executor}. For utility methods to manage a
* {@code ThreadPoolExecutor} see {@link ConcurrencyUtil}. {@code vertexComparator} provided via
* this constructor is used to create instances of {@code ConcurrentSkipListSet} for the
* individual buckets. This gives a gives a small performance benefit for shortest paths
* computation.
*
* @param graph graph
* @param executor executor which will be used for parallelization
* @param vertexComparator comparator for vertices of the {@code graph}
*/
public DeltaSteppingShortestPath(
Graph graph, ThreadPoolExecutor executor, Comparator vertexComparator)
{
this(graph, 0.0, executor, vertexComparator);
}
/**
* Constructs a new instance of the algorithm for a given graph, delta.
*
* @param graph the graph
* @param delta bucket width
* @deprecated replaced with
* {@link #DeltaSteppingShortestPath(Graph, double, ThreadPoolExecutor)}
*/
@Deprecated
public DeltaSteppingShortestPath(Graph graph, double delta)
{
this(graph, delta, DEFAULT_PARALLELISM);
}
/**
* Constructs a new instance of the algorithm for a given graph, delta and {@code executor}. It
* is up to a user of this algorithm to handle the creation and termination of the provided
* {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see
* {@link ConcurrencyUtil}.
*
* @param graph the graph
* @param delta bucket width
* @param executor executor which will be used for parallelization
*/
public DeltaSteppingShortestPath(Graph graph, double delta, ThreadPoolExecutor executor)
{
super(graph);
Objects.requireNonNull(executor, "executor must not be null!");
init(graph, delta, executor, null);
}
/**
* Constructs a new instance of the algorithm for a given graph, delta, {@code executor} and
* {@code vertexComparator}. It is up to a user of this algorithm to handle the creation and
* termination of the provided {@code executor}. For utility methods to manage a
* {@code ThreadPoolExecutor} see {@link ConcurrencyUtil}. {@code vertexComparator} provided via
* this constructor is used to create instances of {@code ConcurrentSkipListSet} for the
* individual buckets. This gives a gives a small performance benefit for shortest paths
* computation.
*
* @param graph the graph
* @param delta bucket width
* @param executor executor which will be used for parallelization
* @param vertexComparator comparator for vertices of the {@code graph}
*/
public DeltaSteppingShortestPath(
Graph graph, double delta, ThreadPoolExecutor executor,
Comparator vertexComparator)
{
super(graph);
Objects.requireNonNull(executor, "executor must not be null!");
Objects.requireNonNull(executor, "vertexComparator must not be null!");
init(graph, delta, executor, vertexComparator);
}
/**
* Constructs a new instance of the algorithm for a given graph, parallelism.
*
* @param graph the graph
* @param parallelism maximum number of threads used in the computations
* @deprecated replaced with {@link #DeltaSteppingShortestPath(Graph, ThreadPoolExecutor)}
*/
@Deprecated
public DeltaSteppingShortestPath(Graph graph, int parallelism)
{
this(graph, 0.0, parallelism);
}
/**
* Constructs a new instance of the algorithm for a given graph, delta, parallelism. If delta is
* $0.0$ it will be computed during the algorithm execution. In general if the value of
* $\frac{maximum edge weight}{maximum outdegree}$ is known beforehand, it is preferable to
* specify it via this constructor, because processing the whole graph to compute this value may
* significantly slow down the algorithm.
*
* @param graph the graph
* @param delta bucket width
* @param parallelism maximum number of threads used in the computations
* @deprecated replaced with
* {@link #DeltaSteppingShortestPath(Graph, double, ThreadPoolExecutor)}
*/
@Deprecated
public DeltaSteppingShortestPath(Graph graph, double delta, int parallelism)
{
super(graph);
init(graph, delta, ConcurrencyUtil.createThreadPoolExecutor(parallelism), null);
}
/**
* Initializes {@code delta}, {@code parallelism}, {@code distanceAndPredecessorMap},
* {@code completionService}, {@code verticesQueue}, {@code lightRelaxTask} and
* {@code heavyRelaxTask} fields.
*
* @param graph a graph
* @param delta bucket width
* @param executor executor which will be used for parallelization
*/
private void init(
Graph graph, double delta, ThreadPoolExecutor executor,
Comparator vertexComparator)
{
if (delta < 0) {
throw new IllegalArgumentException(DELTA_MUST_BE_NON_NEGATIVE);
}
this.delta = delta;
this.parallelism = executor.getMaximumPoolSize();
this.vertexComparator = vertexComparator;
distanceAndPredecessorMap = new ConcurrentHashMap<>(graph.vertexSet().size());
completionService = new ExecutorCompletionService<>(executor);
verticesQueue = new ConcurrentLinkedQueue<>();
lightRelaxTask = new LightRelaxTask(verticesQueue);
heavyRelaxTask = new HeavyRelaxTask(verticesQueue);
}
/**
* Calculates max edge weight in the {@link #graph}.
*
* @return max edge weight
*/
private double getMaxEdgeWeight()
{
ForkJoinTask task = ForkJoinPool.commonPool().submit(
new MaxEdgeWeightTask(
graph.edgeSet().spliterator(),
graph.edgeSet().size() / (TASKS_TO_THREADS_RATIO * parallelism) + 1));
return task.join();
}
/**
* Is used during the algorithm to compute maximum edge weight of the {@link #graph}. Apart from
* computing the maximal edge weight in the graph the task also checks if there exist edges with
* negative weights.
*/
class MaxEdgeWeightTask
extends RecursiveTask
{
/**
* Is used to split a collection and create new recursive tasks during the computation.
*/
Spliterator spliterator;
/**
* Amount of edges which are processed in parallel.
*/
long loadBalancing;
/**
* Constructs a new instance for the given spliterator and loadBalancing
*
* @param spliterator spliterator
* @param loadBalancing loadBalancing
*/
MaxEdgeWeightTask(Spliterator spliterator, long loadBalancing)
{
this.spliterator = spliterator;
this.loadBalancing = loadBalancing;
}
/**
* Computes maximum edge weight. If amount of edges in {@link #spliterator} is less than
* {@link #loadBalancing}, then computation is performed sequentially. If not, the
* {@link #spliterator} is used to split the collection and then two new child tasks are
* created.
*
* @return max edge weight
*/
@Override
protected Double compute()
{
if (spliterator.estimateSize() <= loadBalancing) {
double[] max = { 0 };
spliterator.forEachRemaining(e -> {
double weight = graph.getEdgeWeight(e);
if (weight < 0) {
throw new IllegalArgumentException(NEGATIVE_EDGE_WEIGHT_NOT_ALLOWED);
}
max[0] = Math.max(weight, max[0]);
});
return max[0];
} else {
MaxEdgeWeightTask t1 = new MaxEdgeWeightTask(spliterator.trySplit(), loadBalancing);
t1.fork();
MaxEdgeWeightTask t2 = new MaxEdgeWeightTask(spliterator, loadBalancing);
return Math.max(t2.compute(), t1.join());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public GraphPath getPath(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);
}
return getPaths(source).getPath(sink);
}
/**
* {@inheritDoc}
*/
@Override
public SingleSourcePaths getPaths(V source)
{
if (!graph.containsVertex(source)) {
throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX);
}
maxEdgeWeight = getMaxEdgeWeight();
if (delta == 0.0) { // the value should be computed
delta = findDelta();
}
numOfBuckets = (int) (Math.ceil(maxEdgeWeight / delta) + 1);
bucketStructure = new ArrayList<>(numOfBuckets);
bucketsSupplier = getBucketsSupplier(source);
for (int i = 0; i < numOfBuckets; i++) {
bucketStructure.add(bucketsSupplier.get());
}
fillDistanceAndPredecessorMap();
computeShortestPaths(source);
return new TreeSingleSourcePathsImpl<>(graph, source, distanceAndPredecessorMap);
}
/**
* Creates a supplier of sets for the {@code bucketStructure}.
*
* @param vertex a vertex in the graph
* @return supplier of buckets
*/
private Supplier> getBucketsSupplier(V vertex)
{
if (vertexComparator != null) {
return () -> new ConcurrentSkipListSet<>(vertexComparator);
} else if (vertex instanceof Comparable) {
return () -> new ConcurrentSkipListSet<>();
} else {
return () -> Collections.newSetFromMap(new ConcurrentHashMap<>());
}
}
/**
* Calculates value of {@link #delta}. The value is calculated as the maximal edge weight
* divided by maximal out-degree in the {@link #graph} or $1.0$ if edge set of the
* {@link #graph} is empty.
*
* @return bucket width
*/
private double findDelta()
{
if (maxEdgeWeight == 0) {
return 1.0;
} else {
int maxOutDegree =
graph.vertexSet().parallelStream().mapToInt(graph::outDegreeOf).max().orElse(0);
return maxEdgeWeight / maxOutDegree;
}
}
/**
* Fills {@link #distanceAndPredecessorMap} concurrently.
*/
private void fillDistanceAndPredecessorMap()
{
graph.vertexSet().parallelStream().forEach(
v -> distanceAndPredecessorMap.put(v, Pair.of(Double.POSITIVE_INFINITY, null)));
}
/**
* Performs shortest path computations.
*
* @param source the source vertex
*/
private void computeShortestPaths(V source)
{
relax(source, null, 0.0);
List> removed = new ArrayList<>();
while (true) {
int firstNonEmptyBucket = 0;
while (firstNonEmptyBucket < numOfBuckets
&& bucketStructure.get(firstNonEmptyBucket).isEmpty())
{ // skip empty buckets
++firstNonEmptyBucket;
}
if (firstNonEmptyBucket == numOfBuckets) { // terminate if all buckets are empty
break;
}
// the content of a bucket is replaced
// in order not to handle the same vertices
// multiple times
Set bucketElements = getContentAndReplace(firstNonEmptyBucket);
while (!bucketElements.isEmpty()) { // reinsertions may occur
removed.add(bucketElements);
findAndRelaxLightRequests(bucketElements);
bucketElements = getContentAndReplace(firstNonEmptyBucket);
}
findAndRelaxHeavyRequests(removed);
removed.clear();
}
}
/**
* Manages edge relaxations. Adds all elements from {@code vertices} to the
* {@link #verticesQueue} and submits as many {@link #lightRelaxTask} to the
* {@link #completionService} as needed.
*
* @param vertices vertices
*/
private void findAndRelaxLightRequests(Set vertices)
{
allVerticesAdded = false;
int numOfVertices = vertices.size();
int numOfTasks;
if (numOfVertices >= parallelism) {
// use as available tasks
numOfTasks = parallelism;
Iterator iterator = vertices.iterator();
// provide some work to the workers
addSetVertices(iterator, parallelism);
submitTasks(lightRelaxTask, parallelism - 1); // one thread should
// submit rest of vertices
addSetRemaining(iterator);
submitTasks(lightRelaxTask, 1); // use remaining thread for relaxation
} else {
// only several relaxation tasks are needed
numOfTasks = numOfVertices;
addSetRemaining(vertices.iterator());
submitTasks(lightRelaxTask, numOfVertices);
}
allVerticesAdded = true;
waitForTasksCompletion(numOfTasks);
}
/**
* Manages execution of edges relaxation. Adds all elements from {@code vertices} to the
* {@link #verticesQueue} and submits as many {@link #heavyRelaxTask} to the
* {@link #completionService} as needed.
*
* @param verticesSets set of sets of vertices
*/
private void findAndRelaxHeavyRequests(List> verticesSets)
{
allVerticesAdded = false;
int numOfVertices = verticesSets.stream().mapToInt(Set::size).sum();
int numOfTasks;
if (numOfVertices >= parallelism) {
// use as available tasks
numOfTasks = parallelism;
Iterator> setIterator = verticesSets.iterator();
// provide some work to the workers
Iterator iterator = addSetsVertices(setIterator, parallelism);
submitTasks(heavyRelaxTask, parallelism - 1);// one thread should
// submit rest of vertices
addSetRemaining(iterator);
addSetsRemaining(setIterator);
submitTasks(heavyRelaxTask, 1); // use remaining thread for relaxation
} else {
// only several relaxation tasks are needed
numOfTasks = numOfVertices;
addSetsRemaining(verticesSets.iterator());
submitTasks(heavyRelaxTask, numOfVertices);
}
allVerticesAdded = true;
waitForTasksCompletion(numOfTasks);
}
/**
* Adds {@code numOfVertices} vertices to the {@link #verticesQueue} provided by the
* {@code iterator}.
*
* @param iterator vertices iterator
* @param numOfVertices vertices amount
*/
private void addSetVertices(Iterator iterator, int numOfVertices)
{
for (int i = 0; i < numOfVertices && iterator.hasNext(); i++) {
verticesQueue.add(iterator.next());
}
}
/**
* Adds all remaining vertices to the {@link #verticesQueue} provided by the {@code iterator}.
*
* @param iterator vertices iterator
*/
private void addSetRemaining(Iterator iterator)
{
while (iterator.hasNext()) {
verticesQueue.add(iterator.next());
}
}
/**
* Adds {@code numOfVertices} vertices to the {@link #verticesQueue} that are contained in the
* sets provided by the {@code setIterator}. Returns iterator of the set which vertex was added
* last.
*
* @param setIterator sets of vertices iterator
* @param numOfVertices vertices amount
* @return iterator of the last set
*/
private Iterator addSetsVertices(Iterator> setIterator, int numOfVertices)
{
int i = 0;
Iterator iterator = null;
while (setIterator.hasNext() && i < numOfVertices) {
iterator = setIterator.next().iterator();
while (iterator.hasNext() && i < numOfVertices) {
verticesQueue.add(iterator.next());
i++;
}
}
return iterator;
}
/**
* Adds all remaining vertices to the {@link #verticesQueue} that are contained in the sets
* provided by the {@code setIterator}.
*
* @param setIterator sets of vertices iterator
*/
private void addSetsRemaining(Iterator> setIterator)
{
while (setIterator.hasNext()) {
verticesQueue.addAll(setIterator.next());
}
}
/**
* Submits the {@code task} {@code numOfTasks} times to the {@link #completionService}.
*
* @param task task to be submitted
* @param numOfTasks amount of times task should be submitted
*/
private void submitTasks(Runnable task, int numOfTasks)
{
for (int i = 0; i < numOfTasks; i++) {
completionService.submit(task, null);
}
}
/**
* Takes {@code numOfTasks} tasks from the {@link #completionService}.
*
* @param numOfTasks amount of tasks
*/
private void waitForTasksCompletion(int numOfTasks)
{
for (int i = 0; i < numOfTasks; i++) {
try {
completionService.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Performs relaxation in parallel-safe fashion. Synchronises by {@code vertex}, then if new
* tentative distance is less then removes {@code v} from the old bucket, adds is to the new
* bucket and updates {@link #distanceAndPredecessorMap} value for {@code v}.
*
* @param v vertex
* @param e edge to predecessor
* @param distance distance
*/
private void relax(V v, E e, double distance)
{
int updatedBucket = bucketIndex(distance);
synchronized (v) { // to make relaxation updates thread-safe
Pair oldData = distanceAndPredecessorMap.get(v);
if (distance < oldData.getFirst()) {
if (!oldData.getFirst().equals(Double.POSITIVE_INFINITY)) {
bucketStructure.get(bucketIndex(oldData.getFirst())).remove(v);
}
bucketStructure.get(updatedBucket).add(v);
distanceAndPredecessorMap.put(v, Pair.of(distance, e));
}
}
}
/**
* Calculates bucket index for a given {@code distance}.
*
* @param distance distance
* @return bucket index
*/
private int bucketIndex(double distance)
{
return (int) Math.round(distance / delta) % numOfBuckets;
}
/**
* Replaces the bucket at the {@code bucketIndex} with a new instance of the concurrent set.
* Return the reference to the set that was previously in the bucket.
*
* @param bucketIndex bucket index
* @return content of the bucket
*/
private Set getContentAndReplace(int bucketIndex)
{
Set result = bucketStructure.get(bucketIndex);
bucketStructure.set(bucketIndex, bucketsSupplier.get());
return result;
}
/**
* Task that is submitted to the {@link #completionService} during shortest path computation for
* light relax requests relaxation.
*/
class LightRelaxTask
implements Runnable
{
/**
* Vertices which edges will be relaxed.
*/
private Queue vertices;
/**
* Constructs instance of a new task.
*
* @param vertices vertices
*/
LightRelaxTask(Queue vertices)
{
this.vertices = vertices;
}
/**
* Performs relaxation of edges emanating from {@link #vertices}.
*/
@Override
public void run()
{
while (true) {
V v = vertices.poll();
if (v == null) { // we might have a termination situation
if (allVerticesAdded && vertices.isEmpty()) { // need to check
// is the queue is empty, because some vertices might have been added
// while passing from first if condition to the second
break;
}
} else {
for (E e : graph.outgoingEdgesOf(v)) {
if (graph.getEdgeWeight(e) <= delta) {
relax(
Graphs.getOppositeVertex(graph, e, v), e,
distanceAndPredecessorMap.get(v).getFirst()
+ graph.getEdgeWeight(e));
}
}
}
}
}
}
/**
* Task that is submitted to the {@link #completionService} during shortest path computation for
* heavy relax requests relaxation.
*/
class HeavyRelaxTask
implements Runnable
{
/**
* Vertices which edges will be relaxed.
*/
private Queue vertices;
/**
* Constructs instance of a new task.
*
* @param vertices vertices
*/
HeavyRelaxTask(Queue vertices)
{
this.vertices = vertices;
}
/**
* Performs relaxation of edges emanating from {@link #vertices}.
*/
@Override
public void run()
{
while (true) {
V v = vertices.poll();
if (v == null) {
if (allVerticesAdded && vertices.isEmpty()) {
break;
}
} else {
for (E e : graph.outgoingEdgesOf(v)) {
if (graph.getEdgeWeight(e) > delta) {
relax(
Graphs.getOppositeVertex(graph, e, v), e,
distanceAndPredecessorMap.get(v).getFirst()
+ graph.getEdgeWeight(e));
}
}
}
}
}
}
}