In this topic, we’ll discuss Tarjan’s algorithm for finding strongly connected components (SCCs) in directed graphs. We can check out Kosaraju’s algorithm for the definition of SCCs to start.
2. An Example Graph
Let’s pick an example graph, , for our discussion:
is a directed graph with four SCCs. We’ve depicted the SCCs with different colors for visual comprehension:
Further, we’ll use this graph to demonstrate the ideas of Tarjan’s algorithm.
3. The Spanning Forest of DFS
Before digging into the algorithm itself, we need to introduce the notion of the DFS spanning forest. When DFS traverses a directed graph, it defines a set of non-intersecting trees. We’ll call this set the spanning forest of DFS.
We’ll also classify the edges of the graph depending on how DFS discovers them. If DFS processes vertex and it discovers:
- An unvisited neighbour, , then edge (, ) is a tree edge.
- A visited but not yet fully processed neighbor, , then edge (, ) is a back edge.
- A fully processed neighbour, , then edge (, ) is a cross edge.
Let’s classify the edges of the sample graph. We first run DFS for vertex . DFS visits vertices , , , , , and exits. Then, let’s run DFS for . Now, DFS visits the remaining vertices: , , , and . The tree edges are depicted with solid lines, the back edges with dashed lines, and the cross edges with dotted lines:
The spanning forest of the graph consists of two trees:
- The first tree consists of the set of vertices and the set of edges .
- The second tree consists of the set of vertices and the set of edges .
Note that depending on which vertex we start DFS for, the classification of edges may change. For example, some back edges may become tree edges and vice versa, and some cross edges may become tree edges. Thus, the tree set in the spanning forest may also change. Fortunately, that doesn’t affect Tarjan’s algorithm in any way.
4. Tarjan’s Algorithm
Tarjan’s algorithm makes use of the observation that SCCs can be built out of the trees in the spanning forest. A single tree in the spanning forest may contain several SCCs, but no SCC can belong to more than one tree. If an SCC belonged to more than one tree, then those trees would have been reachable from each other during DFS traversal, thus forming a single tree.
If each SCC exactly matched a tree in the spanning forest, the problem of finding SCCs would have been solved by running a simple DFS and identifying trees in the spanning forest. But that approach only works for finding connected components in undirected graphs. For directed graphs, a tree in the spanning forest may contain multiple SCCs.
Let’s pay attention to another observation that is used by the algorithm. Any SCC can be treated as a directed cycle because any two vertices in an SCC are reachable from each other. If we start DFS for any of the SCC vertices, there will be a moment when DFS sees a back edge to that vertex. A back edge actually identifies a cycle. Thus, when we see a back edge during DFS, we conclude that either we’ve found an SCC or a small cycle inside of a bigger cycle.
4.2. Algorithm Description
Tarjan’s algorithm defines arrays and , which help in classifying edges. They also help in identifying the starting vertex of an SCC. The algorithm also uses a stack to keep the current DFS tree’s vertices and correctly fetches the vertices of SCCs afterward.
The steps of the algorithm are described below:
- Select an unvisited vertex, . If there’re no unvisited vertices, the algorithm terminates
- Run DFS for
- Go to step 1
- is marked as visited
- is initialized to be the current value of the counter
- is initially equal to
- Next, we go over the neighbors. If we see an unvisited neighbor, , we invoke DFS for it and, upon returning, update with if
- If we see a visited but not processed neighbor, , then we have a back edge. In this case, we update with if
- After we process ‘s neighbours, we mark as processed
- After is processed, we check if . If that’s the case, is the starting vertex of its component. We unwind the stack until we retrieve . The unwound vertices belong to the ‘s SCC
4.3. Running the Algorithm for the Example Graph
In our example, the white vertices are unvisited ones. The light grey are visited but not yet processed ones and the dark grey are fully processed vertices. The processed edges are colored red. Also, we depict the vertex stack in the lower right corner.
We start by running DFS for . The image below shows the state after DFS has visited , , and , but hasn’t yet processed back edge :
The image below shows the state when DFS has processed back edge , updated , backtracked, and updated . Next, DFS visited the remaining vertices reachable from :
Then, DFS backtracks from , and in DFS finds the first SCC = :
Next, DFS backtracks to , and the second SCC = is found:
Now, DFS backtracks to , then to , and in DFS finds the third SCC = :
The DFS invocation terminates at this point as no reachable vertices are left. Next, we run DFS for an unvisited vertex, . DFS visits , , , and , processes back edge (, ), and updates :
Then, DFS backtracks to and updates all the vertices on the way:
Finally, when processing , DFS finds the last SCC = .
5. The Pseudocode of the Algorithm
In this section, we’ll implement Tarjan’s algorithm. We’re using a number of variables needed by the algorithm. Note that we could have added all those variables as parameters to the DFS procedure. But let’s keep the DFS implementation simple and have all the auxiliary variables as global data. Here’re all the additional variables used by the algorithm:
- – a counter used to assign sequential numbers to the vertices
- – an array of integers holding the vertice numbers, is the number assigned to
- – an array of integers holding the minimum reachable vertex numbers, is the minimum number of a vertex reachable from
- – an array of booleans indicating which vertices have been visited by DFS so far. If is TRUE, then DFS has already seen , but it hasn’t necessarily finished processing
- – an array of booleans indicating which vertices have been already processed by DFS. If is TRUE, then DFS has already finished with
- – a stack of vertices used to keep the working set of vertices. holds all the vertices reachable from the starting vertex. When the algorithm finds an SCC, it will unwind the stack until it gets all the vertices of that SCC
Tarjan’s algorithm now takes the form of a series of DFS invocations:
6. The Complexity Analysis
Tarjan’s algorithm is a modification of the DFS traversal. So, the complexity of the algorithm is linear: , where is the number of vertices and is the number of edges. Please note that to achieve the mentioned complexity, we must use the adjacency list representation of the graph.
In this topic, we’ve discussed Tarjan’s algorithm for finding strongly connected components in directed graphs. It’s an optimal linear time algorithm.
Furthermore, it’s easy to implement as it simply modifies the standard DFS traversal.