Strongly Connected Components

Written by Charlotte Knierim.

This article assumes you are familiar with digraphs (directed graphs) and have already read the article about Preorder and Postorder.

In this article we look at strongly connected components of digraphs. Given a digraph, it is a natural question which vertices can be reached by a given vertex. A digraph is called strongly connected, if every vertex can be reached from every other vertex.

If a digraph is not strongly connected, then we can decompose it into strongly connected components. These are maximal sets in the graph such that every vertex in a set can be reached by every other vertex from the same set.

Example:

D cluster_A cluster_B cluster_C a a b b a->b c c b->c c->a d d c->d e e d->e e->d f f f->c

We can display the relationship between the strongly connected components in a component graph. The component graph is a DAG (directed acyclic graph). Note that there is a path from vertex uu to vertex vv if and only if there is a path from the component containing uu to the component containing vv in the component graph.

Example:

Comp_D 1 f 2 a,b,c 1->2 3 d,e 2->3

Algorithm by Kosaraju

We show an algorithm that computes the strongly connected components of a digraph D=(V,E)D=(V,E) in O(V+E)O(V+E). For this algorithm, we need the transpose DT=(V,ET)D^T =(V,E^T) that consists of all edges of DD reversed. Formally we add an edge (u,v)(u,v) to ETE^T if (v,u)(v,u) was an edge in EE.

Note that the strongly connected components of DTD^T are exactly the same as the strongly connected components of DD.

Steps of the algorithm:

  1. Perform a DFS in DD take note of the postorder of the vertices
  2. Create the transpose graph DTD^T
  3. Perform a DFS in DTD^T according to the reverse postorder of the vertices

Example: Starting the DFS at cc visiting dd and ee and then aa and bb and finally restarting the DFS at ff (because we cannot reach it from cc) gives the following result. The number at each vertex indicates the postorder, the DFS edges are marked in red.

D cluster_A cluster_B cluster_C a a,4 b b,3 a->b c c,5 b->c c->a d d,2 c->d e e,1 d->e e->d f f,6 f->c

Keeping the postorder in mind, we now create the transposed graph DTD^T.

DT cluster_A cluster_B cluster_C a a,4 b b,3 a->b c c,5 b->c c->a d d,2 c->d e e,1 d->e e->d f f,6 f->c

On the resulting graph DTD^T we now perform a DFS starting at the vertex with the highest postorder (here ff). Whenever we restart the DFS, we begin a new strongly connected component. Again the DFS edges are indicated in red.

DT cluster_A cluster_B cluster_C a a,4 b b,3 a->b c c,5 b->c c->a d d,2 c->d e e,1 d->e e->d f f,6 f->c

Implementation

Implementation in C++:

struct graph
{
  int n;
  int num_comp = 0;
  vector<vector<int>> adjlist;
  vector<int> post;
  vector<int> comp;


  void dfs(int curr, int curr_comp = -2)
  {
      if (comp[curr] != -1) return;
      comp[curr] = curr_comp;
      for (auto nx : adjlist[curr]) dfs(nx, curr_comp);
      post.push_back(curr);
  }

  void kosaraju()
  {
      // Create list of vertices in post order
      comp.assign(n, -1);
      for (int i = 0; i < n; i++) dfs(i);

      // Reverse post order
      reverse(post.begin(), post.end());

      // Build transpose graph
      vector<vector<int>> rev(n);
      for (int i = 0; i < n; i++)
              for (auto j : adjlist[i]) rev[j].push_back(i);

      swap(rev, adjlist);

      // Run dfs in reverse post order on transpose graph
      comp.assign(n, -1);
      for (int i = 0; i < n; i++)
              if (comp[post[i]] == -1) dfs(post[i], num_comp++);

      // Restore original graph
      swap(rev, adjlist);
  }
};

Remark In the end we have a vector comp such that for any vertex vv we have that vv belongs to the strongly connected component comp[v]. We can now build the component graph in O(E)O(E) by iterating over all edges of DD and for an edge (u,v)E(D)(u,v)\in E(D) adding the edge (comp[u],comp[v]) to the component graph if it is not contained already.