Count Strongly Connected Components

Last Updated : 18 Oct, 2026

Given a directed graph with V vertices labeled from 0 to V - 1, represented by an adjacency list adj, find the number of strongly connected components (SCCs) in the graph. A strongly connected component is a maximal set of vertices such that every vertex is reachable from every other vertex in the same component.

It has applications in various fields such as social network analysis, web crawling, and network routing.

Examples:

Input: adj[][] = [[1], [2], [0, 3], [4], [3, 5], []]
Output: 3
Explanation: The strongly connected components are: {0, 1, 2},{3, 4} and {5}. Therefore, the total number of SCCs is 3.

blobid0_1777620098

Input: adj[][] = [[1], [2], [0]]
Output: 1
Explanation: All of the nodes are connected to each other. So, there's only one SCC.

blobid2_1745301113
Try It Yourself
redirect icon

What Is a Strongly Connected Component?

A Strongly Connected Component of a directed graph is a maximal subset of vertices such that every vertex in the subset is reachable from every other vertex in that same subset, following the direction of the edges.

In simpler terms, for any two vertices u and v in the same SCC, there must be a directed path from u to v and another directed path from v to u.

  • A single isolated vertex is considered an SCC because a vertex can reach itself using a path of length 0.
  • SCCs partition the graph completely - every vertex belongs to exactly one SCC.
  • An SCC is maximal, meaning no additional vertex can be added to it while maintaining the property that every vertex can reach every other vertex.
  • If you "contract" each SCC into a single node, the resulting graph (called the condensation graph) is always a Directed Acyclic Graph (DAG).

This last property is the key insight nearly every SCC algorithm exploits.

1
Strongly Connected Components

[Naive Approach] Reachability - O(V × (V + E)) Time and O(V² + V + E) Space

The main idea is to find all vertices reachable from every vertex. Two vertices u and v belong to the same SCC if u can reach v and v can reach u.

To implement this:

  • Run BFS or DFS from every vertex and store the vertices reachable from it.
  • For every pair of vertices u and v, check whether they can reach each other.
  • If both are reachable from each other, merge them using Disjoint Set Union (DSU).
  • Count the number of distinct DSU sets to get the number of SCCs.

Consider: adj[][] = [ [1], [2], [0,3], [4], [] ]

file

Step 1: Find Reachable Vertices

  • From 0: {0, 1, 2, 3, 4}
  • From 1: {0, 1, 2, 3, 4}
  • From 2: {0, 1, 2, 3, 4}
  • From 3: {3, 4}
  • From 4: {4}

Step 2: Check Mutual Reachability

  • 0 and 1: 0 reaches 1, and 1 reaches 0 (via 1->2->0) -> mutual, group them.
  • 0 and 2: 0 reaches 2, and 2 reaches 0 directly -> mutual, group them.
  • 1 and 2: already in the same group.
  • 0/1/2 and 3: they reach 3, but 3 can't reach back -> not mutual.
  • 0/1/2 and 4, 3 and 4: same issue, one-way only -> not mutual.

Step 3: Final Groups

  • {0, 1, 2} - These vertices are mutually reachable through the cycle 0 -> 1 -> 2 -> 0, so they form one SCC.
  • {3} - Vertex 3 can be reached from the first SCC, but there is no path from 3 back to those vertices. Therefore, 3 forms its own SCC.
  • {4} - Vertex 4 has no path back to any previous vertex, so it forms its own SCC.

Result: Number of SCCs = 3

C++
#include <bits/stdc++.h>
using namespace std;

// Find root of the set containing x (with path compression)
int find(int x, vector<int> &parent) {
    if (parent[x] != x)
        parent[x] = find(parent[x], parent);
    return parent[x];
}

// Union by rank
void unite(int x, int y, vector<int> &parent, vector<int> &rnk) {
    int px = find(x, parent), py = find(y, parent);

    if (px == py) return;

    if (rnk[px] < rnk[py])
        swap(px, py);

    parent[py] = px;

    if (rnk[px] == rnk[py])
        rnk[px]++;
}

// BFS to find all vertices reachable from src
vector<bool> bfsReach(int src, vector<vector<int>> &adj) {
    int n = adj.size();
    vector<bool> visited(n, false);
    queue<int> q;

    visited[src] = true;
    q.push(src);

    while (!q.empty()) {
        int u = q.front();
        q.pop();

        for (int v : adj[u]) {
            if (!visited[v]) {
                visited[v] = true;
                q.push(v);
            }
        }
    }

    return visited;
}

int countSCC(vector<vector<int>> &adj) {
    int n = adj.size();

    // Compute reachability set for every vertex
    vector<vector<bool>> reach(n);

    for (int i = 0; i < n; i++)
        reach[i] = bfsReach(i, adj);

    // Initialize DSU
    vector<int> parent(n), rnk(n, 0);

    for (int i = 0; i < n; i++)
        parent[i] = i;

    // Union vertices that can reach each other
    for (int u = 0; u < n; u++) {
        for (int v = u + 1; v < n; v++) {
            if (reach[u][v] && reach[v][u])
                unite(u, v, parent, rnk);
        }
    }

    // Count distinct sets (= number of SCCs)
    unordered_set<int> roots;

    for (int i = 0; i < n; i++)
        roots.insert(find(i, parent));

    return roots.size();
}

int main() {
    // Directed Graph:
    //     0 ---> 1
    //     ^      |
    //     |      v
    //     +----- 2 ---> 3 ---> 4

    vector<vector<int>> adj = {
        {1},
        {2},
        {0, 3},
        {4},
        {}
    };

    cout << countSCC(adj) << endl;

    return 0;
}
Java
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Queue;
import java.util.LinkedList;

// Find root of the set containing x (with path compression)
class GFG {

    static int find(int x, int[] parent) {
        if (parent[x] != x)
            parent[x] = find(parent[x], parent);
        return parent[x];
    }

    // Union by rank
    static void unite(int x, int y, int[] parent, int[] rnk) {
        int px = find(x, parent), py = find(y, parent);

        if (px == py)
            return;

        if (rnk[px] < rnk[py]) {
            int temp = px;
            px = py;
            py = temp;
        }

        parent[py] = px;

        if (rnk[px] == rnk[py])
            rnk[px]++;
    }

    // BFS to find all vertices reachable from src
    static boolean[] bfsReach(int src, ArrayList<ArrayList<Integer>> adj) {
        int n = adj.size();
        boolean[] visited = new boolean[n];
        Queue<Integer> q = new LinkedList<>();

        visited[src] = true;
        q.add(src);

        while (!q.isEmpty()) {
            int u = q.poll();

            for (int v : adj.get(u)) {
                if (!visited[v]) {
                    visited[v] = true;
                    q.add(v);
                }
            }
        }

        return visited;
    }

    static int countSCC(ArrayList<ArrayList<Integer>> adj) {
        int n = adj.size();

        // Compute reachability set for every vertex
        boolean[][] reach = new boolean[n][];

        for (int i = 0; i < n; i++)
            reach[i] = bfsReach(i, adj);

        // Initialize DSU
        int[] parent = new int[n];
        int[] rnk = new int[n];

        for (int i = 0; i < n; i++)
            parent[i] = i;

        // Union vertices that reach each other both ways
        for (int u = 0; u < n; u++) {
            for (int v = u + 1; v < n; v++) {
                if (reach[u][v] && reach[v][u])
                    unite(u, v, parent, rnk);
            }
        }

        // Count distinct sets (= number of SCCs)
        HashSet<Integer> roots = new HashSet<>();

        for (int i = 0; i < n; i++)
            roots.add(find(i, parent));

        return roots.size();
    }

    public static void main(String[] args) {
        // Directed Graph:
        //     0 ---> 1
        //     ^      |
        //     |      v
        //     +----- 2 ---> 3 ---> 4

        ArrayList<ArrayList<Integer>> adj = new ArrayList<>();

        adj.add(new ArrayList<>(java.util.Arrays.asList(1)));
        adj.add(new ArrayList<>(java.util.Arrays.asList(2)));
        adj.add(new ArrayList<>(java.util.Arrays.asList(0, 3)));
        adj.add(new ArrayList<>(java.util.Arrays.asList(4)));
        adj.add(new ArrayList<>());

        System.out.println(countSCC(adj));
    }
}
Python
from collections import deque

# Find root of the set containing x (with path compression)
def find(x, parent):
    if parent[x] != x:
        parent[x] = find(parent[x], parent)
    return parent[x]


# Union by rank
def unite(x, y, parent, rnk):
    px = find(x, parent)
    py = find(y, parent)

    if px == py:
        return

    if rnk[px] < rnk[py]:
        px, py = py, px

    parent[py] = px

    if rnk[px] == rnk[py]:
        rnk[px] += 1


# BFS to find all vertices reachable from src
def bfsReach(src, adj):
    n = len(adj)
    visited = [False] * n
    q = deque()

    visited[src] = True
    q.append(src)

    while q:
        u = q.popleft()

        for v in adj[u]:
            if not visited[v]:
                visited[v] = True
                q.append(v)

    return visited


def countSCC(adj):
    n = len(adj)

    # Compute reachability set for every vertex
    reach = [None] * n

    for i in range(n):
        reach[i] = bfsReach(i, adj)

    # Initialize DSU
    parent = list(range(n))
    rnk = [0] * n

    # Union vertices that reach each other both ways
    for u in range(n):
        for v in range(u + 1, n):
            if reach[u][v] and reach[v][u]:
                unite(u, v, parent, rnk)

    # Count distinct sets (= number of SCCs)
    roots = set()

    for i in range(n):
        roots.add(find(i, parent))

    return len(roots)


if __name__ == "__main__":
    # Directed Graph:
    #     0 ---> 1
    #     ^      |
    #     |      v
    #     +----- 2 ---> 3 ---> 4
 
    adj = [
        [1],
        [2],
        [0, 3],
        [4],
        []
    ]

    print(countSCC(adj))
C#
using System;
using System.Collections.Generic;

// Find root of the set containing x (with path compression)
class GFG
{
    static int find(int x, int[] parent)
    {
        if (parent[x] != x)
            parent[x] = find(parent[x], parent);

        return parent[x];
    }

    // Union by rank
    static void unite(int x, int y, int[] parent, int[] rnk)
    {
        int px = find(x, parent);
        int py = find(y, parent);

        if (px == py)
            return;

        if (rnk[px] < rnk[py])
        {
            int temp = px;
            px = py;
            py = temp;
        }

        parent[py] = px;

        if (rnk[px] == rnk[py])
            rnk[px]++;
    }

    // BFS to find all vertices reachable from src
    static bool[] bfsReach(int src, List<List<int>> adj)
    {
        int n = adj.Count;
        bool[] visited = new bool[n];
        Queue<int> q = new Queue<int>();

        visited[src] = true;
        q.Enqueue(src);

        while (q.Count > 0)
        {
            int u = q.Dequeue();

            foreach (int v in adj[u])
            {
                if (!visited[v])
                {
                    visited[v] = true;
                    q.Enqueue(v);
                }
            }
        }

        return visited;
    }

    static int countSCC(List<List<int>> adj)
    {
        int n = adj.Count;

        // Compute reachability set for every vertex
        bool[][] reach = new bool[n][];

        for (int i = 0; i < n; i++)
            reach[i] = bfsReach(i, adj);

        // Initialize DSU
        int[] parent = new int[n];
        int[] rnk = new int[n];

        for (int i = 0; i < n; i++)
            parent[i] = i;

        // Union vertices that reach each other both ways
        for (int u = 0; u < n; u++)
        {
            for (int v = u + 1; v < n; v++)
            {
                if (reach[u][v] && reach[v][u])
                    unite(u, v, parent, rnk);
            }
        }

        // Count distinct sets (= number of SCCs)
        HashSet<int> roots = new HashSet<int>();

        for (int i = 0; i < n; i++)
            roots.Add(find(i, parent));

        return roots.Count;
    }

    static void Main()
    {
        // Directed Graph:
        //     0 ---> 1
        //     ^      |
        //     |      v
        //     +----- 2 ---> 3 ---> 4

        List<List<int>> adj = new List<List<int>>
        {
            new List<int> { 1 },
            new List<int> { 2 },
            new List<int> { 0, 3 },
            new List<int> { 4 },
            new List<int>()
        };

        Console.WriteLine(countSCC(adj));
    }
}
JavaScript
// Find root of the set containing x (with path compression)
function find(x, parent) {
    if (parent[x] !== x)
        parent[x] = find(parent[x], parent);
    return parent[x];
}

// Union by rank
function unite(x, y, parent, rnk) {
    let px = find(x, parent);
    let py = find(y, parent);

    if (px === py)
        return;

    if (rnk[px] < rnk[py]) {
        [px, py] = [py, px];
    }

    parent[py] = px;

    if (rnk[px] === rnk[py])
        rnk[px]++;
}

// BFS to find all vertices reachable from src
function bfsReach(src, adj) {
    let n = adj.length;
    let visited = new Array(n).fill(false);
    let q = [];
    let front = 0;

    visited[src] = true;
    q.push(src);

    while (front < q.length) {
        let u = q[front++];

        for (let v of adj[u]) {
            if (!visited[v]) {
                visited[v] = true;
                q.push(v);
            }
        }
    }

    return visited;
}

function countSCC(adj) {
    let n = adj.length;

    // Compute reachability set for every vertex
    let reach = new Array(n);

    for (let i = 0; i < n; i++)
        reach[i] = bfsReach(i, adj);

    // Initialize DSU
    let parent = new Array(n);
    let rnk = new Array(n).fill(0);

    for (let i = 0; i < n; i++)
        parent[i] = i;

    // Union vertices that reach each other both ways
    for (let u = 0; u < n; u++) {
        for (let v = u + 1; v < n; v++) {
            if (reach[u][v] && reach[v][u])
                unite(u, v, parent, rnk);
        }
    }

    // Count distinct sets (= number of SCCs)
    let roots = new Set();

    for (let i = 0; i < n; i++)
        roots.add(find(i, parent));

    return roots.size;
}

// Driver code

// Directed Graph:
//     0 ---> 1
//     ^      |
//     |      v
//     +----- 2 ---> 3 ---> 4

let adj = [
    [1],
    [2],
    [0, 3],
    [4],
    []
];

console.log(countSCC(adj));

Output
3

[Expected Approach 1] Kosaraju's Algorithm - O(V + E) Time and O(V + E) Space

Kosaraju's Algorithm finds the Strongly Connected Components (SCCs) of a directed graph using two DFS traversals and the transpose of the graph.

The algorithm works in three main steps:

Step 1: Store Vertices by Finishing Time

  • Perform DFS on the original graph.
  • Whenever DFS finishes processing a vertex, push that vertex into a stack.
  • In other words, a vertex is added to the stack after all its reachable unvisited vertices have been processed.
  • This gives us the vertices in decreasing order of their DFS finishing times when they are popped from the stack.

Step 2: Reverse (transpose) the graph

  • Create the transpose graph by reversing every directed edge.
  • For every edge: u -> v add the reverse edge: v -> u

Step 3: DFS in stack order on the reversed graph

  • Pop vertices from the stack one at a time.
  • If a vertex hasn't been visited yet, do a DFS from it on the transposed graph.
  • Every vertex reached in this DFS belongs to one SCC.
  • Each time you start a fresh DFS here, increment the SCC count.
C++
#include <bits/stdc++.h>
using namespace std;

// Store vertices according to their DFS finishing time
void fillOrder(int u, vector<vector<int>> &adj, vector<bool> &visited,
               stack<int> &st) {
    visited[u] = true;

    for (int v : adj[u]) {
        if (!visited[v])
            fillOrder(v, adj, visited, st);
    }

    // Push after visiting all adjacent vertices
    st.push(u);
}

// DFS on the transpose graph to find one SCC
void dfsTranspose(int u, vector<vector<int>> &transpose,
                  vector<bool> &visited) {
    visited[u] = true;

    for (int v : transpose[u]) {
        if (!visited[v])
            dfsTranspose(v, transpose, visited);
    }
}

int countSCC(vector<vector<int>> &adj) {
    int n = adj.size();
    vector<vector<int>> transpose(n);

    // Build the transpose graph
    for (int u = 0; u < n; u++) {
        for (int v : adj[u])
            transpose[v].push_back(u);
    }

    stack<int> st;
    vector<bool> visited(n, false);

    // First DFS: store vertices by finishing time
    for (int i = 0; i < n; i++) {
        if (!visited[i])
            fillOrder(i, adj, visited, st);
    }

    // Reset visited for the second DFS
    fill(visited.begin(), visited.end(), false);

    int sccCount = 0;

    // Process vertices in decreasing finishing order
    while (!st.empty()) {
        int u = st.top();
        st.pop();

        if (!visited[u]) {
            // Each DFS gives one strongly connected component
            dfsTranspose(u, transpose, visited);
            sccCount++;
        }
    }

    return sccCount;
}

int main() {
    
    // Directed Graph:
    //     0 ---> 1
    //     ^      |
    //     |      v
    //     +----- 2 ---> 3 ---> 4
    vector<vector<int>> adj = {
        {1},
        {2},
        {0, 3},
        {4},
        {}
    };

    cout << countSCC(adj) << endl;

    return 0;
}
Java
import java.util.ArrayList;
import java.util.Stack;
import java.util.Arrays;

class GFG {

    // Store vertices according to their DFS finishing time
    static void fillOrder(int u, ArrayList<ArrayList<Integer>> adj,
                          boolean[] visited, Stack<Integer> st) {
        visited[u] = true;

        for (int v : adj.get(u)) {
            if (!visited[v])
                fillOrder(v, adj, visited, st);
        }

        // Push after visiting all adjacent vertices
        st.push(u);
    }

    // DFS on the transpose graph to find one SCC
    static void dfsTranspose(int u, ArrayList<ArrayList<Integer>> transpose,
                             boolean[] visited) {
        visited[u] = true;

        for (int v : transpose.get(u)) {
            if (!visited[v])
                dfsTranspose(v, transpose, visited);
        }
    }

    static int countSCC(ArrayList<ArrayList<Integer>> adj) {
        int n = adj.size();
        ArrayList<ArrayList<Integer>> transpose = new ArrayList<>();

        for (int i = 0; i < n; i++)
            transpose.add(new ArrayList<>());

        // Build the transpose graph
        for (int u = 0; u < n; u++) {
            for (int v : adj.get(u))
                transpose.get(v).add(u);
        }

        Stack<Integer> st = new Stack<>();
        boolean[] visited = new boolean[n];

        // First DFS: store vertices by finishing time
        for (int i = 0; i < n; i++) {
            if (!visited[i])
                fillOrder(i, adj, visited, st);
        }

        // Reset visited for the second DFS
        Arrays.fill(visited, false);

        int sccCount = 0;

        // Process vertices in decreasing finishing order
        while (!st.empty()) {
            int u = st.pop();

            if (!visited[u]) {
                // Each DFS gives one strongly connected component
                dfsTranspose(u, transpose, visited);
                sccCount++;
            }
        }

        return sccCount;
    }

    public static void main(String[] args) {
        // Directed Graph:
        //     0 ---> 1
        //     ^      |
        //     |      v
        //     +----- 2 ---> 3 ---> 4
        
        ArrayList<ArrayList<Integer>> adj = new ArrayList<>();

        adj.add(new ArrayList<>(Arrays.asList(1)));
        adj.add(new ArrayList<>(Arrays.asList(2)));
        adj.add(new ArrayList<>(Arrays.asList(0, 3)));
        adj.add(new ArrayList<>(Arrays.asList(4)));
        adj.add(new ArrayList<>());

        System.out.println(countSCC(adj));
    }
}
Python
# Store vertices according to their DFS finishing time
def fillOrder(u, adj, visited, st):
    visited[u] = True

    for v in adj[u]:
        if not visited[v]:
            fillOrder(v, adj, visited, st)

    # Push after visiting all adjacent vertices
    st.append(u)


# DFS on the transpose graph to find one SCC
def dfsTranspose(u, transpose, visited):
    visited[u] = True

    for v in transpose[u]:
        if not visited[v]:
            dfsTranspose(v, transpose, visited)


def countSCC(adj):
    n = len(adj)
    transpose = [[] for _ in range(n)]

    # Build the transpose graph
    for u in range(n):
        for v in adj[u]:
            transpose[v].append(u)

    st = []
    visited = [False] * n

    # First DFS: store vertices by finishing time
    for i in range(n):
        if not visited[i]:
            fillOrder(i, adj, visited, st)

    # Reset visited for the second DFS
    visited = [False] * n

    sccCount = 0

    # Process vertices in decreasing finishing order
    while st:
        u = st.pop()

        if not visited[u]:
            # Each DFS gives one strongly connected component
            dfsTranspose(u, transpose, visited)
            sccCount += 1

    return sccCount


if __name__ == "__main__":
    
    # Directed Graph:
    #     0 ---> 1
    #     ^      |
    #     |      v
    #     +----- 2 ---> 3 ---> 4
    adj = [
        [1],
        [2],
        [0, 3],
        [4],
        []
    ]

    print(countSCC(adj))
C#
using System;
using System.Collections.Generic;

class GFG
{
    // Store vertices according to their DFS finishing time
    static void fillOrder(int u, List<List<int>> adj,
                          bool[] visited, Stack<int> st)
    {
        visited[u] = true;

        foreach (int v in adj[u])
        {
            if (!visited[v])
                fillOrder(v, adj, visited, st);
        }

        // Push after visiting all adjacent vertices
        st.Push(u);
    }

    // DFS on the transpose graph to find one SCC
    static void dfsTranspose(int u, List<List<int>> transpose,
                             bool[] visited)
    {
        visited[u] = true;

        foreach (int v in transpose[u])
        {
            if (!visited[v])
                dfsTranspose(v, transpose, visited);
        }
    }

    static int countSCC(List<List<int>> adj)
    {
        int n = adj.Count;
        List<List<int>> transpose = new List<List<int>>();

        for (int i = 0; i < n; i++)
            transpose.Add(new List<int>());

        // Build the transpose graph
        for (int u = 0; u < n; u++)
        {
            foreach (int v in adj[u])
                transpose[v].Add(u);
        }

        Stack<int> st = new Stack<int>();
        bool[] visited = new bool[n];

        // First DFS: store vertices by finishing time
        for (int i = 0; i < n; i++)
        {
            if (!visited[i])
                fillOrder(i, adj, visited, st);
        }

        // Reset visited for the second DFS
        Array.Fill(visited, false);

        int sccCount = 0;

        // Process vertices in decreasing finishing order
        while (st.Count > 0)
        {
            int u = st.Pop();

            if (!visited[u])
            {
                // Each DFS gives one strongly connected component
                dfsTranspose(u, transpose, visited);
                sccCount++;
            }
        }

        return sccCount;
    }

    static void Main()
    {
        // Directed Graph:
        //     0 ---> 1
        //     ^      |
        //     |      v
        //     +----- 2 ---> 3 ---> 4

        List<List<int>> adj = new List<List<int>>
        {
            new List<int> { 1 },
            new List<int> { 2 },
            new List<int> { 0, 3 },
            new List<int> { 4 },
            new List<int>()
        };

        Console.WriteLine(countSCC(adj));
    }
}
JavaScript
// Store vertices according to their DFS finishing time
function fillOrder(u, adj, visited, st) {
    visited[u] = true;

    for (let v of adj[u]) {
        if (!visited[v])
            fillOrder(v, adj, visited, st);
    }

    // Push after visiting all adjacent vertices
    st.push(u);
}

// DFS on the transpose graph to find one SCC
function dfsTranspose(u, transpose, visited) {
    visited[u] = true;

    for (let v of transpose[u]) {
        if (!visited[v])
            dfsTranspose(v, transpose, visited);
    }
}

function countSCC(adj) {
    let n = adj.length;
    let transpose = Array.from({ length: n }, () => []);

    // Build the transpose graph
    for (let u = 0; u < n; u++) {
        for (let v of adj[u])
            transpose[v].push(u);
    }

    let st = [];
    let visited = new Array(n).fill(false);

    // First DFS: store vertices by finishing time
    for (let i = 0; i < n; i++) {
        if (!visited[i])
            fillOrder(i, adj, visited, st);
    }

    // Reset visited for the second DFS
    visited.fill(false);

    let sccCount = 0;

    // Process vertices in decreasing finishing order
    while (st.length > 0) {
        let u = st.pop();

        if (!visited[u]) {
            // Each DFS gives one strongly connected component
            dfsTranspose(u, transpose, visited);
            sccCount++;
        }
    }

    return sccCount;
}

// Driver code

    // Directed Graph:
    //     0 ---> 1
    //     ^      |
    //     |      v
    //     +----- 2 ---> 3 ---> 4

    let adj = [
        [1],
        [2],
        [0, 3],
        [4],
        []
    ];

    console.log(countSCC(adj));

Output
3

How does this work?

  • DFS of a graph produces a single tree if all vertices are reachable from the DFS starting point. Otherwise DFS produces a forest. So DFS of a graph with only one SCC always produces a tree.
  • The important point to note is DFS may produce a tree or a forest when there are more than one SCCs depending upon the chosen starting point. For example, in the below diagram, if we start DFS from vertices 0 or 1 or 2, we get a tree as output. And if we start from 3 or 4, we get a forest. To find and print all SCCs, we would want to start DFS from vertex 4 (which is a sink vertex), then move to 3 which is sink in the remaining set (set excluding 4) and finally any of the remaining vertices (0, 1, 2).
4
  • How do we find the right sequence of picking vertices as starting points of DFS? Unfortunately, there is no direct way for getting this sequence. However, if we do a DFS of graph and store vertices according to their finish times, we make sure that the finish time of a vertex that connects to other SCCs (other that its own SCC), will always be greater than finish time of vertices in the other SCC. For example, in DFS of above example graph, finish time of 0 is always greater than 3 and 4 (irrespective of the sequence of vertices considered for DFS). And finish time of 3 is always greater than 4. DFS doesn't guarantee about other vertices, for example finish times of 1 and 2 may be smaller or greater than 3 and 4 depending upon the sequence of vertices considered for DFS. So to use this property, we do DFS traversal of complete graph and push every finished vertex to a stack. In stack, 3 always appears after 4, and 0 appear after both 3 and 4. In the next step, we reverse the graph.
  • Consider the below graph of SCCs. In the reversed graph, the edges that connect two components are reversed. So the SCC {0, 1, 2} becomes sink and the SCC {4} becomes source. As discussed above, in stack, we always have 0 before 3 and 4. So if we do a DFS of the reversed graph using sequence of vertices in stack, we process vertices from sink to source (in reversed graph). That is what we wanted to achieve and that is all needed to print SCCs one by one.
5

[Expected Approach 2] Tarjan's Algorithm - O(V + E) Time and O(V + E) Space

Tarjan's Algorithm is more efficient because it finds SCCs in a single DFS pass using a stack and some additional bookkeeping:

  • DFS Traversal: During the DFS, maintain an index for each node and the smallest index (low-link value) that can be reached from the node.
  • Stack: Keep track of nodes currently in the recursion stack (part of the current SCC being explored).
  • Identifying SCCs: When a node’s low-link value equals its index, it means we have found an SCC. Pop all nodes from the stack until we reach the current node.

Please refer Tarjan's Algorithm for more details

Comment