Skip to content

Commit c657138

Browse files
authored
Enhance docs, add tests in TarjansAlgorithm (#5970)
1 parent 474e0de commit c657138

File tree

2 files changed

+158
-81
lines changed

2 files changed

+158
-81
lines changed

src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java

+82-67
Original file line numberDiff line numberDiff line change
@@ -5,78 +5,85 @@
55
import java.util.Stack;
66

77
/**
8-
* Java program that implements Tarjan's Algorithm.
9-
* @author <a href="https://github.com/shivu2002a">Shivanagouda S A</a>
8+
* Java program that implements Tarjan's Algorithm to find Strongly Connected Components (SCCs) in a directed graph.
9+
*
1010
* <p>
11-
* Tarjan's algorithm is a linear time algorithm to find the strongly connected components of a
12-
directed graph, which, from here onwards will be referred as SCC.
13-
14-
* A graph is said to be strongly connected if every vertex is reachable from every other vertex.
15-
The SCCs of a directed graph form a partition into subgraphs that are themselves strongly
16-
connected. Single node is always a SCC.
17-
18-
* Example:
19-
0 --------> 1 -------> 3 --------> 4
20-
^ /
21-
| /
22-
| /
23-
| /
24-
| /
25-
| /
26-
| /
27-
| /
28-
| /
29-
| /
30-
|V
31-
2
32-
33-
For the above graph, the SCC list goes as follows:
34-
1, 2, 0
35-
3
36-
4
37-
38-
We can also see that order of the nodes in an SCC doesn't matter since they are in cycle.
39-
40-
{@summary}
41-
Tarjan's Algorithm:
42-
* DFS search produces a DFS tree
43-
* Strongly Connected Components form subtrees of the DFS tree.
44-
* If we can find the head of these subtrees, we can get all the nodes in that subtree (including
45-
the head) and that will be one SCC.
46-
* There is no back edge from one SCC to another (here can be cross edges, but they will not be
47-
used).
48-
49-
* Kosaraju Algorithm aims at doing the same but uses two DFS traversalse whereas Tarjan’s
50-
algorithm does the same in a single DFS, which leads to much lower constant factors in the latter.
51-
11+
* Tarjan's algorithm is a linear time algorithm (O(V + E)) that identifies the SCCs of a directed graph.
12+
* An SCC is a maximal subgraph where every vertex is reachable from every other vertex within the subgraph.
13+
*
14+
* <h3>Algorithm Overview:</h3>
15+
* <ul>
16+
* <li>DFS Search: A depth-first search (DFS) is performed on the graph to generate a DFS tree.</li>
17+
* <li>Identification of SCCs: SCCs correspond to subtrees within this DFS tree.</li>
18+
* <li>Low-Link Values: For each node, a low-link value is maintained, which indicates the earliest visited
19+
* vertex (the one with the minimum insertion time) that can be reached from that subtree.</li>
20+
* <li>Stack Usage: Nodes are stored in a stack during DFS. When an SCC is identified, nodes are popped from
21+
* the stack until the head of the SCC is reached.</li>
22+
* </ul>
23+
*
24+
* <p>
25+
* Example of a directed graph:
26+
* <pre>
27+
* 0 --------> 1 -------> 3 --------> 4
28+
* ^ /
29+
* | /
30+
* | /
31+
* | /
32+
* | /
33+
* | /
34+
* | /
35+
* | /
36+
* | /
37+
* | /
38+
* V
39+
* 2
40+
* </pre>
41+
*
42+
* <p>
43+
* For the above graph, the SCC list is as follows:
44+
* <ul>
45+
* <li>1, 2, 0</li>
46+
* <li>3</li>
47+
* <li>4</li>
48+
* </ul>
49+
* The order of nodes in an SCC does not matter as they form cycles.
50+
*
51+
* <h3>Comparison with Kosaraju's Algorithm:</h3>
52+
* <p>
53+
* Kosaraju's algorithm also identifies SCCs but does so using two DFS traversals.
54+
* In contrast, Tarjan's algorithm achieves this in a single DFS traversal, leading to improved performance
55+
* in terms of constant factors.
56+
* </p>
5257
*/
5358
public class TarjansAlgorithm {
5459

55-
// Timer for tracking lowtime and insertion time
60+
// Timer for tracking low time and insertion time
5661
private int time;
5762

58-
private final List<List<Integer>> sccList = new ArrayList<List<Integer>>();
63+
// List to store all strongly connected components
64+
private final List<List<Integer>> sccList = new ArrayList<>();
5965

66+
/**
67+
* Finds and returns the strongly connected components (SCCs) of the directed graph.
68+
*
69+
* @param v the number of vertices in the graph
70+
* @param graph the adjacency list representation of the graph
71+
* @return a list of lists, where each inner list represents a strongly connected component
72+
*/
6073
public List<List<Integer>> stronglyConnectedComponents(int v, List<List<Integer>> graph) {
61-
62-
// Initially all vertices as unvisited, insertion and low time are undefined
63-
64-
// insertionTime:Time when a node is visited 1st time while DFS traversal
65-
66-
// lowTime: indicates the earliest visited vertex (the vertex with minimum insertion time)
67-
// that can be reached from a subtree rooted with a particular node.
74+
// Initialize arrays for insertion time and low-link values
6875
int[] lowTime = new int[v];
6976
int[] insertionTime = new int[v];
7077
for (int i = 0; i < v; i++) {
7178
insertionTime[i] = -1;
7279
lowTime[i] = -1;
7380
}
7481

75-
// To check if element is present in stack
82+
// Track if vertices are in the stack
7683
boolean[] isInStack = new boolean[v];
7784

78-
// Store nodes during DFS
79-
Stack<Integer> st = new Stack<Integer>();
85+
// Stack to hold nodes during DFS
86+
Stack<Integer> st = new Stack<>();
8087

8188
for (int i = 0; i < v; i++) {
8289
if (insertionTime[i] == -1) {
@@ -87,36 +94,44 @@ public List<List<Integer>> stronglyConnectedComponents(int v, List<List<Integer>
8794
return sccList;
8895
}
8996

97+
/**
98+
* A utility function to perform DFS and find SCCs.
99+
*
100+
* @param u the current vertex being visited
101+
* @param lowTime array to keep track of the low-link values
102+
* @param insertionTime array to keep track of the insertion times
103+
* @param isInStack boolean array indicating if a vertex is in the stack
104+
* @param st the stack used for DFS
105+
* @param graph the adjacency list representation of the graph
106+
*/
90107
private void stronglyConnCompsUtil(int u, int[] lowTime, int[] insertionTime, boolean[] isInStack, Stack<Integer> st, List<List<Integer>> graph) {
91-
92-
// Initialize insertion time and lowTime value of current node
108+
// Set insertion time and low-link value
93109
insertionTime[u] = time;
94110
lowTime[u] = time;
95-
time += 1;
111+
time++;
96112

97-
// Push current node into stack
113+
// Push current node onto the stack
98114
isInStack[u] = true;
99115
st.push(u);
100116

101-
// Go through all vertices adjacent to this
117+
// Explore adjacent vertices
102118
for (Integer vertex : graph.get(u)) {
103-
// If the adjacent node is unvisited, do DFS
104119
if (insertionTime[vertex] == -1) {
105120
stronglyConnCompsUtil(vertex, lowTime, insertionTime, isInStack, st, graph);
106-
// update lowTime for the current node comparing lowtime of adj node
121+
// Update low-link value
107122
lowTime[u] = Math.min(lowTime[u], lowTime[vertex]);
108123
} else if (isInStack[vertex]) {
109-
// If adj node is in stack, update low
124+
// Vertex is in the stack; update low-link value
110125
lowTime[u] = Math.min(lowTime[u], insertionTime[vertex]);
111126
}
112127
}
113-
// If lowtime and insertion time are same, current node is the head of an SCC
114-
// head node found, get all the nodes in this SCC
128+
129+
// Check if the current vertex is the root of an SCC
115130
if (lowTime[u] == insertionTime[u]) {
116131
int w = -1;
117-
var scc = new ArrayList<Integer>();
132+
List<Integer> scc = new ArrayList<>();
118133

119-
// Stack has all the nodes of the current SCC
134+
// Pop vertices from the stack until the root is found
120135
while (w != u) {
121136
w = st.pop();
122137
scc.add(w);

src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java

+76-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.thealgorithms.datastructures.graphs;
22

3-
import static org.junit.jupiter.api.Assertions.assertTrue;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
44

55
import java.util.ArrayList;
66
import java.util.Arrays;
@@ -9,11 +9,11 @@
99

1010
public class TarjansAlgorithmTest {
1111

12-
TarjansAlgorithm tarjansAlgo = new TarjansAlgorithm();
12+
private final TarjansAlgorithm tarjansAlgo = new TarjansAlgorithm();
1313

1414
@Test
15-
public void findStronglyConnectedComps() {
16-
var v = 5;
15+
public void testFindStronglyConnectedComponents() {
16+
int v = 5;
1717
var graph = new ArrayList<List<Integer>>();
1818
for (int i = 0; i < v; i++) {
1919
graph.add(new ArrayList<>());
@@ -32,23 +32,20 @@ public void findStronglyConnectedComps() {
3232
4
3333
*/
3434
List<List<Integer>> expectedResult = new ArrayList<>();
35-
36-
expectedResult.add(Arrays.asList(4));
37-
expectedResult.add(Arrays.asList(3));
35+
expectedResult.add(List.of(4));
36+
expectedResult.add(List.of(3));
3837
expectedResult.add(Arrays.asList(2, 1, 0));
39-
assertTrue(expectedResult.equals(actualResult));
38+
assertEquals(expectedResult, actualResult);
4039
}
4140

4241
@Test
43-
public void findStronglyConnectedCompsShouldGetSingleNodes() {
44-
// Create a adjacency list of graph
45-
var n = 8;
42+
public void testFindStronglyConnectedComponentsWithSingleNodes() {
43+
// Create a graph where each node is its own SCC
44+
int n = 8;
4645
var adjList = new ArrayList<List<Integer>>(n);
47-
4846
for (int i = 0; i < n; i++) {
4947
adjList.add(new ArrayList<>());
5048
}
51-
5249
adjList.get(0).add(1);
5350
adjList.get(1).add(2);
5451
adjList.get(2).add(3);
@@ -65,6 +62,71 @@ public void findStronglyConnectedCompsShouldGetSingleNodes() {
6562
7, 6, 5, 4, 3, 2, 1, 0
6663
*/
6764
expectedResult.add(Arrays.asList(7, 6, 5, 4, 3, 2, 1, 0));
68-
assertTrue(expectedResult.equals(actualResult));
65+
assertEquals(expectedResult, actualResult);
66+
}
67+
68+
@Test
69+
public void testGraphWithMultipleSCCs() {
70+
int v = 6;
71+
var graph = new ArrayList<List<Integer>>();
72+
for (int i = 0; i < v; i++) {
73+
graph.add(new ArrayList<>());
74+
}
75+
graph.get(0).add(1);
76+
graph.get(1).add(2);
77+
graph.get(2).add(0);
78+
graph.get(3).add(4);
79+
graph.get(4).add(5);
80+
graph.get(5).add(3);
81+
82+
var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph);
83+
List<List<Integer>> expectedResult = new ArrayList<>();
84+
expectedResult.add(Arrays.asList(2, 1, 0)); // SCC containing 0, 1, 2
85+
expectedResult.add(Arrays.asList(5, 4, 3)); // SCC containing 3, 4, 5
86+
assertEquals(expectedResult, actualResult);
87+
}
88+
89+
@Test
90+
public void testDisconnectedGraph() {
91+
int v = 7;
92+
var graph = new ArrayList<List<Integer>>();
93+
for (int i = 0; i < v; i++) {
94+
graph.add(new ArrayList<>());
95+
}
96+
graph.get(0).add(1);
97+
graph.get(1).add(0);
98+
graph.get(2).add(3);
99+
graph.get(3).add(4);
100+
graph.get(4).add(2);
101+
102+
var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph);
103+
List<List<Integer>> expectedResult = new ArrayList<>();
104+
expectedResult.add(Arrays.asList(1, 0)); // SCC containing 0, 1
105+
expectedResult.add(Arrays.asList(4, 3, 2)); // SCC containing 2, 3, 4
106+
expectedResult.add(List.of(5)); // SCC containing 5
107+
expectedResult.add(List.of(6)); // SCC containing 6
108+
assertEquals(expectedResult, actualResult);
109+
}
110+
111+
@Test
112+
public void testSingleNodeGraph() {
113+
int v = 1;
114+
var graph = new ArrayList<List<Integer>>();
115+
graph.add(new ArrayList<>());
116+
117+
var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph);
118+
List<List<Integer>> expectedResult = new ArrayList<>();
119+
expectedResult.add(List.of(0)); // SCC with a single node
120+
assertEquals(expectedResult, actualResult);
121+
}
122+
123+
@Test
124+
public void testEmptyGraph() {
125+
int v = 0;
126+
var graph = new ArrayList<List<Integer>>();
127+
128+
var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph);
129+
List<List<Integer>> expectedResult = new ArrayList<>(); // No SCCs in an empty graph
130+
assertEquals(expectedResult, actualResult);
69131
}
70132
}

0 commit comments

Comments
 (0)