Strongly Connected Components



Strongly Connected Components (SCCs) are specific sub-graphs in a directed graph where every node can reach every other node within that sub-graph. In simple terms, SCCs are clusters of nodes that are tightly connected but are separate from other parts of the graph. Understanding SCCs is important, as they help in detecting cycles and studying network structures.

A directed graph is said to be strongly connected if every node can reach every other node directly or indirectly. However, in larger graphs, we may have multiple strongly connected parts that are not connected to each other. These independent sections are called strongly connected components.

For example, consider a directed graph with nodes A, B, C, D, and E. If A can reach B and B can reach A, but C, D, and E form a separate cluster where they can all reach each other but not A or B, then there are two SCCs in the graph: {A, B} and {C, D, E}.

Strongly Connected Components Algorithm

There are two popular algorithms to find strongly connected components in a graph:

  • Kosaraju's Algorithm
  • Tarjan's Algorithm

Kosaraju's Algorithm

Kosaraju's Algorithm is a two-pass algorithm that finds strongly connected components in a directed graph. Here's how it works:

  • First Pass: Perform a Depth First Search (DFS) on the graph and store the order of nodes based on their finishing times.
  • Second Pass: Reverse the graph and perform another DFS on the nodes in the order of their finishing times from the first pass. This will give us the strongly connected components.

Kosaraju's Algorithm Example

Let's consider a directed graph with nodes A, B, C, D, and E, and the following edges:

 A -> B B -> C C -> A C -> D D -> E E -> D 

Using Kosaraju's Algorithm, we can find the strongly connected components in the graph:

  • First Pass: The finishing times of the nodes are E, D, C, B, A.
  • Second Pass: The strongly connected components are {A, B, C} and {D, E}.

Code for Kosaraju's Algorithm

Here's an example code snippet for Kosaraju's Algorithm in C, C++, Java, and Python:

 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_VERTICES 5 // Graph adjacency matrix int adj[MAX_VERTICES][MAX_VERTICES] = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 0}, {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0} }; int visited[MAX_VERTICES] = {0}; int finishing_time[MAX_VERTICES]; int finish_index = 0; // Mapping index to letters (A-E) char map[MAX_VERTICES] = {'A', 'B', 'C', 'D', 'E'}; void DFS(int vertex, int print) { visited[vertex] = 1; if (print) printf("%c ", map[vertex]); // Print vertex if it's in SCC traversal for (int v = 0; v < MAX_VERTICES; v++) { if (adj[vertex][v] && !visited[v]) { DFS(v, print); } } if (!print) finishing_time[finish_index++] = vertex; // Store finishing time if not printing SCC } void reverse_graph() { int temp[MAX_VERTICES][MAX_VERTICES]; for (int i = 0; i < MAX_VERTICES; i++) { for (int j = 0; j < MAX_VERTICES; j++) { temp[i][j] = adj[i][j]; } } // Transpose the graph for (int i = 0; i < MAX_VERTICES; i++) { for (int j = 0; j < MAX_VERTICES; j++) { adj[i][j] = temp[j][i]; } } } void find_scc() { // Step 1: Compute finishing times in the original graph for (int v = 0; v < MAX_VERTICES; v++) { if (!visited[v]) { DFS(v, 0); // First pass DFS (store finish times) } } // Step 2: Reverse the graph reverse_graph(); // Step 3: Second DFS based on decreasing finishing times memset(visited, 0, sizeof(visited)); // Reset visited array printf("Strongly Connected Components:\n"); for (int i = MAX_VERTICES - 1; i >= 0; i--) { int vertex = finishing_time[i]; if (!visited[vertex]) { printf("SCC: "); DFS(vertex, 1); // Second pass DFS (print SCC) printf("\n"); } } } int main() { find_scc(); return 0; } 

Output

Following is the output of the above code:

 Strongly Connected Components: SCC: A C B SCC: D E 
 #include <iostream> #include <cstring> using namespace std; #define MAX_VERTICES 5 // Graph adjacency matrix int adj[MAX_VERTICES][MAX_VERTICES] = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 0}, {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0} }; int visited[MAX_VERTICES] = {0}; int finishing_time[MAX_VERTICES]; int finish_index = 0; // Mapping index to letters (A-E) char map[MAX_VERTICES] = {'A', 'B', 'C', 'D', 'E'}; void DFS(int vertex, int print) { visited[vertex] = 1; if (print) cout << map[vertex] << " "; // Print vertex if it's in SCC traversal for (int v = 0; v < MAX_VERTICES; v++) { if (adj[vertex][v] && !visited[v]) { DFS(v, print); } } if (!print) finishing_time[finish_index++] = vertex; // Store finishing time if not printing SCC } void reverse_graph() { int temp[MAX_VERTICES][MAX_VERTICES]; for (int i = 0; i < MAX_VERTICES; i++) { for (int j = 0; j < MAX_VERTICES; j++) { temp[i][j] = adj[i][j]; } } // Transpose the graph for (int i = 0; i < MAX_VERTICES; i++) { for (int j = 0; j < MAX_VERTICES; j++) { adj[i][j] = temp[j][i]; } } } void find_scc() { // Step 1: Compute finishing times in the original graph for (int v = 0; v < MAX_VERTICES; v++) { if (!visited[v]) { DFS(v, 0); // First pass DFS (store finish times) } } // Step 2: Reverse the graph reverse_graph(); // Step 3: Second DFS based on decreasing finishing times memset(visited, 0, sizeof(visited)); // Reset visited array cout << "Strongly Connected Components:" << endl; for (int i = MAX_VERTICES - 1; i >= 0; i--) { int vertex = finishing_time[i]; if (!visited[vertex]) { cout << "SCC: "; DFS(vertex, 1); // Second pass DFS (print SCC) cout << endl; } } } int main() { find_scc(); return 0; } 

Output

Following is the output of the above code:

 Strongly Connected Components: SCC: A C B SCC: D E 
 public class StronglyConnectedComponents { static final int MAX_VERTICES = 5; static int[][] adj = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 0}, {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0} }; static int[] visited = new int[MAX_VERTICES]; static int[] finishing_time = new int[MAX_VERTICES]; static int finish_index = 0; static char[] map = {'A', 'B', 'C', 'D', 'E'}; static void DFS(int vertex, boolean recordFinishTime) { visited[vertex] = 1; if (!recordFinishTime) { System.out.print(map[vertex] + " "); } for (int v = 0; v < MAX_VERTICES; v++) { if (adj[vertex][v] == 1 && visited[v] == 0) { DFS(v, recordFinishTime); } } if (recordFinishTime) { finishing_time[finish_index++] = vertex; } } static void reverse_graph() { int[][] temp = new int[MAX_VERTICES][MAX_VERTICES]; for (int i = 0; i < MAX_VERTICES; i++) { for (int j = 0; j < MAX_VERTICES; j++) { temp[i][j] = adj[j][i]; } } adj = temp; } static void find_scc() { for (int v = 0; v < MAX_VERTICES; v++) { if (visited[v] == 0) { DFS(v, true); } } reverse_graph(); visited = new int[MAX_VERTICES]; System.out.println("Strongly Connected Components:"); for (int i = MAX_VERTICES - 1; i >= 0; i--) { int vertex = finishing_time[i]; if (visited[vertex] == 0) { System.out.print("SCC: "); DFS(vertex, false); System.out.println(); } } } public static void main(String[] args) { find_scc(); } } 

Output

Following is the output of the above code:

 SCC: A C B SCC: D E 
 MAX_VERTICES = 5 # Graph adjacency matrix adj = [ [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [1, 0, 0, 1, 0], [0, 0, 0, 0, 1], [0, 0, 0, 1, 0] ] visited = [False] * MAX_VERTICES finishing_time = [0] * MAX_VERTICES finish_index = 0 # Mapping index to letters (A-E) vertex_map = ['A', 'B', 'C', 'D', 'E'] def DFS(vertex, record_time): global finish_index # Declare as global to avoid UnboundLocalError visited[vertex] = True if not record_time: print(vertex_map[vertex], end=" ") # Print SCC members for v in range(MAX_VERTICES): if adj[vertex][v] and not visited[v]: DFS(v, record_time) if record_time: # Store finishing order in first DFS pass finishing_time[finish_index] = vertex finish_index += 1 def reverse_graph(): global adj adj = [[adj[j][i] for j in range(MAX_VERTICES)] for i in range(MAX_VERTICES)] def find_scc(): global visited, finish_index visited = [False] * MAX_VERTICES finish_index = 0 # First DFS pass to get finishing times for v in range(MAX_VERTICES): if not visited[v]: DFS(v, True) # Step 2: Reverse the graph reverse_graph() # Step 3: Second DFS based on decreasing finishing times visited = [False] * MAX_VERTICES print("Strongly Connected Components:") for i in range(MAX_VERTICES - 1, -1, -1): vertex = finishing_time[i] if not visited[vertex]: print("SCC:", end=" ") DFS(vertex, False) print() if __name__ == "__main__": find_scc() 

Output

Following is the output of the above code:

 SCC: A C B SCC: D E 

Tarjan's Algorithm

Tarjan's Algorithm is another popular algorithm to find strongly connected components in a directed graph. It uses Depth First Search (DFS) to traverse the graph and identify SCCs. Here's how it works:

  • Initialize a stack to keep track of visited nodes and their order of traversal.
  • Perform a DFS traversal of the graph, assigning a unique ID to each node and keeping track of the lowest ID reachable from that node.
  • Nodes with the same ID and low-link value form a strongly connected component.

Tarjan's Algorithm Example

Consider the same directed graph with nodes A, B, C, D, and E, and the following edges:

 A -> B B -> C C -> A C -> D D -> E E -> D 

Using Tarjan's Algorithm, we can find the strongly connected components in the graph:

  • The strongly connected components are {A, B, C} and {D, E}.

Code for Tarjan's Algorithm

Here's an example code snippet for Tarjan's Algorithm in C, C++, Java, and Python:

 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_VERTICES 5 // Graph adjacency matrix int adj[MAX_VERTICES][MAX_VERTICES] = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 0}, {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0} }; int visited[MAX_VERTICES] = {0}; int stack[MAX_VERTICES]; int stack_index = 0; int ids[MAX_VERTICES]; int low[MAX_VERTICES]; int id = 0; // Mapping index to letters (A-E) char map[MAX_VERTICES] = {'A', 'B', 'C', 'D', 'E'}; int map_index = 0; void DFS(int vertex) { stack[stack_index++] = vertex; visited[vertex] = 1; ids[vertex] = low[vertex] = id++; for (int v = 0; v < MAX_VERTICES; v++) { if (adj[vertex][v]) { if (!visited[v]) { DFS(v); } if (ids[v] != -1) { low[vertex] = low[vertex] 0) { int node = stack[--stack_index]; low[node] = ids[vertex]; printf("%c ", map[node]); if (node == vertex) break; } printf("\n"); } } void find_scc() { memset(ids, -1, sizeof(ids)); memset(low, -1, sizeof(low)); for (int v = 0; v < MAX_VERTICES; v++) { if (!visited[v]) { DFS(v); } } } int main() { find_scc(); return 0; } 

Output

Following is the output of the above code:

 SCC: E D SCC: C B A 
 #include <iostream> #include <cstring> using namespace std; #define MAX_VERTICES 5 // Graph adjacency matrix int adj[MAX_VERTICES][MAX_VERTICES] = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 0}, {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0} }; int visited[MAX_VERTICES] = {0}; int stack[MAX_VERTICES]; int stack_index = 0; int ids[MAX_VERTICES]; int low[MAX_VERTICES]; int id = 0; // Mapping index to letters (A-E) char map[MAX_VERTICES] = {'A', 'B', 'C', 'D', 'E'}; int map_index = 0; void DFS(int vertex) { stack[stack_index++] = vertex; visited[vertex] = 1; ids[vertex] = low[vertex] = id++; for (int v = 0; v < MAX_VERTICES; v++) { if (adj[vertex][v]) { if (!visited[v]) { DFS(v); } if (ids[v] != -1) { low[vertex] = low[vertex] < low[v] ? low[vertex] : low[v]; } } } if (ids[vertex] == low[vertex]) { cout << "SCC: "; while (stack_index > 0) { int node = stack[--stack_index]; low[node] = ids[vertex]; cout << map[node] << " "; if (node == vertex) break; } cout << endl; } } void find_scc() { memset(ids, -1, sizeof(ids)); memset(low, -1, sizeof(low)); for (int v = 0; v < MAX_VERTICES; v++) { if (!visited[v]) { DFS(v); } } } int main() { find_scc(); return 0; } 

Output

Following is the output of the above code:

 SCC: E D SCC: C B A 
 public class TarjansAlgorithm { static final int MAX_VERTICES = 5; // Graph adjacency matrix static int[][] adj = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 0}, {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0} }; static int[] visited = new int[MAX_VERTICES]; static int[] stack = new int[MAX_VERTICES]; static int stack_index = 0; static int[] ids = new int[MAX_VERTICES]; static int[] low = new int[MAX_VERTICES]; static int id = 0; // Mapping index to letters (A-E) static char[] map = {'A', 'B', 'C', 'D', 'E'}; static int map_index = 0; static void DFS(int vertex) { stack[stack_index++] = vertex; visited[vertex] = 1; ids[vertex] = low[vertex] = id++; for (int v = 0; v < MAX_VERTICES; v++) { if (adj[vertex][v] == 1) { if (visited[v] == 0) { DFS(v); } if (ids[v] != -1) { low[vertex] = low[vertex] < low[v] ? low[vertex] : low[v]; } } } if (ids[vertex] == low[vertex]) { System.out.print("SCC: "); while (stack_index > 0) { int node = stack[--stack_index]; low[node] = ids[vertex]; System.out.print(map[node] + " "); if (node == vertex) break; } System.out.println(); } } static void find_scc() { for (int v = 0; v < MAX_VERTICES; v++) { if (visited[v] == 0) { DFS(v); } } } public static void main(String[] args) { find_scc(); } } 

Output

Following is the output of the above code:

 SCC: E D SCC: C B A 
 MAX_VERTICES = 5 adj = [ [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [1, 0, 0, 1, 0], [0, 0, 0, 0, 1], [0, 0, 0, 1, 0] ] visited = [False] * MAX_VERTICES stack = [] on_stack = [False] * MAX_VERTICES ids = [-1] * MAX_VERTICES low = [-1] * MAX_VERTICES scc_count = 0 id = 0 map_index = ['A', 'B', 'C', 'D', 'E'] def DFS(vertex): global id, scc_count stack.append(vertex) on_stack[vertex] = True ids[vertex] = low[vertex] = id id += 1 for v in range(MAX_VERTICES): if adj[vertex][v] == 1: if ids[v] == -1: # Unvisited DFS(v) low[vertex] = min(low[vertex], low[v]) elif on_stack[v]: # In the current SCC low[vertex] = min(low[vertex], ids[v]) if ids[vertex] == low[vertex]: print("SCC:", end=" ") while True: node = stack.pop() on_stack[node] = False low[node] = ids[vertex] print(map_index[node], end=" ") if node == vertex: break print() scc_count += 1 def find_scc(): for v in range(MAX_VERTICES): if ids[v] == -1: DFS(v) find_scc() 

Output

Following is the output of the above code:

 SCC: E D SCC: C B A 

Applications of Strongly Connected Components

Following are some common applications of Strongly Connected Components:

  • Network Analysis: SCCs help in understanding the structure and connectivity of networks, such as social networks, transportation networks, and communication networks.
  • Graph Algorithms: SCCs are used in various graph algorithms, such as cycle detection, pathfinding, and network flow optimization.
  • Software Engineering: SCCs are useful in software engineering for dependency analysis, module identification, and code refactoring.

Conclusion

Understanding Strongly Connected Components is essential for analyzing the connectivity and structure of directed graphs. Algorithms like Kosaraju's Algorithm and Tarjan's Algorithm provide efficient ways to identify SCCs in graphs, enabling various applications in network analysis, graph algorithms, and software engineering.

Advertisements