blob: ebd451fdce5ded0d5d75caf18e7ce63d4212c2a0 [file] [log] [blame]
tom0633d682014-09-10 12:10:03 -07001package org.onlab.graph;
2
3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.HashMap;
6import java.util.HashSet;
7import java.util.List;
8import java.util.Map;
9import java.util.Set;
10
11/**
12 * Tarjan algorithm for searching a graph and producing results describing
13 * the graph SCC (strongly-connected components).
14 */
15public class TarjanGraphSearch<V extends Vertex, E extends Edge<V>>
16 implements GraphSearch<V, E> {
17
18 /**
19 * {@inheritDoc}
20 * <p/>
21 * This implementation produces results augmented with information on
22 * SCCs within the graph.
23 * <p/>
24 * To prevent traversal of an edge, the {@link EdgeWeight#weight} should
25 * return a negative value as an edge weight.
26 */
27 @Override
28 public SCCResult<V, E> search(Graph<V, E> graph, EdgeWeight<V, E> weight) {
29 SCCResult<V, E> result = new SCCResult<>(graph);
30 for (V vertex : graph.getVertexes()) {
31 VertexData data = result.data(vertex);
32 if (data == null) {
33 connect(graph, vertex, weight, result);
34 }
35 }
36 return result.build();
37 }
38
39 /**
40 * Scans the specified graph, using recursion, and produces SCC results.
41 *
42 * @param graph graph to search
43 * @param vertex current vertex to scan and connect
44 * @param weight optional edge weight
45 * @param result graph search result
46 * @return augmentation vertexData for the current vertex
47 */
48 private VertexData<V> connect(Graph<V, E> graph, V vertex,
49 EdgeWeight<V, E> weight,
50 SCCResult<V, E> result) {
51 VertexData<V> data = result.addData(vertex);
52
53 // Scan through all egress edges of the current vertex.
54 for (E edge : graph.getEdgesFrom(vertex)) {
55 V nextVertex = edge.dst();
56
57 // If edge weight is negative, skip it.
58 if (weight != null && weight.weight(edge) < 0) {
59 continue;
60 }
61
62 // Attempt to get the augmentation vertexData for the next vertex.
63 VertexData<V> nextData = result.data(nextVertex);
64 if (nextData == null) {
65 // Next vertex has not been visited yet, so do this now.
66 nextData = connect(graph, nextVertex, weight, result);
67 data.lowLink = Math.min(data.lowLink, nextData.lowLink);
68
69 } else if (result.visited(nextData)) {
70 // Next vertex has been visited, which means it is in the
71 // same cluster as the current vertex.
72 data.lowLink = Math.min(data.lowLink, nextData.index);
73 }
74 }
75
76 if (data.lowLink == data.index) {
77 result.addCluster(data);
78 }
79 return data;
80 }
81
82 /**
83 * Graph search result augmented with SCC vertexData.
84 */
85 public static final class SCCResult<V extends Vertex, E extends Edge<V>>
86 implements Result {
87
88 private final Graph<V, E> graph;
89 private List<Set<V>> clusterVertexes = new ArrayList<>();
90 private List<Set<E>> clusterEdges = new ArrayList<>();
91
92 private int index = 0;
93 private final Map<V, VertexData<V>> vertexData = new HashMap<>();
94 private final List<VertexData<V>> visited = new ArrayList<>();
95
96 private SCCResult(Graph<V, E> graph) {
97 this.graph = graph;
98 }
99
100 /**
101 * Returns the number of SCC clusters in the graph.
102 *
103 * @return number of clusters
104 */
105 public int clusterCount() {
106 return clusterEdges.size();
107 }
108
109 /**
110 * Returns the list of strongly connected vertex clusters.
111 *
112 * @return list of strongly connected vertex sets
113 */
114 public List<Set<V>> clusterVertexes() {
115 return clusterVertexes;
116 }
117
118 /**
119 * Returns the list of edges linking strongly connected vertex clusters.
120 *
121 * @return list of strongly connected edge sets
122 */
123 public List<Set<E>> clusterEdges() {
124 return clusterEdges;
125 }
126
127 // Gets the augmentation vertexData for the specified vertex
128 private VertexData<V> data(V vertex) {
129 return vertexData.get(vertex);
130 }
131
132 // Adds augmentation vertexData for the specified vertex
133 private VertexData<V> addData(V vertex) {
134 VertexData<V> d = new VertexData<>(vertex, index);
135 vertexData.put(vertex, d);
136 visited.add(0, d);
137 index++;
138 return d;
139 }
140
141 // Indicates whether the given vertex has been visited
142 private boolean visited(VertexData data) {
143 return visited.contains(data);
144 }
145
146 // Adds a new cluster for the specified vertex
147 private void addCluster(VertexData data) {
148 Set<V> vertexes = findClusterVertices(data);
149 clusterVertexes.add(vertexes);
150 clusterEdges.add(findClusterEdges(vertexes));
151 }
152
153 private Set<V> findClusterVertices(VertexData data) {
154 VertexData<V> nextVertexData;
155 Set<V> vertexes = new HashSet<>();
156 do {
157 nextVertexData = visited.remove(0);
158 vertexes.add(nextVertexData.vertex);
159 } while (data != nextVertexData);
160 return Collections.unmodifiableSet(vertexes);
161 }
162
163 private Set<E> findClusterEdges(Set<V> vertexes) {
164 Set<E> edges = new HashSet<>();
165 for (V vertex : vertexes) {
166 for (E edge : graph.getEdgesFrom(vertex)) {
167 if (vertexes.contains((edge.dst()))) {
168 edges.add(edge);
169 }
170 }
171 }
172 return Collections.unmodifiableSet(edges);
173 }
174
175 public SCCResult<V, E> build() {
176 clusterVertexes = Collections.unmodifiableList(clusterVertexes);
177 clusterEdges = Collections.unmodifiableList(clusterEdges);
178 return this;
179 }
180 }
181
182 // Augments the vertex to assist in determining SCC clusters.
183 private static final class VertexData<V extends Vertex> {
184 final V vertex;
185 int index;
186 int lowLink;
187
188 private VertexData(V vertex, int index) {
189 this.vertex = vertex;
190 this.index = index;
191 this.lowLink = index;
192 }
193 }
194
195}