tom

Added Tarjan SCC computation algorithm and associated tests.

1 +package org.onlab.graph;
2 +
3 +import java.util.ArrayList;
4 +import java.util.Collections;
5 +import java.util.HashMap;
6 +import java.util.HashSet;
7 +import java.util.List;
8 +import java.util.Map;
9 +import java.util.Set;
10 +
11 +/**
12 + * Tarjan algorithm for searching a graph and producing results describing
13 + * the graph SCC (strongly-connected components).
14 + */
15 +public 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 +}
...@@ -31,17 +31,17 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest { ...@@ -31,17 +31,17 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
31 31
32 @Test 32 @Test
33 public void searchGraphWithNegativeCycles() { 33 public void searchGraphWithNegativeCycles() {
34 - Set<TestVertex> vertexes = new HashSet<>(vertices()); 34 + Set<TestVertex> vertexes = new HashSet<>(vertexes());
35 vertexes.add(Z); 35 vertexes.add(Z);
36 36
37 Set<TestEdge> edges = new HashSet<>(edges()); 37 Set<TestEdge> edges = new HashSet<>(edges());
38 edges.add(new TestEdge(G, Z, 1.0)); 38 edges.add(new TestEdge(G, Z, 1.0));
39 edges.add(new TestEdge(Z, G, -2.0)); 39 edges.add(new TestEdge(Z, G, -2.0));
40 40
41 - g = new AdjacencyListsGraph<>(vertexes, edges); 41 + graph = new AdjacencyListsGraph<>(vertexes, edges);
42 42
43 GraphPathSearch<TestVertex, TestEdge> search = graphSearch(); 43 GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
44 - Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths(); 44 + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
45 assertEquals("incorrect paths count", 1, paths.size()); 45 assertEquals("incorrect paths count", 1, paths.size());
46 46
47 Path p = paths.iterator().next(); 47 Path p = paths.iterator().next();
...@@ -50,10 +50,10 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest { ...@@ -50,10 +50,10 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
50 assertEquals("incorrect path length", 5, p.edges().size()); 50 assertEquals("incorrect path length", 5, p.edges().size());
51 assertEquals("incorrect path cost", 5.0, p.cost(), 0.1); 51 assertEquals("incorrect path cost", 5.0, p.cost(), 0.1);
52 52
53 - paths = search.search(g, A, G, weight).paths(); 53 + paths = search.search(graph, A, G, weight).paths();
54 assertEquals("incorrect paths count", 0, paths.size()); 54 assertEquals("incorrect paths count", 0, paths.size());
55 55
56 - paths = search.search(g, A, null, weight).paths(); 56 + paths = search.search(graph, A, null, weight).paths();
57 printPaths(paths); 57 printPaths(paths);
58 assertEquals("incorrect paths count", 6, paths.size()); 58 assertEquals("incorrect paths count", 6, paths.size());
59 } 59 }
......
...@@ -29,10 +29,10 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -29,10 +29,10 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
29 29
30 // Executes the default test 30 // Executes the default test
31 protected void executeDefaultTest(int pathCount, int pathLength, double pathCost) { 31 protected void executeDefaultTest(int pathCount, int pathLength, double pathCost) {
32 - g = new AdjacencyListsGraph<>(vertices(), edges()); 32 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
33 33
34 GraphPathSearch<TestVertex, TestEdge> search = graphSearch(); 34 GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
35 - Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths(); 35 + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
36 assertEquals("incorrect paths count", 1, paths.size()); 36 assertEquals("incorrect paths count", 1, paths.size());
37 37
38 Path p = paths.iterator().next(); 38 Path p = paths.iterator().next();
...@@ -41,7 +41,7 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -41,7 +41,7 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
41 assertEquals("incorrect path length", pathLength, p.edges().size()); 41 assertEquals("incorrect path length", pathLength, p.edges().size());
42 assertEquals("incorrect path cost", pathCost, p.cost(), 0.1); 42 assertEquals("incorrect path cost", pathCost, p.cost(), 0.1);
43 43
44 - paths = search.search(g, A, null, weight).paths(); 44 + paths = search.search(graph, A, null, weight).paths();
45 printPaths(paths); 45 printPaths(paths);
46 assertEquals("incorrect paths count", pathCount, paths.size()); 46 assertEquals("incorrect paths count", pathCount, paths.size());
47 } 47 }
......
...@@ -33,11 +33,11 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -33,11 +33,11 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
33 33
34 protected void executeDefaultTest(int minLength, int maxLength, 34 protected void executeDefaultTest(int minLength, int maxLength,
35 double minCost, double maxCost) { 35 double minCost, double maxCost) {
36 - g = new AdjacencyListsGraph<>(vertices(), edges()); 36 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
37 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch(); 37 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
38 38
39 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result = 39 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
40 - search.search(g, A, H, weight); 40 + search.search(graph, A, H, weight);
41 Set<Path<TestVertex, TestEdge>> paths = result.paths(); 41 Set<Path<TestVertex, TestEdge>> paths = result.paths();
42 assertEquals("incorrect path count", 1, paths.size()); 42 assertEquals("incorrect path count", 1, paths.size());
43 43
...@@ -57,12 +57,12 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -57,12 +57,12 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
57 } 57 }
58 58
59 public void executeBroadSearch() { 59 public void executeBroadSearch() {
60 - g = new AdjacencyListsGraph<>(vertices(), edges()); 60 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
61 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch(); 61 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
62 62
63 // Perform narrow path search to a specific destination. 63 // Perform narrow path search to a specific destination.
64 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result = 64 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
65 - search.search(g, A, null, weight); 65 + search.search(graph, A, null, weight);
66 assertEquals("incorrect paths count", 7, result.paths().size()); 66 assertEquals("incorrect paths count", 7, result.paths().size());
67 67
68 int[] types = new int[]{0, 0, 0, 0}; 68 int[] types = new int[]{0, 0, 0, 0};
......
...@@ -32,22 +32,22 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { ...@@ -32,22 +32,22 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
32 32
33 @Test 33 @Test
34 public void noPath() { 34 public void noPath() {
35 - g = new AdjacencyListsGraph<>(of(A, B, C, D), 35 + graph = new AdjacencyListsGraph<>(of(A, B, C, D),
36 of(new TestEdge(A, B, 1), 36 of(new TestEdge(A, B, 1),
37 new TestEdge(B, A, 1), 37 new TestEdge(B, A, 1),
38 new TestEdge(C, D, 1), 38 new TestEdge(C, D, 1),
39 new TestEdge(D, C, 1))); 39 new TestEdge(D, C, 1)));
40 GraphPathSearch<TestVertex, TestEdge> gs = graphSearch(); 40 GraphPathSearch<TestVertex, TestEdge> gs = graphSearch();
41 - Set<Path<TestVertex, TestEdge>> paths = gs.search(g, A, B, weight).paths(); 41 + Set<Path<TestVertex, TestEdge>> paths = gs.search(graph, A, B, weight).paths();
42 printPaths(paths); 42 printPaths(paths);
43 assertEquals("incorrect paths count", 1, paths.size()); 43 assertEquals("incorrect paths count", 1, paths.size());
44 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1); 44 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
45 45
46 - paths = gs.search(g, A, D, weight).paths(); 46 + paths = gs.search(graph, A, D, weight).paths();
47 printPaths(paths); 47 printPaths(paths);
48 assertEquals("incorrect paths count", 0, paths.size()); 48 assertEquals("incorrect paths count", 0, paths.size());
49 49
50 - paths = gs.search(g, A, null, weight).paths(); 50 + paths = gs.search(graph, A, null, weight).paths();
51 printPaths(paths); 51 printPaths(paths);
52 assertEquals("incorrect paths count", 1, paths.size()); 52 assertEquals("incorrect paths count", 1, paths.size());
53 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1); 53 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
...@@ -55,17 +55,17 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { ...@@ -55,17 +55,17 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
55 55
56 @Test 56 @Test
57 public void simpleMultiplePath() { 57 public void simpleMultiplePath() {
58 - g = new AdjacencyListsGraph<>(of(A, B, C, D), 58 + graph = new AdjacencyListsGraph<>(of(A, B, C, D),
59 of(new TestEdge(A, B, 1), 59 of(new TestEdge(A, B, 1),
60 new TestEdge(A, C, 1), 60 new TestEdge(A, C, 1),
61 new TestEdge(B, D, 1), 61 new TestEdge(B, D, 1),
62 new TestEdge(C, D, 1))); 62 new TestEdge(C, D, 1)));
63 - executeSearch(graphSearch(), g, A, D, weight, 2, 2.0); 63 + executeSearch(graphSearch(), graph, A, D, weight, 2, 2.0);
64 } 64 }
65 65
66 @Test 66 @Test
67 public void denseMultiplePath() { 67 public void denseMultiplePath() {
68 - g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G), 68 + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G),
69 of(new TestEdge(A, B, 1), 69 of(new TestEdge(A, B, 1),
70 new TestEdge(A, C, 1), 70 new TestEdge(A, C, 1),
71 new TestEdge(B, D, 1), 71 new TestEdge(B, D, 1),
...@@ -75,12 +75,12 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { ...@@ -75,12 +75,12 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
75 new TestEdge(E, G, 1), 75 new TestEdge(E, G, 1),
76 new TestEdge(F, G, 1), 76 new TestEdge(F, G, 1),
77 new TestEdge(A, G, 4))); 77 new TestEdge(A, G, 4)));
78 - executeSearch(graphSearch(), g, A, G, weight, 5, 4.0); 78 + executeSearch(graphSearch(), graph, A, G, weight, 5, 4.0);
79 } 79 }
80 80
81 @Test 81 @Test
82 public void dualEdgeMultiplePath() { 82 public void dualEdgeMultiplePath() {
83 - g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H), 83 + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H),
84 of(new TestEdge(A, B, 1), new TestEdge(A, C, 3), 84 of(new TestEdge(A, B, 1), new TestEdge(A, C, 3),
85 new TestEdge(B, D, 2), new TestEdge(B, C, 1), 85 new TestEdge(B, D, 2), new TestEdge(B, C, 1),
86 new TestEdge(B, E, 4), new TestEdge(C, E, 1), 86 new TestEdge(B, E, 4), new TestEdge(C, E, 1),
...@@ -88,7 +88,7 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { ...@@ -88,7 +88,7 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
88 new TestEdge(E, F, 1), new TestEdge(F, D, 1), 88 new TestEdge(E, F, 1), new TestEdge(F, D, 1),
89 new TestEdge(F, G, 1), new TestEdge(F, H, 1), 89 new TestEdge(F, G, 1), new TestEdge(F, H, 1),
90 new TestEdge(A, E, 3), new TestEdge(B, D, 1))); 90 new TestEdge(A, E, 3), new TestEdge(B, D, 1)));
91 - executeSearch(graphSearch(), g, A, E, weight, 3, 3.0); 91 + executeSearch(graphSearch(), graph, A, E, weight, 3, 3.0);
92 } 92 }
93 93
94 } 94 }
......
...@@ -19,7 +19,7 @@ public class GraphTest { ...@@ -19,7 +19,7 @@ public class GraphTest {
19 static final TestVertex H = new TestVertex("H"); 19 static final TestVertex H = new TestVertex("H");
20 static final TestVertex Z = new TestVertex("Z"); 20 static final TestVertex Z = new TestVertex("Z");
21 21
22 - protected Graph<TestVertex, TestEdge> g; 22 + protected Graph<TestVertex, TestEdge> graph;
23 23
24 protected EdgeWeight<TestVertex, TestEdge> weight = 24 protected EdgeWeight<TestVertex, TestEdge> weight =
25 new EdgeWeight<TestVertex, TestEdge>() { 25 new EdgeWeight<TestVertex, TestEdge>() {
...@@ -35,7 +35,7 @@ public class GraphTest { ...@@ -35,7 +35,7 @@ public class GraphTest {
35 } 35 }
36 } 36 }
37 37
38 - protected Set<TestVertex> vertices() { 38 + protected Set<TestVertex> vertexes() {
39 return of(A, B, C, D, E, F, G, H); 39 return of(A, B, C, D, E, F, G, H);
40 } 40 }
41 41
......
1 +package org.onlab.graph;
2 +
3 +import org.junit.Test;
4 +
5 +import static com.google.common.collect.ImmutableSet.of;
6 +import static org.junit.Assert.assertEquals;
7 +import static org.onlab.graph.TarjanGraphSearch.SCCResult;
8 +
9 +/**
10 + * Tarjan graph search tests.
11 + */
12 +public class TarjanGraphSearchTest extends GraphTest {
13 +
14 + private void validate(SCCResult<TestVertex, TestEdge> result, int cc) {
15 + System.out.println("Cluster count: " + result.clusterVertexes().size());
16 + System.out.println("Clusters: " + result.clusterVertexes());
17 + assertEquals("incorrect cluster count", cc, result.clusterCount());
18 + }
19 +
20 + private void validate(SCCResult<TestVertex, TestEdge> result,
21 + int i, int vc, int ec) {
22 + assertEquals("incorrect cluster count", vc, result.clusterVertexes().get(i).size());
23 + assertEquals("incorrect edge count", ec, result.clusterEdges().get(i).size());
24 + }
25 +
26 + @Test
27 + public void basic() {
28 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
29 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
30 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
31 + validate(result, 6);
32 + }
33 +
34 + @Test
35 + public void singleCluster() {
36 + graph = new AdjacencyListsGraph<>(vertexes(),
37 + of(new TestEdge(A, B, 1),
38 + new TestEdge(B, C, 1),
39 + new TestEdge(C, D, 1),
40 + new TestEdge(D, E, 1),
41 + new TestEdge(E, F, 1),
42 + new TestEdge(F, G, 1),
43 + new TestEdge(G, H, 1),
44 + new TestEdge(H, A, 1)));
45 +
46 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
47 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
48 + validate(result, 1);
49 + validate(result, 0, 8, 8);
50 + }
51 +
52 + @Test
53 + public void twoUnconnectedCluster() {
54 + graph = new AdjacencyListsGraph<>(vertexes(),
55 + of(new TestEdge(A, B, 1),
56 + new TestEdge(B, C, 1),
57 + new TestEdge(C, D, 1),
58 + new TestEdge(D, A, 1),
59 + new TestEdge(E, F, 1),
60 + new TestEdge(F, G, 1),
61 + new TestEdge(G, H, 1),
62 + new TestEdge(H, E, 1)));
63 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
64 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
65 + validate(result, 2);
66 + validate(result, 0, 4, 4);
67 + validate(result, 1, 4, 4);
68 + }
69 +
70 + @Test
71 + public void twoWeaklyConnectedClusters() {
72 + graph = new AdjacencyListsGraph<>(vertexes(),
73 + of(new TestEdge(A, B, 1),
74 + new TestEdge(B, C, 1),
75 + new TestEdge(C, D, 1),
76 + new TestEdge(D, A, 1),
77 + new TestEdge(E, F, 1),
78 + new TestEdge(F, G, 1),
79 + new TestEdge(G, H, 1),
80 + new TestEdge(H, E, 1),
81 + new TestEdge(B, E, 1)));
82 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
83 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
84 + validate(result, 2);
85 + validate(result, 0, 4, 4);
86 + validate(result, 1, 4, 4);
87 + }
88 +
89 + @Test
90 + public void twoClustersConnectedWithIgnoredEdges() {
91 + graph = new AdjacencyListsGraph<>(vertexes(),
92 + of(new TestEdge(A, B, 1),
93 + new TestEdge(B, C, 1),
94 + new TestEdge(C, D, 1),
95 + new TestEdge(D, A, 1),
96 + new TestEdge(E, F, 1),
97 + new TestEdge(F, G, 1),
98 + new TestEdge(G, H, 1),
99 + new TestEdge(H, E, 1),
100 + new TestEdge(B, E, -1),
101 + new TestEdge(E, B, -1)));
102 +
103 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
104 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, weight);
105 + validate(result, 2);
106 + validate(result, 0, 4, 4);
107 + validate(result, 1, 4, 4);
108 + }
109 +
110 +}