Graphs · 15 min

Strongly connected components: two searches, the second backwards

Groups of nodes that all reach each other: why the node a depth-first search finishes last lies in a source component, why Kosaraju's second search runs on the reversed graph, how Tarjan's low-links find the same groups in one pass, and why squashing them leaves a DAG.

01Why it matters

Can you get back?

In a town of one-way streets, driving from the station to the market doesn't mean you can drive back. Some junctions form a tangle where you can get from any of them to any other and home again; leave it by the wrong street and there may be no way back in.

Those tangles are a directed graph's strongly connected components: the largest groups of nodes that can all reach each other. A plain depth-first search can't pick one out: from a node it reaches everything downstream, including nodes with no way back. Kosaraju's algorithm uses two searches, the second with every edge turned round; Tarjan's manages with one.

02Key parts

The pieces you will see

strongly connected
Nodes u and v are strongly connected when a path leads from u to v and another leads back.
strongly connected component (SCC)
A largest group of nodes that are all strongly connected. Every node is in exactly one; a node on no cycle is a component by itself.
condensation
Each SCC squashed into one node, with an edge wherever an edge joins two components. It has no cycles: it is a DAG.
source and sink
A source component has no edges coming in from other components; a sink has none going out.
finishing order
The order a depth-first search finishes nodes in: each after every node its search went on to discover.
reversed graph
Every edge turned round. A path from u to v becomes one from v to u, so the SCCs stay the same.

03See it work

Two searches, the second one backwards

This graph has three components: A, B and C; D, E and F; G and H. B → D and G → F lead into D, E and F, and no edge leads out of them. Neighbours are taken in alphabetical order. In the first pass, each node goes on top of S as it finishes.

The first pass searches from A, finishing C, F, E, D, B and A, then from G, finishing H and G, so G ends on top of S. Then the edges turn round and nodes are popped. G's search collects H: S1 = {G, H}. A's collects C and B: S2 = {A, B, C}. D's collects F and E and turns back at B and G, which are already placed: S3 = {D, E, F}.

A node finishes only after everything its search discovers, so D, E and F finish before B, which led into them. Nothing from A reaches G, so the loop starts a second search there, and G finishes last.

That last finisher always lies in a source component, one that no other component points into. Suppose a component X had an edge into G's. If the first pass entered X first, it reached G's component from there and finished all of it before backing out of X. If it entered G's first, it could never reach X (or the two would be one component), so it finished G's before visiting X. Either way a node of X finishes after G.

Now reverse every edge. Components keep their members, but the edges between them turn round, so the source becomes a sink: a search from G can reach only G's own component. Each later search starts from the latest finisher still unplaced, whose component is a source of what remains, so its reversed edges out lead only to components already placed. The search skips those, which is why D's search turned back at B and G.

The components come out sources first, a topological order of the condensation. Each pass reads every node and edge once (16 node visits and 20 edges examined here), and building the reversed adjacency lists is one more sweep: O(V + E) in all.

04Predict

Your turn: who joins?

Six nodes, typed in as A>B, B>A, C>A, C>D, D>E, E>C, E>F. The first pass plays without questions: work out the finishing order and check it against S. Then predict which node is popped next, and whether each node the backwards search meets joins the current component.

Make 8 predictions: step forward and answer each question before the next step plays.

0 of 8 made

The first search began at A, but A and B form a sink, so they finished early. C finished last, in the source component C, D and E: where the first pass starts doesn't matter.

05Try it

The first pass in code, and Tarjan's single pass

Here is the first pass on four nodes: A and B point at each other, so do C and D, and C → A joins the pairs. finished records each node as it finishes, and the loop at the bottom starts a search from every node not yet seen.

def dfs(graph, u, seen, finished):
    seen[u] = True
    for v in graph[u]:
        if v not in seen:
            dfs(graph, v, seen, finished)
    finished.append(u)

graph = {'A': ['B'], 'B': ['A'], 'C': ['A', 'D'], 'D': ['C']}
seen = {}
finished = []
for u in graph:
    if u not in seen:
        dfs(graph, u, seen, finished)
print(' '.join(finished))

So the second pass starts at C, and the reversed edges matter as much as the order.

Tarjan's algorithm finds the same components in one search. Each node gets an index, its place in the visit order, and a low-link: the smallest index it can get back to through the search tree below it plus one more edge, counting only nodes still on a stack S, where nodes stay after they finish until their component is known. A node that finishes with its low-link equal to its index roots a component: pop S down to it, and those nodes are one SCC.

The search from A gives A to F the indices 0 to 5. C → A drops C's low-link to 0, then B's on the way back; F → D drops F's to 3, then E's. D is the first to finish with its low-link equal to its index, so S pops down to D: {D, E, F}. A pops {A, B, C}, and a search from G pops {G, H}: 8 node visits and 10 edges examined, half Kosaraju's.

Components close sinks first, where Kosaraju's came out sources first. G → F is ignored: F's component is finished and can't reach back to G. Its extra memory is O(V); Kosaraju's reversed copy of the edges needs O(V + E).

The condensation never has a cycle (its components would merge), so SCCs are a common first step. Packages in one component of a dependency graph depend on each other in a loop and must be built together; a topological sort of the condensation orders the groups. In a follow network, a component is a group of accounts that all reach each other. A 2-SAT solver turns each clause “a or b” into edges not-a → b and not-b → a: the clauses can all hold exactly when no variable shares a component with its negation.

The DAG the topological sort lesson ran DFS on. With no cycles, every node is a component of its own: the second pass pops A, C, B, D and E, one search each. That is the reverse finishing order, a topological order, since a DAG is its own condensation.

A reaches every node of that DAG, and still every node is alone.

The opposite slip splits one component in two.

06Check yourself

Check yourself

  1. Question 1 of 6

    Edges: A → B, B → C, C → B, C → D. Which are the strongly connected components?

    Your answer comes back in Review in a few days, so it sticks.

  2. Question 2 of 6

    Edges: A → B, B → C, C → A, C → D, D → E, E → C, E → F. How many strongly connected components are there?

  3. Question 3 of 6

    A hurried Kosaraju pops nodes in reverse finishing order but searches the original edges. On A → B, B → A, B → C, C → D, D → C, the first pass finishes D, C, B, then A. What does the second pass report?

  4. Question 4 of 6

    In Tarjan’s algorithm, node u finishes with index 5 and low-link 2. What happens?

  5. Question 5 of 6

    On a graph with 12 nodes, Kosaraju’s second pass starts 5 searches. What is true of the graph’s condensation?

  6. Question 6 of 6

    Each course is a node, with an edge from every prerequisite to the course that needs it, and no course is its own prerequisite. When can every course be taken?

07Explain it back

Say it in your own words

What is a strongly connected component, and how does Kosaraju's algorithm find them all? Why does its second pass search the reversed graph?

08Recap

What to remember

  • A strongly connected component is a largest group of nodes that all reach each other. Every node is in exactly one, a node on no cycle is alone, and reaching one way isn't enough.
  • Squash each component to a node and the condensation is a DAG, which topological sort can order.
  • Kosaraju's: the last node a DFS finishes lies in a source component. Reverse every edge and pop nodes in reverse finishing order: each search of the reversed graph collects exactly one SCC, sources first.
  • Tarjan's: one DFS that gives each node an index and a low-link. A node whose low-link equals its index roots a component, popped off the stack in one go, sinks first.
  • Both are O(V + E) with adjacency lists. SCCs find dependency cycles, groups in follow networks and 2-SAT answers.
  • Practise with Can Every Course Be Taken?, where a cycle means a component of two or more courses, and Course Order; write the search in Write depth-first search, or try your own graphs in the Kosaraju and Tarjan visualisers.
Back to path