5
5
import java .util .Stack ;
6
6
7
7
/**
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
+ *
10
10
* <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>
52
57
*/
53
58
public class TarjansAlgorithm {
54
59
55
- // Timer for tracking lowtime and insertion time
60
+ // Timer for tracking low time and insertion time
56
61
private int time ;
57
62
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 <>();
59
65
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
+ */
60
73
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
68
75
int [] lowTime = new int [v ];
69
76
int [] insertionTime = new int [v ];
70
77
for (int i = 0 ; i < v ; i ++) {
71
78
insertionTime [i ] = -1 ;
72
79
lowTime [i ] = -1 ;
73
80
}
74
81
75
- // To check if element is present in stack
82
+ // Track if vertices are in the stack
76
83
boolean [] isInStack = new boolean [v ];
77
84
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 <>();
80
87
81
88
for (int i = 0 ; i < v ; i ++) {
82
89
if (insertionTime [i ] == -1 ) {
@@ -87,36 +94,44 @@ public List<List<Integer>> stronglyConnectedComponents(int v, List<List<Integer>
87
94
return sccList ;
88
95
}
89
96
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
+ */
90
107
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
93
109
insertionTime [u ] = time ;
94
110
lowTime [u ] = time ;
95
- time += 1 ;
111
+ time ++ ;
96
112
97
- // Push current node into stack
113
+ // Push current node onto the stack
98
114
isInStack [u ] = true ;
99
115
st .push (u );
100
116
101
- // Go through all vertices adjacent to this
117
+ // Explore adjacent vertices
102
118
for (Integer vertex : graph .get (u )) {
103
- // If the adjacent node is unvisited, do DFS
104
119
if (insertionTime [vertex ] == -1 ) {
105
120
stronglyConnCompsUtil (vertex , lowTime , insertionTime , isInStack , st , graph );
106
- // update lowTime for the current node comparing lowtime of adj node
121
+ // Update low-link value
107
122
lowTime [u ] = Math .min (lowTime [u ], lowTime [vertex ]);
108
123
} else if (isInStack [vertex ]) {
109
- // If adj node is in stack, update low
124
+ // Vertex is in the stack; update low-link value
110
125
lowTime [u ] = Math .min (lowTime [u ], insertionTime [vertex ]);
111
126
}
112
127
}
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
115
130
if (lowTime [u ] == insertionTime [u ]) {
116
131
int w = -1 ;
117
- var scc = new ArrayList <Integer >();
132
+ List < Integer > scc = new ArrayList <>();
118
133
119
- // Stack has all the nodes of the current SCC
134
+ // Pop vertices from the stack until the root is found
120
135
while (w != u ) {
121
136
w = st .pop ();
122
137
scc .add (w );
0 commit comments