Tarjan’s SCC Algorithm
Find every strongly connected component in a single DFS pass.
Why Tarjan’s SCC Algorithm?
A Strongly Connected Component (SCC) of a directed graph is a maximal set of vertices such that every vertex is reachable from every other vertex in the set. Decomposing a graph into its SCCs is fundamental to problems in dependency analysis, compiler optimisation, and competitive programming.
Tarjan’s algorithm, published in 1972, finds all SCCs in a single depth-first search. Compared to Kosaraju’s two-pass approach, Tarjan’s requires only one DFS traversal and uses an explicit stack to track which vertices might belong to the current SCC.
- Single pass — no need to reverse the graph or run DFS twice
- O(V + E) time and space, same as Kosaraju’s
- Online friendly — SCCs are emitted as soon as they are fully explored
- Foundation for 2-SAT, condensation graphs, and finding bridges/articulation points
The Algorithm
Tarjan’s algorithm maintains two arrays and a stack:
disc[u]— the discovery time when vertex u is first visitedlow[u]— the smallest discovery time reachable from the subtree rooted at u using at most one back edge- Stack — holds vertices that may still be part of the current SCC
timer = 0
function dfs(u):
disc[u] = low[u] = timer++
stack.push(u); onStack[u] = true
for each neighbor v of u:
if disc[v] == -1: // tree edge
dfs(v)
low[u] = min(low[u], low[v])
else if onStack[v]: // back edge
low[u] = min(low[u], disc[v])
if disc[u] == low[u]: // u is SCC root
pop vertices from stack until u is popped
→ these vertices form one SCC
Key insight: A vertex u is the root of its SCC exactly when disc[u] == low[u]. At that point every vertex above u on the stack belongs to the same SCC. We pop them all off and record the component.
Back-Edge Detection
When we encounter an edge u → v where v is already on the stack, we know there is a path from v back to u (through the DFS tree) and from u to v (the current edge). This cycle means u and v are in the same SCC, so we update low[u] = min(low[u], disc[v]).
Step-by-Step Walkthrough
Trace Tarjan’s algorithm on this 8-node directed graph. The three SCCs are {0, 1, 2}, {3, 4, 5}, and {6, 7}.
Dashed arrows are cross-edges between SCCs. Solid arrows form cycles within each SCC.
Initialise
disc = [-1,-1,-1,-1,-1,-1,-1,-1], low same. Stack empty. Timer = 0.
Visit node 0
disc[0]=0, low[0]=0. Push 0 onto stack.
Stack: [0]
Visit node 1 via 0 → 1
disc[1]=1, low[1]=1. Push 1.
Stack: [0, 1]
Visit node 2 via 1 → 2
disc[2]=2, low[2]=2. Push 2.
Stack: [0, 1, 2]
Edge 2 → 0 — back edge!
Node 0 is on the stack. low[2] = min(2, disc[0]) = 0.
This tells us node 2 can reach as far back as node 0 through a cycle.
low[2] updated: 0Visit node 3 via 2 → 3 (cross-edge to new SCC)
disc[3]=3, low[3]=3. Push 3.
Stack: [0, 1, 2, 3]
Visit node 4 via 3 → 4
disc[4]=4, low[4]=4. Push 4.
Stack: [0, 1, 2, 3, 4]
Visit node 5 via 4 → 5
disc[5]=5, low[5]=5. Push 5.
Stack: [0, 1, 2, 3, 4, 5]
Edge 5 → 3 — back edge!
Node 3 is on the stack. low[5] = min(5, disc[3]) = 3.
Visit node 6 via 5 → 6
disc[6]=6, low[6]=6. Push 6.
Stack: [0, 1, 2, 3, 4, 5, 6]
Visit node 7 via 6 → 7
disc[7]=7, low[7]=7. Push 7.
Stack: [0, 1, 2, 3, 4, 5, 6, 7]
Edge 7 → 6 — back edge & SCC {6, 7} found!
low[7] = min(7, disc[6]) = 6. Backtrack to node 6: low[6] = min(6, low[7]) = 6.
Since disc[6] == low[6] == 6, node 6 is an SCC root. Pop stack until 6: {7, 6}.
Backtrack — SCC {3, 4, 5} found!
Back to 5: low[5] = 3 (unchanged). Back to 4: low[4] = min(4, 3) = 3. Back to 3: low[3] = min(3, 3) = 3.
disc[3] == low[3] == 3 → node 3 is SCC root. Pop: {5, 4, 3}.
Backtrack — SCC {0, 1, 2} found!
Back to 2: low[2] = 0 (unchanged). Back to 1: low[1] = min(1, 0) = 0. Back to 0: low[0] = min(0, 0) = 0.
disc[0] == low[0] == 0 → node 0 is SCC root. Pop: {2, 1, 0}.
Tarjan vs Kosaraju
Tarjan’s Algorithm
- Single DFS pass
- Uses an explicit stack +
onStackarray - Emits SCCs as soon as root is identified
- Slightly more complex to implement
Kosaraju’s Algorithm
- Two DFS passes (original + transposed graph)
- Requires building the reversed graph
- Conceptually simpler — finish-time ordering
- Extra O(V + E) space for the transposed graph
| Property | Tarjan | Kosaraju |
|---|---|---|
| DFS passes | 1 | 2 |
| Extra graph? | No | Yes (transposed) |
| Time | O(V+E) | O(V+E) |
| Space overhead | disc + low + stack | Transposed adj list |
| SCC output order | Reverse topological | Topological |
| Implementation | Moderate | Easy |
Both run in O(V + E). Choose Tarjan’s when you want a single pass or need SCCs in reverse topological order. Choose Kosaraju’s when simplicity matters or you already have the transposed graph.
Interactive Animation
Tarjan’s SCC — Live
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| disc | - | - | - | - | - | - | - | - |
| low | - | - | - | - | - | - | - | - |
Stack: [ ]
Press Play or Step to begin.
Colour key: ● Default ● On stack ● Current ■ SCC found
Condensation Graph (DAG of SCCs)
Once all SCCs are found, contract each SCC into a single super-node. The resulting graph is a DAG (directed acyclic graph) called the condensation. Every edge in the original graph that crosses SCC boundaries becomes an edge in the DAG.
The condensation is useful for:
- Topological ordering of components for dependency resolution
- DP on DAGs — e.g., longest path, counting paths
- Reachability queries — reduce to reachability on the smaller DAG
- 2-SAT — check satisfiability via the implication graph’s SCC structure
C++ Implementation
Tarjan’s SCC
#include <bits/stdc++.h>
using namespace std;
struct TarjanSCC {
int n, timer = 0;
vector<vector<int>> adj;
vector<int> disc, low, stk;
vector<bool> onStk;
vector<vector<int>> sccs;
TarjanSCC(int n) : n(n), adj(n), disc(n, -1),
low(n), onStk(n, false) {}
void addEdge(int u, int v) { adj[u].push_back(v); }
void dfs(int u) {
disc[u] = low[u] = timer++;
stk.push_back(u);
onStk[u] = true;
for (int v : adj[u]) {
if (disc[v] == -1) {
dfs(v);
low[u] = min(low[u], low[v]);
} else if (onStk[v]) {
low[u] = min(low[u], disc[v]);
}
}
if (disc[u] == low[u]) {
vector<int> scc;
while (true) {
int v = stk.back(); stk.pop_back();
onStk[v] = false;
scc.push_back(v);
if (v == u) break;
}
sccs.push_back(scc);
}
}
void findSCCs() {
for (int i = 0; i < n; i++)
if (disc[i] == -1) dfs(i);
}
};
Condensation Graph Builder
// Returns {condensation DAG adjacency, comp[v] = SCC index of v}
pair<vector<vector<int>>, vector<int>>
buildCondensation(TarjanSCC& t) {
int k = t.sccs.size();
vector<int> comp(t.n);
for (int i = 0; i < k; i++)
for (int v : t.sccs[i])
comp[v] = i;
vector<vector<int>> dag(k);
set<pair<int,int>> seen;
for (int u = 0; u < t.n; u++)
for (int v : t.adj[u])
if (comp[u] != comp[v])
if (seen.insert({comp[u], comp[v]}).second)
dag[comp[u]].push_back(comp[v]);
return {dag, comp};
}
Usage Example
int main() {
int n, m; cin >> n >> m;
TarjanSCC g(n);
while (m--) {
int u, v; cin >> u >> v;
g.addEdge(u, v);
}
g.findSCCs();
// Print SCCs
for (auto& scc : g.sccs) {
for (int v : scc) cout << v << ' ';
cout << '\n';
}
// Build and use condensation
auto [dag, comp] = buildCondensation(g);
// dag is a DAG — apply topological sort, DP, etc.
}
Complexity Analysis
| Aspect | Complexity | Reason |
|---|---|---|
| Time | O(V + E) | Each vertex and edge visited exactly once in DFS |
| Space | O(V + E) | Adjacency list + disc/low/stack arrays |
| Condensation | O(V + E) | Single pass over all edges to build the DAG |
Every vertex is pushed and popped from the stack exactly once, and each edge is examined once during the DFS. The low-link updates are O(1) per edge. This makes Tarjan’s algorithm optimal for SCC decomposition.
Practical note: The recursive DFS can overflow the call stack for very large graphs (V > 106). In competitive programming, either increase the stack size or convert to an iterative implementation.
Practice Problems
- CSES. Planets and Kingdoms — Direct SCC decomposition
- CSES. Coin Collector — SCC condensation + DP on DAG
- Codeforces 427C. Checkposts — Min-cost vertex per SCC
- LeetCode 1192. Critical Connections in a Network — Tarjan’s bridges variant
- LeetCode 2360. Longest Cycle in a Graph — Cycle detection via SCC ideas
- Codeforces 22E. Scheme — Make a graph strongly connected
Summary
- Tarjan’s algorithm finds all SCCs in O(V + E) with a single DFS pass.
- It uses
disc[]andlow[]arrays plus an explicit stack to detect SCC roots. - A vertex
uis an SCC root whendisc[u] == low[u]; pop the stack down touto extract the component. - The condensation graph collapses each SCC into a node, yielding a DAG for further algorithms (DP, topological sort).
- Compared to Kosaraju, Tarjan needs only one DFS and no transposed graph.
- The same low-link idea powers algorithms for bridges and articulation points.