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.
Input: adj[][] = [[1], [2], [0]] Output: 1 Explanation: All of the nodes are connected to each other. So, there's only one SCC.
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.
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], [] ]
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>usingnamespacestd;// Find root of the set containing x (with path compression)intfind(intx,vector<int>&parent){if(parent[x]!=x)parent[x]=find(parent[x],parent);returnparent[x];}// Union by rankvoidunite(intx,inty,vector<int>&parent,vector<int>&rnk){intpx=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 srcvector<bool>bfsReach(intsrc,vector<vector<int>>&adj){intn=adj.size();vector<bool>visited(n,false);queue<int>q;visited[src]=true;q.push(src);while(!q.empty()){intu=q.front();q.pop();for(intv:adj[u]){if(!visited[v]){visited[v]=true;q.push(v);}}}returnvisited;}intcountSCC(vector<vector<int>>&adj){intn=adj.size();// Compute reachability set for every vertexvector<vector<bool>>reach(n);for(inti=0;i<n;i++)reach[i]=bfsReach(i,adj);// Initialize DSUvector<int>parent(n),rnk(n,0);for(inti=0;i<n;i++)parent[i]=i;// Union vertices that can reach each otherfor(intu=0;u<n;u++){for(intv=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(inti=0;i<n;i++)roots.insert(find(i,parent));returnroots.size();}intmain(){// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4vector<vector<int>>adj={{1},{2},{0,3},{4},{}};cout<<countSCC(adj)<<endl;return0;}
Java
importjava.util.ArrayList;importjava.util.HashSet;importjava.util.Queue;importjava.util.LinkedList;// Find root of the set containing x (with path compression)classGFG{staticintfind(intx,int[]parent){if(parent[x]!=x)parent[x]=find(parent[x],parent);returnparent[x];}// Union by rankstaticvoidunite(intx,inty,int[]parent,int[]rnk){intpx=find(x,parent),py=find(y,parent);if(px==py)return;if(rnk[px]<rnk[py]){inttemp=px;px=py;py=temp;}parent[py]=px;if(rnk[px]==rnk[py])rnk[px]++;}// BFS to find all vertices reachable from srcstaticboolean[]bfsReach(intsrc,ArrayList<ArrayList<Integer>>adj){intn=adj.size();boolean[]visited=newboolean[n];Queue<Integer>q=newLinkedList<>();visited[src]=true;q.add(src);while(!q.isEmpty()){intu=q.poll();for(intv:adj.get(u)){if(!visited[v]){visited[v]=true;q.add(v);}}}returnvisited;}staticintcountSCC(ArrayList<ArrayList<Integer>>adj){intn=adj.size();// Compute reachability set for every vertexboolean[][]reach=newboolean[n][];for(inti=0;i<n;i++)reach[i]=bfsReach(i,adj);// Initialize DSUint[]parent=newint[n];int[]rnk=newint[n];for(inti=0;i<n;i++)parent[i]=i;// Union vertices that reach each other both waysfor(intu=0;u<n;u++){for(intv=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=newHashSet<>();for(inti=0;i<n;i++)roots.add(find(i,parent));returnroots.size();}publicstaticvoidmain(String[]args){// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4ArrayList<ArrayList<Integer>>adj=newArrayList<>();adj.add(newArrayList<>(java.util.Arrays.asList(1)));adj.add(newArrayList<>(java.util.Arrays.asList(2)));adj.add(newArrayList<>(java.util.Arrays.asList(0,3)));adj.add(newArrayList<>(java.util.Arrays.asList(4)));adj.add(newArrayList<>());System.out.println(countSCC(adj));}}
Python
fromcollectionsimportdeque# Find root of the set containing x (with path compression)deffind(x,parent):ifparent[x]!=x:parent[x]=find(parent[x],parent)returnparent[x]# Union by rankdefunite(x,y,parent,rnk):px=find(x,parent)py=find(y,parent)ifpx==py:returnifrnk[px]<rnk[py]:px,py=py,pxparent[py]=pxifrnk[px]==rnk[py]:rnk[px]+=1# BFS to find all vertices reachable from srcdefbfsReach(src,adj):n=len(adj)visited=[False]*nq=deque()visited[src]=Trueq.append(src)whileq:u=q.popleft()forvinadj[u]:ifnotvisited[v]:visited[v]=Trueq.append(v)returnvisiteddefcountSCC(adj):n=len(adj)# Compute reachability set for every vertexreach=[None]*nforiinrange(n):reach[i]=bfsReach(i,adj)# Initialize DSUparent=list(range(n))rnk=[0]*n# Union vertices that reach each other both waysforuinrange(n):forvinrange(u+1,n):ifreach[u][v]andreach[v][u]:unite(u,v,parent,rnk)# Count distinct sets (= number of SCCs)roots=set()foriinrange(n):roots.add(find(i,parent))returnlen(roots)if__name__=="__main__":# Directed Graph:# 0 ---> 1# ^ |# | v# +----- 2 ---> 3 ---> 4adj=[[1],[2],[0,3],[4],[]]print(countSCC(adj))
C#
usingSystem;usingSystem.Collections.Generic;// Find root of the set containing x (with path compression)classGFG{staticintfind(intx,int[]parent){if(parent[x]!=x)parent[x]=find(parent[x],parent);returnparent[x];}// Union by rankstaticvoidunite(intx,inty,int[]parent,int[]rnk){intpx=find(x,parent);intpy=find(y,parent);if(px==py)return;if(rnk[px]<rnk[py]){inttemp=px;px=py;py=temp;}parent[py]=px;if(rnk[px]==rnk[py])rnk[px]++;}// BFS to find all vertices reachable from srcstaticbool[]bfsReach(intsrc,List<List<int>>adj){intn=adj.Count;bool[]visited=newbool[n];Queue<int>q=newQueue<int>();visited[src]=true;q.Enqueue(src);while(q.Count>0){intu=q.Dequeue();foreach(intvinadj[u]){if(!visited[v]){visited[v]=true;q.Enqueue(v);}}}returnvisited;}staticintcountSCC(List<List<int>>adj){intn=adj.Count;// Compute reachability set for every vertexbool[][]reach=newbool[n][];for(inti=0;i<n;i++)reach[i]=bfsReach(i,adj);// Initialize DSUint[]parent=newint[n];int[]rnk=newint[n];for(inti=0;i<n;i++)parent[i]=i;// Union vertices that reach each other both waysfor(intu=0;u<n;u++){for(intv=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=newHashSet<int>();for(inti=0;i<n;i++)roots.Add(find(i,parent));returnroots.Count;}staticvoidMain(){// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4List<List<int>>adj=newList<List<int>>{newList<int>{1},newList<int>{2},newList<int>{0,3},newList<int>{4},newList<int>()};Console.WriteLine(countSCC(adj));}}
JavaScript
// Find root of the set containing x (with path compression)functionfind(x,parent){if(parent[x]!==x)parent[x]=find(parent[x],parent);returnparent[x];}// Union by rankfunctionunite(x,y,parent,rnk){letpx=find(x,parent);letpy=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 srcfunctionbfsReach(src,adj){letn=adj.length;letvisited=newArray(n).fill(false);letq=[];letfront=0;visited[src]=true;q.push(src);while(front<q.length){letu=q[front++];for(letvofadj[u]){if(!visited[v]){visited[v]=true;q.push(v);}}}returnvisited;}functioncountSCC(adj){letn=adj.length;// Compute reachability set for every vertexletreach=newArray(n);for(leti=0;i<n;i++)reach[i]=bfsReach(i,adj);// Initialize DSUletparent=newArray(n);letrnk=newArray(n).fill(0);for(leti=0;i<n;i++)parent[i]=i;// Union vertices that reach each other both waysfor(letu=0;u<n;u++){for(letv=u+1;v<n;v++){if(reach[u][v]&&reach[v][u])unite(u,v,parent,rnk);}}// Count distinct sets (= number of SCCs)letroots=newSet();for(leti=0;i<n;i++)roots.add(find(i,parent));returnroots.size;}// Driver code// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4letadj=[[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>usingnamespacestd;// Store vertices according to their DFS finishing timevoidfillOrder(intu,vector<vector<int>>&adj,vector<bool>&visited,stack<int>&st){visited[u]=true;for(intv:adj[u]){if(!visited[v])fillOrder(v,adj,visited,st);}// Push after visiting all adjacent verticesst.push(u);}// DFS on the transpose graph to find one SCCvoiddfsTranspose(intu,vector<vector<int>>&transpose,vector<bool>&visited){visited[u]=true;for(intv:transpose[u]){if(!visited[v])dfsTranspose(v,transpose,visited);}}intcountSCC(vector<vector<int>>&adj){intn=adj.size();vector<vector<int>>transpose(n);// Build the transpose graphfor(intu=0;u<n;u++){for(intv:adj[u])transpose[v].push_back(u);}stack<int>st;vector<bool>visited(n,false);// First DFS: store vertices by finishing timefor(inti=0;i<n;i++){if(!visited[i])fillOrder(i,adj,visited,st);}// Reset visited for the second DFSfill(visited.begin(),visited.end(),false);intsccCount=0;// Process vertices in decreasing finishing orderwhile(!st.empty()){intu=st.top();st.pop();if(!visited[u]){// Each DFS gives one strongly connected componentdfsTranspose(u,transpose,visited);sccCount++;}}returnsccCount;}intmain(){// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4vector<vector<int>>adj={{1},{2},{0,3},{4},{}};cout<<countSCC(adj)<<endl;return0;}
Java
importjava.util.ArrayList;importjava.util.Stack;importjava.util.Arrays;classGFG{// Store vertices according to their DFS finishing timestaticvoidfillOrder(intu,ArrayList<ArrayList<Integer>>adj,boolean[]visited,Stack<Integer>st){visited[u]=true;for(intv:adj.get(u)){if(!visited[v])fillOrder(v,adj,visited,st);}// Push after visiting all adjacent verticesst.push(u);}// DFS on the transpose graph to find one SCCstaticvoiddfsTranspose(intu,ArrayList<ArrayList<Integer>>transpose,boolean[]visited){visited[u]=true;for(intv:transpose.get(u)){if(!visited[v])dfsTranspose(v,transpose,visited);}}staticintcountSCC(ArrayList<ArrayList<Integer>>adj){intn=adj.size();ArrayList<ArrayList<Integer>>transpose=newArrayList<>();for(inti=0;i<n;i++)transpose.add(newArrayList<>());// Build the transpose graphfor(intu=0;u<n;u++){for(intv:adj.get(u))transpose.get(v).add(u);}Stack<Integer>st=newStack<>();boolean[]visited=newboolean[n];// First DFS: store vertices by finishing timefor(inti=0;i<n;i++){if(!visited[i])fillOrder(i,adj,visited,st);}// Reset visited for the second DFSArrays.fill(visited,false);intsccCount=0;// Process vertices in decreasing finishing orderwhile(!st.empty()){intu=st.pop();if(!visited[u]){// Each DFS gives one strongly connected componentdfsTranspose(u,transpose,visited);sccCount++;}}returnsccCount;}publicstaticvoidmain(String[]args){// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4ArrayList<ArrayList<Integer>>adj=newArrayList<>();adj.add(newArrayList<>(Arrays.asList(1)));adj.add(newArrayList<>(Arrays.asList(2)));adj.add(newArrayList<>(Arrays.asList(0,3)));adj.add(newArrayList<>(Arrays.asList(4)));adj.add(newArrayList<>());System.out.println(countSCC(adj));}}
Python
# Store vertices according to their DFS finishing timedeffillOrder(u,adj,visited,st):visited[u]=Trueforvinadj[u]:ifnotvisited[v]:fillOrder(v,adj,visited,st)# Push after visiting all adjacent verticesst.append(u)# DFS on the transpose graph to find one SCCdefdfsTranspose(u,transpose,visited):visited[u]=Trueforvintranspose[u]:ifnotvisited[v]:dfsTranspose(v,transpose,visited)defcountSCC(adj):n=len(adj)transpose=[[]for_inrange(n)]# Build the transpose graphforuinrange(n):forvinadj[u]:transpose[v].append(u)st=[]visited=[False]*n# First DFS: store vertices by finishing timeforiinrange(n):ifnotvisited[i]:fillOrder(i,adj,visited,st)# Reset visited for the second DFSvisited=[False]*nsccCount=0# Process vertices in decreasing finishing orderwhilest:u=st.pop()ifnotvisited[u]:# Each DFS gives one strongly connected componentdfsTranspose(u,transpose,visited)sccCount+=1returnsccCountif__name__=="__main__":# Directed Graph:# 0 ---> 1# ^ |# | v# +----- 2 ---> 3 ---> 4adj=[[1],[2],[0,3],[4],[]]print(countSCC(adj))
C#
usingSystem;usingSystem.Collections.Generic;classGFG{// Store vertices according to their DFS finishing timestaticvoidfillOrder(intu,List<List<int>>adj,bool[]visited,Stack<int>st){visited[u]=true;foreach(intvinadj[u]){if(!visited[v])fillOrder(v,adj,visited,st);}// Push after visiting all adjacent verticesst.Push(u);}// DFS on the transpose graph to find one SCCstaticvoiddfsTranspose(intu,List<List<int>>transpose,bool[]visited){visited[u]=true;foreach(intvintranspose[u]){if(!visited[v])dfsTranspose(v,transpose,visited);}}staticintcountSCC(List<List<int>>adj){intn=adj.Count;List<List<int>>transpose=newList<List<int>>();for(inti=0;i<n;i++)transpose.Add(newList<int>());// Build the transpose graphfor(intu=0;u<n;u++){foreach(intvinadj[u])transpose[v].Add(u);}Stack<int>st=newStack<int>();bool[]visited=newbool[n];// First DFS: store vertices by finishing timefor(inti=0;i<n;i++){if(!visited[i])fillOrder(i,adj,visited,st);}// Reset visited for the second DFSArray.Fill(visited,false);intsccCount=0;// Process vertices in decreasing finishing orderwhile(st.Count>0){intu=st.Pop();if(!visited[u]){// Each DFS gives one strongly connected componentdfsTranspose(u,transpose,visited);sccCount++;}}returnsccCount;}staticvoidMain(){// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4List<List<int>>adj=newList<List<int>>{newList<int>{1},newList<int>{2},newList<int>{0,3},newList<int>{4},newList<int>()};Console.WriteLine(countSCC(adj));}}
JavaScript
// Store vertices according to their DFS finishing timefunctionfillOrder(u,adj,visited,st){visited[u]=true;for(letvofadj[u]){if(!visited[v])fillOrder(v,adj,visited,st);}// Push after visiting all adjacent verticesst.push(u);}// DFS on the transpose graph to find one SCCfunctiondfsTranspose(u,transpose,visited){visited[u]=true;for(letvoftranspose[u]){if(!visited[v])dfsTranspose(v,transpose,visited);}}functioncountSCC(adj){letn=adj.length;lettranspose=Array.from({length:n},()=>[]);// Build the transpose graphfor(letu=0;u<n;u++){for(letvofadj[u])transpose[v].push(u);}letst=[];letvisited=newArray(n).fill(false);// First DFS: store vertices by finishing timefor(leti=0;i<n;i++){if(!visited[i])fillOrder(i,adj,visited,st);}// Reset visited for the second DFSvisited.fill(false);letsccCount=0;// Process vertices in decreasing finishing orderwhile(st.length>0){letu=st.pop();if(!visited[u]){// Each DFS gives one strongly connected componentdfsTranspose(u,transpose,visited);sccCount++;}}returnsccCount;}// Driver code// Directed Graph:// 0 ---> 1// ^ |// | v// +----- 2 ---> 3 ---> 4letadj=[[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).
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.
[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.