org.jgrapht.traverse.ClosestFirstIterator Maven / Gradle / Ivy
/*
* (C) Copyright 2003-2021, by John V Sichi 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.traverse;
import org.jgrapht.*;
import org.jheaps.*;
import org.jheaps.tree.*;
import java.util.*;
import java.util.function.*;
/**
* A closest-first iterator for a directed or undirected graph. For this iterator to work correctly
* the graph must not be modified during iteration. Currently there are no means to ensure that, nor
* to fail-fast. The results of such modifications are undefined.
*
*
* The metric for closest here is the weighted path length from a start vertex, i.e.
* Graph.getEdgeWeight(Edge) is summed to calculate path length. Negative edge weights will result
* in an IllegalArgumentException. Optionally, path length may be bounded by a finite radius. A
* custom heap implementation can be specified during the construction time. Pairing heap is used by
* default.
*
*
* @param the graph vertex type
* @param the graph edge type
* @author John V. Sichi
*/
public class ClosestFirstIterator
extends
CrossComponentIterator>>
{
/**
* Priority queue of fringe vertices.
*/
private AddressableHeap> heap;
/**
* Maximum distance to search.
*/
private double radius = Double.POSITIVE_INFINITY;
private boolean initialized = false;
/**
* Creates a new closest-first iterator for the specified graph. Iteration will start at the
* specified start vertex and will be limited to the connected component that includes that
* vertex. If the specified start vertex is null
, iteration will start at an
* arbitrary vertex and will not be limited, that is, will be able to traverse all the graph.
*
* @param g the graph to be iterated.
* @param startVertex the vertex iteration to be started.
*/
public ClosestFirstIterator(Graph g, V startVertex)
{
this(g, startVertex, Double.POSITIVE_INFINITY);
}
/**
* Creates a new closest-first iterator for the specified graph. Iteration will start at the
* specified start vertices and will be limited to the subset of the graph reachable from those
* vertices. Iteration order is based on minimum distance from any of the start vertices,
* regardless of the order in which the start vertices are supplied. Because of this, the entire
* traversal is treated as if it were over a single connected component with respect to events
* fired.
*
* @param g the graph to be iterated.
* @param startVertices the vertices iteration to be started.
*/
public ClosestFirstIterator(Graph g, Iterable startVertices)
{
this(g, startVertices, Double.POSITIVE_INFINITY);
}
/**
* Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will
* start at the specified start vertex and will be limited to the subset of the connected
* component which includes that vertex and is reachable via paths of weighted length less than
* or equal to the specified radius. The specified start vertex may not be
* null
.
*
* @param g the graph to be iterated.
* @param startVertex the vertex iteration to be started.
* @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded
* search.
*/
public ClosestFirstIterator(Graph g, V startVertex, double radius)
{
this(
g, startVertex == null ? null : Collections.singletonList(startVertex), radius,
PairingHeap::new);
}
/**
* Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will
* start at the specified start vertex and will be limited to the subset of the connected
* component which includes that vertex and is reachable via paths of weighted length less than
* or equal to the specified radius. The specified start vertex may not be
* null
. This algorithm will use the heap supplied by the {@code heapSupplier}.
*
* @param g the graph to be iterated.
* @param startVertex the vertex iteration to be started.
* @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded
* search.
* @param heapSupplier supplier of the preferable heap implementation
*/
public ClosestFirstIterator(
Graph g, V startVertex, double radius,
Supplier>> heapSupplier)
{
this(
g, startVertex == null ? null : Collections.singletonList(startVertex), radius,
heapSupplier);
}
/**
* Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will
* start at the specified start vertices and will be limited to the subset of the graph
* reachable from those vertices via paths of weighted length less than or equal to the
* specified radius. The specified collection of start vertices may not be null
.
* Iteration order is based on minimum distance from any of the start vertices, regardless of
* the order in which the start vertices are supplied. Because of this, the entire traversal is
* treated as if it were over a single connected component with respect to events fired.
*
* @param g the graph to be iterated.
* @param startVertices the vertices iteration to be started.
* @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded
* search.
*/
public ClosestFirstIterator(Graph g, Iterable startVertices, double radius)
{
this(g, startVertices, radius, PairingHeap::new);
}
/**
* Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will
* start at the specified start vertices and will be limited to the subset of the graph
* reachable from those vertices via paths of weighted length less than or equal to the
* specified radius. The specified collection of start vertices may not be null
.
* Iteration order is based on minimum distance from any of the start vertices, regardless of
* the order in which the start vertices are supplied. Because of this, the entire traversal is
* treated as if it were over a single connected component with respect to events fired. This
* algorithm will use the heap supplied by the {@code heapSupplier}.
*
* @param g the graph to be iterated.
* @param startVertices the vertices iteration to be started.
* @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded
* search.
* @param heapSupplier supplier of the preferable heap implementation
*/
public ClosestFirstIterator(
Graph g, Iterable startVertices, double radius,
Supplier>> heapSupplier)
{
super(g, startVertices);
this.radius = radius;
Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null");
this.heap = heapSupplier.get();
checkRadiusTraversal(isCrossComponentTraversal());
initialized = true;
if (!crossComponentTraversal) {
// prime the heap by processing the first start vertex
hasNext();
Iterator iter = startVertices.iterator();
if (iter.hasNext()) {
// discard the first since we already primed it above
iter.next();
// prime the heap with the rest of the start vertices so that
// we can process them all simultaneously
while (iter.hasNext()) {
V v = iter.next();
encounterVertex(v, null);
}
}
}
}
// override AbstractGraphIterator
@Override
public void setCrossComponentTraversal(boolean crossComponentTraversal)
{
if (initialized) {
checkRadiusTraversal(crossComponentTraversal);
}
super.setCrossComponentTraversal(crossComponentTraversal);
}
/**
* Get the weighted length of the shortest path known to the given vertex. If the vertex has
* already been visited, then it is truly the shortest path length; otherwise, it is the best
* known upper bound.
*
* @param vertex vertex being sought from start vertex
* @return weighted length of shortest path known, or Double.POSITIVE_INFINITY if no path found
* yet
*/
public double getShortestPathLength(V vertex)
{
AddressableHeap.Handle> node = getSeenData(vertex);
if (node == null) {
return Double.POSITIVE_INFINITY;
}
return node.getKey();
}
/**
* Get the spanning tree edge reaching a vertex which has been seen already in this traversal.
* This edge is the last link in the shortest known path between the start vertex and the
* requested vertex. If the vertex has already been visited, then it is truly the minimum
* spanning tree edge; otherwise, it is the best candidate seen so far.
*
* @param vertex the spanned vertex.
* @return the spanning tree edge, or null if the vertex either has not been seen yet or is a
* start vertex.
*/
public E getSpanningTreeEdge(V vertex)
{
AddressableHeap.Handle> node = getSeenData(vertex);
if (node == null) {
return null;
}
return node.getValue().spanningTreeEdge;
}
/**
* @see CrossComponentIterator#isConnectedComponentExhausted()
*/
@Override
protected boolean isConnectedComponentExhausted()
{
if (heap.size() == 0) {
return true;
} else {
if (heap.findMin().getKey() > radius) {
heap.clear();
return true;
} else {
return false;
}
}
}
/**
* @see CrossComponentIterator#encounterVertex(Object, Object)
*/
@Override
protected void encounterVertex(V vertex, E edge)
{
double shortestPathLength;
if (edge == null) {
shortestPathLength = 0;
} else {
shortestPathLength = calculatePathLength(vertex, edge);
}
AddressableHeap.Handle> handle =
heap.insert(shortestPathLength, new QueueEntry<>(vertex, edge));
putSeenData(vertex, handle);
}
/**
* Override superclass. When we see a vertex again, we need to see if the new edge provides a
* shorter path than the old edge.
*
* @param vertex the vertex re-encountered
* @param edge the edge via which the vertex was re-encountered
*/
@Override
protected void encounterVertexAgain(V vertex, E edge)
{
AddressableHeap.Handle> node = getSeenData(vertex);
if (node.getValue().frozen) {
// no improvement for this vertex possible
return;
}
double candidatePathLength = calculatePathLength(vertex, edge);
if (candidatePathLength < node.getKey()) {
node.getValue().spanningTreeEdge = edge;
node.decreaseKey(candidatePathLength);
}
}
/**
* @see CrossComponentIterator#provideNextVertex()
*/
@Override
protected V provideNextVertex()
{
AddressableHeap.Handle> node = heap.deleteMin();
node.getValue().frozen = true;
return node.getValue().vertex;
}
private void assertNonNegativeEdge(E edge)
{
if (getGraph().getEdgeWeight(edge) < 0) {
throw new IllegalArgumentException("negative edge weights not allowed");
}
}
/**
* Determine weighted path length to a vertex via an edge, using the path length for the
* opposite vertex.
*
* @param vertex the vertex for which to calculate the path length.
* @param edge the edge via which the path is being extended.
* @return calculated path length.
*/
private double calculatePathLength(V vertex, E edge)
{
assertNonNegativeEdge(edge);
V otherVertex = Graphs.getOppositeVertex(getGraph(), edge, vertex);
AddressableHeap.Handle> otherEntry = getSeenData(otherVertex);
return otherEntry.getKey() + getGraph().getEdgeWeight(edge);
}
private void checkRadiusTraversal(boolean crossComponentTraversal)
{
if (crossComponentTraversal && (radius != Double.POSITIVE_INFINITY)) {
throw new IllegalArgumentException(
"radius may not be specified for cross-component traversal");
}
}
/**
* Private data to associate with each entry in the priority queue.
*/
static class QueueEntry
{
/**
* The vertex reached.
*/
V vertex;
/**
* Best spanning tree edge to vertex seen so far.
*/
E spanningTreeEdge;
/**
* True once spanningTreeEdge is guaranteed to be the true minimum.
*/
boolean frozen;
QueueEntry(V vertex, E spanningTreeEdge)
{
this.vertex = vertex;
this.spanningTreeEdge = spanningTreeEdge;
}
}
}