org.jgrapht.traverse.LexBreadthFirstIterator Maven / Gradle / Ivy
/*
* (C) Copyright 2018-2021, by Timofey 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.traverse;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.jgrapht.Graph;
import org.jgrapht.GraphTests;
import org.jgrapht.Graphs;
import org.jgrapht.util.CollectionUtil;
/**
* A lexicographical breadth-first iterator for an undirected graph.
*
* Every vertex has an implicit label (they aren't used explicitly in order to reduce time and
* memory complexity). When some vertex is returned by this iterator, its index is the number of
* vertices in this graph minus number of already returned vertices. For a given vertex v its label
* is a concatenation of indices of already returned vertices, that were also its neighbours, with
* some separator between them. For example, 7#4#3 is a valid vertex label.
*
* Iterator chooses vertex with lexicographically largest label and returns it. It breaks ties
* arbitrarily. For more information on lexicographical BFS see the following article: Corneil D.G.
* (2004)
* Lexicographic Breadth First Search – A Survey. In: Hromkovič J., Nagl M., Westfechtel
* B. (eds) Graph-Theoretic Concepts in Computer Science. WG 2004. Lecture Notes in Computer
* Science, vol 3353. Springer, Berlin, Heidelberg; and the following
* paper:CS 762:
* Graph-theoretic algorithms. Lecture notes of a graduate course. University of Waterloo.
*
* 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.
*
* Note: only vertex events are fired by this iterator.
*
* @param the graph vertex type.
* @param the graph edge type.
* @author Timofey Chudakov
*/
public class LexBreadthFirstIterator
extends
AbstractGraphIterator
{
/**
* Reference to the {@code BucketList} that contains unvisited vertices.
*/
private BucketList bucketList;
/**
* Contains current vertex of the {@code graph}.
*/
private V current;
/**
* Creates new lexicographical breadth-first iterator for {@code graph}.
*
* @param graph the graph to be iterated.
*/
public LexBreadthFirstIterator(Graph graph)
{
super(graph);
GraphTests.requireUndirected(graph);
bucketList = new BucketList(graph.vertexSet());
}
/**
* Checks whether there exist unvisited vertices.
*
* @return true if there exist unvisited vertices.
*/
@Override
public boolean hasNext()
{
if (current != null) {
return true;
}
current = advance();
if (current != null && nListeners != 0) {
fireVertexTraversed(createVertexTraversalEvent(current));
}
return current != null;
}
/**
* Returns the next vertex in the ordering.
*
* @return the next vertex in the ordering.
*/
@Override
public V next()
{
if (!hasNext()) {
throw new NoSuchElementException();
}
V result = current;
current = null;
if (nListeners != 0) {
fireVertexFinished(createVertexTraversalEvent(result));
}
return result;
}
/**
* {@inheritDoc}
*
* Always returns true since this iterator doesn't care about connected components.
*/
@Override
public boolean isCrossComponentTraversal()
{
return true;
}
/**
* {@inheritDoc}
*
* Trying to disable the cross components nature of this iterator will result into throwing a
* {@link IllegalArgumentException}.
*/
@Override
public void setCrossComponentTraversal(boolean crossComponentTraversal)
{
if (!crossComponentTraversal) {
throw new IllegalArgumentException("Iterator is always cross-component");
}
}
/**
* Retrieves vertex from the {@code bucketList} and returns it.
*
* @return the vertex retrieved from the {@code bucketList}.
*/
private V advance()
{
V vertex = bucketList.poll();
if (vertex != null) {
bucketList.updateBuckets(getUnvisitedNeighbours(vertex));
}
return vertex;
}
/**
* Computes and returns neighbours of {@code vertex} which haven't been visited by this
* iterator.
*
* @param vertex the vertex, whose neighbours are being explored.
* @return neighbours of {@code vertex} which have yet to be visited by this iterator.
*/
private Set getUnvisitedNeighbours(V vertex)
{
Set unmapped = new LinkedHashSet<>();
Set edges = graph.edgesOf(vertex);
for (E edge : edges) {
V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex);
if (bucketList.containsBucketWith(oppositeVertex)) {
unmapped.add(oppositeVertex);
}
}
return unmapped;
}
/**
* Data structure for performing lexicographical breadth-first search. Allows to add and
* retrieve vertices from buckets, update their buckets after a new vertex has been added to the
* LexBFS order. Labels aren't used explicitly, which results in time and space optimization.
*
* @author Timofey Chudakov
*/
class BucketList
{
/**
* Bucket with the vertices that have lexicographically largest label.
*/
private Bucket head;
/**
* Map for mapping vertices to buckets they are currently in. Is used for finding the bucket
* of the vertex in constant time.
*/
private Map bucketMap;
/**
* Creates a {@code BucketList} with a single bucket and all specified {@code vertices} in
* it.
*
* @param vertices the vertices of the graph, that should be stored in the {@code head}
* bucket.
*/
BucketList(Collection vertices)
{
head = new Bucket(vertices);
bucketMap = CollectionUtil.newHashMapWithExpectedSize(vertices.size());
for (V vertex : vertices) {
bucketMap.put(vertex, head);
}
}
/**
* Checks whether there exists a bucket with the specified {@code vertex}.
*
* @param vertex the vertex whose presence in some {@code Bucket} in this {@code BucketList}
* is checked.
* @return true if there exists a bucket with {@code vertex} in it, otherwise
* false.
*/
boolean containsBucketWith(V vertex)
{
return bucketMap.containsKey(vertex);
}
/**
* Retrieves element from the head bucket by invoking {@link Bucket#poll()} or null if this
* {@code BucketList} is empty.
*
* Removes the head bucket if it becomes empty after the operation.
*
* @return vertex returned by {@link Bucket#poll()} invoked on head bucket or null if this
* {@code BucketList} is empty.
*/
V poll()
{
if (bucketMap.size() > 0) {
V res = head.poll();
bucketMap.remove(res);
if (head.isEmpty()) {
head = head.next;
if (head != null) {
head.prev = null;
}
}
return res;
} else {
return null;
}
}
/**
* For every bucket B in this {@code BucketList}, which contains vertices from the set
* {@code
* vertices}, creates a new {@code Bucket} B' and moves vertices from B to B' according to
* the following rule: $B' = B\cap vertices$ and $B = B\backslash B'$. For every such
* {@code Bucket} B only one {@code Bucket} B' is created. If some bucket B becomes empty
* after this operation, it is removed from the data structure.
*
* @param vertices the vertices, that should be moved to new buckets.
*/
void updateBuckets(Set vertices)
{
Set visitedBuckets = new HashSet<>();
for (V vertex : vertices) {
Bucket bucket = bucketMap.get(vertex);
if (visitedBuckets.contains(bucket)) {
bucket.prev.addVertex(vertex);
bucketMap.put(vertex, bucket.prev);
} else {
visitedBuckets.add(bucket);
Bucket newBucket = new Bucket(vertex);
newBucket.insertBefore(bucket);
bucketMap.put(vertex, newBucket);
if (head == bucket) {
head = newBucket;
}
}
bucket.removeVertex(vertex);
if (bucket.isEmpty()) {
visitedBuckets.remove(bucket);
bucket.removeSelf();
}
}
}
/**
* Plays the role of the container of vertices. All vertices stored in a bucket have
* identical label. Labels aren't used explicitly.
*
* Encapsulates operations of addition and removal of vertices from the bucket and removal
* of a bucket from the data structure.
*/
private class Bucket
{
/**
* Reference of the bucket with lexicographically smaller label.
*/
private Bucket next;
/**
* Reference of the bucket with lexicographically larger label.
*/
private Bucket prev;
/**
* Set of vertices currently stored in this bucket.
*/
private Set vertices;
/**
* Creates a new bucket with all {@code vertices} stored in it.
*
* @param vertices vertices to store in this bucket.
*/
Bucket(Collection vertices)
{
this.vertices = new LinkedHashSet<>(vertices);
}
/**
* Creates a new Bucket with a single {@code vertex} in it.
*
* @param vertex the vertex to store in this bucket.
*/
Bucket(V vertex)
{
this.vertices = new LinkedHashSet<>();
vertices.add(vertex);
}
/**
* Removes the {@code vertex} from this bucket.
*
* @param vertex the vertex to remove.
*/
void removeVertex(V vertex)
{
vertices.remove(vertex);
}
/**
* Removes this bucket from the data structure.
*/
void removeSelf()
{
if (next != null) {
next.prev = prev;
}
if (prev != null) {
prev.next = next;
}
}
/**
* Inserts this bucket in the data structure before the {@code bucket}.
*
* @param bucket the bucket, that will be the next to this bucket.
*/
void insertBefore(Bucket bucket)
{
this.next = bucket;
if (bucket != null) {
this.prev = bucket.prev;
if (bucket.prev != null) {
bucket.prev.next = this;
}
bucket.prev = this;
} else {
this.prev = null;
}
}
/**
* Adds the {@code vertex} to this bucket.
*
* @param vertex the vertex to add.
*/
void addVertex(V vertex)
{
vertices.add(vertex);
}
/**
* Retrieves one vertex from this bucket.
*
* @return vertex, that was removed from this bucket, null if the bucket was empty.
*/
V poll()
{
if (vertices.isEmpty()) {
return null;
} else {
V vertex = vertices.iterator().next();
vertices.remove(vertex);
return vertex;
}
}
/**
* Checks whether this bucket is empty.
*
* @return true if this bucket doesn't contain any elements, otherwise false.
*/
boolean isEmpty()
{
return vertices.size() == 0;
}
}
}
}