Strong connectivity#2403

Strong connectivity (Editorial)

The problem requires finding the number of strongly connected components of a directed graph. To do this, we use Kosaraju's algorithm, which performs two depth-first searches:

The reverse graph has the same strongly connected components as the original one: if there are paths from to and from to , then after reversing the edges these paths are preserved, only their direction changes. The order of processing vertices in the second traversal guarantees that the depth-first search does not leave the current component: all components it could reach have already been visited by this moment.

The answer is the number of depth-first search starts in the second traversal.

Example

The graphs given in the examples are shown below. Strongly connected components are highlighted in color.

The first graph contains no cycles: it is impossible to return to any vertex. Therefore, each vertex forms a separate strongly connected component: , , .

In the second graph, vertices , and lie on the cycle 1 \to 2 \to 3 \to 1 and form one component \{1, 2, 3\}. Vertex cannot be reached from vertex , so vertices and form separate components and . Thus, each of the graphs contains strongly connected components.

Let us simulate the algorithm on the second graph. The first depth-first search from vertex follows the path 1 \to 2 \to 3, and then from vertex the path 4 \to 5. The order in which vertex processing is finished is top = (3, 2, 1, 5, 4). After reversing, we get (4, 5, 1, 2, 3).

The reverse graph contains the edges 2 \to 1, 3 \to 2, 1 \to 3 and 5 \to 4. Perform the second traversal in the order (4, 5, 1, 2, 3):

Vertices and have already been visited, so there are no new starts. We obtain c = 3 components.

Note that in the reverse graph vertex , which belongs to another component, is reachable from vertex . However, vertex is processed before vertex , so by the time is called it has already been visited and does not get into the component of vertex .

Algorithm implementation

Store the input graph as an adjacency list , and the reverse graph as an adjacency list . The array marks visited vertices. The array stores the vertices in the order in which their processing is finished during the first depth-first search.

vector<vector<int> > g, gr;
vector<int> used, top;

The function dfs1 performs a depth-first search on the input graph. Vertex is appended to the end of the array after all vertices reachable from it have been processed, that is, at the moment of leaving it.

void dfs1(int v)
{
  used[v] = 1;
  for (int to : g[v])
    if (!used[to]) dfs1(to);
  top.push_back(v);
}

The function dfs2 performs a depth-first search on the reverse graph. All vertices visited during one call of from the main program form one strongly connected component.

void dfs2(int v)
{
  used[v] = 1;
  for (int to : gr[v])
    if (!used[to]) dfs2(to);
}

The main part of the program. Read the input graph. Add each edge a \to b to the graph , and the opposite edge b \to a to the reverse graph .

scanf("%d %d", &n, &m);
g.resize(n + 1);
gr.resize(n + 1);
for(i = 1; i <= m; i++)
{
  scanf("%d %d", &a, &b);
  g[a].push_back(b);
  gr[b].push_back(a);
}

Start a depth-first search on the input graph from every vertex that has not been visited yet. After that, the array contains all vertices of the graph in the order in which their processing was finished.

used.resize(n+1);
for(i = 1; i <= n; i++)
  if (!used[i]) dfs1(i);

Before the second traversal, reset the array . The vertices of the reverse graph must be considered in the reverse order of finishing their processing, that is, the array must be scanned from the end to the beginning. For convenience, reverse the array .

used.assign(n + 1, 0);
reverse(top.begin(), top.end());

The variable counts the number of strongly connected components.

c = 0;

Move through the array from left to right. From each vertex that has not been visited yet, start the depth-first search dfs2 on the reverse graph. It traverses exactly one new strongly connected component, so increase by .

for (int v : top)
  if (!used[v])
  {
    dfs2(v);
    c++;
  }

Print the number of strongly connected components of the graph.

printf("%d\n",c);

Java implementation

import java.util.*;

public class Main
{
  static ArrayList<Integer>[] g, gr;
  static boolean used[];
  static Vector<Integer> top = new Vector<Integer>();

  static void dfs1(int v)
  {
    used[v] = true;
    for(int i = 0; i < g[v].size(); i++)
    {
      int to = g[v].get(i);
      if (!used[to]) dfs1(to);
    }
    top.add(v);
  }

  static void dfs2(int v)
  {
    used[v] = true;
    for(int i = 0; i < gr[v].size(); i++)
    {
      int to = gr[v].get(i);
      if (!used[to]) dfs2(to);
    }
  }

  @SuppressWarnings("unchecked")
  public static void main(String[] args)
  {
    Scanner con = new Scanner(System.in);
    int n = con.nextInt();
    int m = con.nextInt();

    g = new ArrayList[n+1];
    for(int i = 0; i <= n; i++)
      g[i] = new ArrayList<Integer>();
    gr = new ArrayList[n+1];
    for(int i = 0; i <= n; i++)
      gr[i] = new ArrayList<Integer>();

    for (int i = 0; i < m; i++)
    {
      int a = con.nextInt();
      int b = con.nextInt();
      g[a].add(b);
      gr[b].add(a);
    }
    used = new boolean[n+1];
    for(int i = 1; i <= n; i++)
      if (!used[i]) dfs1(i);

    used = new boolean[n+1];
    int c = 0;
    for(int i = 1; i <= n; i++)
    {
      int v = top.get(n-i);
      if (!used[v])
      {
        dfs2(v);
        c++;
      }
    }

    System.out.println(c);
    con.close();
  }
}

Python implementation

The function dfs1 performs a depth-first search on the input graph. Vertex is appended to the end of the list after all vertices reachable from it have been processed, that is, at the moment of leaving it.

def dfs1(v):
  global g, used, top
  used[v] = True
  for to in g[v]:
    if not used[to]: dfs1(to)
  top.append(v)

The function dfs2 performs a depth-first search on the reverse graph. All vertices visited during one call of from the main program form one strongly connected component.

def dfs2(v):
  global gr, used
  used[v] = True
  for to in gr[v]:
    if not used[to]: dfs2(to)

The main part of the program. Read the input graph. Add each edge a \to b to the graph , and the opposite edge b \to a to the reverse graph .

n, m = map(int, input().split())
g = [[] for _ in range(n + 1)]
gr = [[] for _ in range(n + 1)]
for _ in range(m):
  a, b = map(int, input().split())
  g[a].append(b)
  gr[b].append(a)

Start a depth-first search on the input graph from every vertex that has not been visited yet. After that, the list contains all vertices of the graph in the order in which their processing was finished.

used = [False] * (n + 1)
top = []
for i in range(1, n + 1):
  if not used[i]: dfs1(i)

Before the second traversal, reset the list . The vertices of the reverse graph must be considered in the reverse order of finishing their processing, so reverse the list .

used = [False] * (n + 1)
top.reverse()

The variable counts the number of strongly connected components.

c = 0

Move through the list from left to right. From each vertex that has not been visited yet, start the depth-first search dfs2 on the reverse graph. It traverses exactly one new strongly connected component, so increase by .

for v in top:
  if not used[v]:
    dfs2(v)
    c += 1

Print the number of strongly connected components of the graph.

print(c)