blob: 94cb60963d8061a2a0ef0f1c1b3244c76dfb4a8d [file] [log] [blame]
/*
* Copyright 2014-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.graph;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Tarjan algorithm for searching a graph and producing results describing
* the graph SCC (strongly-connected components).
*/
public class TarjanGraphSearch<V extends Vertex, E extends Edge<V>>
implements GraphSearch<V, E> {
/**
* {@inheritDoc}
* <p>
* This implementation produces results augmented with information on
* SCCs within the graph.
* </p>
* <p>
* To prevent traversal of an edge, the {@link EdgeWeight#weight} should
* return a negative value as an edge weight.
* </p>
*/
@Override
public SccResult<V, E> search(Graph<V, E> graph, EdgeWeight<V, E> weight) {
SccResult<V, E> result = new SccResult<>(graph);
for (V vertex : graph.getVertexes()) {
VertexData data = result.data(vertex);
if (data == null) {
connect(graph, vertex, weight, result);
}
}
return result.build();
}
/**
* Scans the specified graph, using recursion, and produces SCC results.
*
* @param graph graph to search
* @param vertex current vertex to scan and connect
* @param weight optional edge weight
* @param result graph search result
* @return augmentation vertexData for the current vertex
*/
private VertexData<V> connect(Graph<V, E> graph, V vertex,
EdgeWeight<V, E> weight,
SccResult<V, E> result) {
VertexData<V> data = result.addData(vertex);
// Scan through all egress edges of the current vertex.
for (E edge : graph.getEdgesFrom(vertex)) {
V nextVertex = edge.dst();
// If edge weight is negative, skip it.
if (weight != null && weight.weight(edge) < 0) {
continue;
}
// Attempt to get the augmentation vertexData for the next vertex.
VertexData<V> nextData = result.data(nextVertex);
if (nextData == null) {
// Next vertex has not been visited yet, so do this now.
nextData = connect(graph, nextVertex, weight, result);
data.lowLink = Math.min(data.lowLink, nextData.lowLink);
} else if (result.visited(nextData)) {
// Next vertex has been visited, which means it is in the
// same cluster as the current vertex.
data.lowLink = Math.min(data.lowLink, nextData.index);
}
}
if (data.lowLink == data.index) {
result.addCluster(data);
}
return data;
}
/**
* Graph search result augmented with SCC vertexData.
*/
public static final class SccResult<V extends Vertex, E extends Edge<V>>
implements Result {
private final Graph<V, E> graph;
private List<Set<V>> clusterVertexes = new ArrayList<>();
private List<Set<E>> clusterEdges = new ArrayList<>();
private int index = 0;
private final Map<V, VertexData<V>> vertexData = new HashMap<>();
private final List<VertexData<V>> visited = new ArrayList<>();
private SccResult(Graph<V, E> graph) {
this.graph = graph;
}
/**
* Returns the number of SCC clusters in the graph.
*
* @return number of clusters
*/
public int clusterCount() {
return clusterEdges.size();
}
/**
* Returns the list of strongly connected vertex clusters.
*
* @return list of strongly connected vertex sets
*/
public List<Set<V>> clusterVertexes() {
return clusterVertexes;
}
/**
* Returns the list of edges linking strongly connected vertex clusters.
*
* @return list of strongly connected edge sets
*/
public List<Set<E>> clusterEdges() {
return clusterEdges;
}
// Gets the augmentation vertexData for the specified vertex
private VertexData<V> data(V vertex) {
return vertexData.get(vertex);
}
// Adds augmentation vertexData for the specified vertex
private VertexData<V> addData(V vertex) {
VertexData<V> d = new VertexData<>(vertex, index);
vertexData.put(vertex, d);
visited.add(0, d);
index++;
return d;
}
// Indicates whether the given vertex has been visited
private boolean visited(VertexData data) {
return visited.contains(data);
}
// Adds a new cluster for the specified vertex
private void addCluster(VertexData data) {
Set<V> vertexes = findClusterVertices(data);
clusterVertexes.add(vertexes);
clusterEdges.add(findClusterEdges(vertexes));
}
private Set<V> findClusterVertices(VertexData data) {
VertexData<V> nextVertexData;
Set<V> vertexes = new HashSet<>();
do {
nextVertexData = visited.remove(0);
vertexes.add(nextVertexData.vertex);
} while (data != nextVertexData);
return Collections.unmodifiableSet(vertexes);
}
private Set<E> findClusterEdges(Set<V> vertexes) {
Set<E> edges = new HashSet<>();
for (V vertex : vertexes) {
for (E edge : graph.getEdgesFrom(vertex)) {
if (vertexes.contains((edge.dst()))) {
edges.add(edge);
}
}
}
return Collections.unmodifiableSet(edges);
}
public SccResult<V, E> build() {
clusterVertexes = Collections.unmodifiableList(clusterVertexes);
clusterEdges = Collections.unmodifiableList(clusterEdges);
return this;
}
}
// Augments the vertex to assist in determining SCC clusters.
private static final class VertexData<V extends Vertex> {
final V vertex;
int index;
int lowLink;
private VertexData(V vertex, int index) {
this.vertex = vertex;
this.index = index;
this.lowLink = index;
}
}
}