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,40 +55,40 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { ...@@ -55,40 +55,40 @@ 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),
72 - new TestEdge(C, D, 1), 72 + new TestEdge(C, D, 1),
73 - new TestEdge(D, E, 1), 73 + new TestEdge(D, E, 1),
74 - new TestEdge(D, F, 1), 74 + new TestEdge(D, F, 1),
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),
87 - new TestEdge(D, H, 5), new TestEdge(D, E, 1), 87 + new TestEdge(D, H, 5), new TestEdge(D, E, 1),
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 +}