Skip to content

Commit 9a7f5b7

Browse files
authored
Create README.md
1 parent 2768224 commit 9a7f5b7

File tree

1 file changed

+396
-0
lines changed
  • 23 - Graph Data Structure Problems/19 - Strongly Connected Components | Kosaraju's Algorithm

1 file changed

+396
-0
lines changed
Lines changed: 396 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
<h1 align='center'>Strongly - Connected - Components</h1>
2+
3+
## Problem Statement
4+
5+
**Problem URL :** [Strongly Connected Components](https://www.geeksforgeeks.org/problems/strongly-connected-components-kosarajus-algo/1)
6+
7+
![image](https://github.com/user-attachments/assets/ff9d26ad-5597-4964-a601-3671afa49c34)
8+
![image](https://github.com/user-attachments/assets/1fb37d2d-d490-48a8-b71a-ec82350645c3)
9+
![image](https://github.com/user-attachments/assets/8074f834-cd76-40fd-911d-584531ca1074)
10+
![image](https://github.com/user-attachments/assets/f5f5659b-4c2c-4a35-85c5-e0f7c452d4fa)
11+
12+
## Problem Explanation
13+
A **Strongly Connected Component (SCC)** of a directed graph is a maximal subgraph in which any two vertices are reachable from each other. For example, if there is a path from (u) to (v) and a path from (v) to (u), then (u) and (v) belong to the same SCC.
14+
15+
The goal of the problem is to find all SCCs in a directed graph using **Kosaraju's algorithm**.
16+
17+
### Example Problem
18+
#### Input:
19+
Consider a directed graph with (V = 5) vertices and the following edges:
20+
[
21+
text{Adjacency List Representation:}
22+
0 to 1
23+
1 to 2
24+
2 to 0
25+
1 to 3
26+
3 to 4
27+
]
28+
29+
#### Graph Representation:
30+
```
31+
0 → 1 → 3 → 4
32+
↑ ↓
33+
2 ← → 2
34+
```
35+
36+
#### Output:
37+
The SCCs in the graph are:
38+
- **SCC 1**: ({0, 1, 2}) (all nodes are mutually reachable)
39+
- **SCC 2**: ({3})
40+
- **SCC 3**: ({4})
41+
42+
### Kosaraju’s Algorithm
43+
44+
Kosaraju's Algorithm uses **two Depth-First Searches (DFS)** to find all SCCs. It takes advantage of the fact that the **transpose** of a graph reverses all its edges, helping isolate SCCs.
45+
46+
#### Steps:
47+
48+
1. **Perform a DFS to Fill Stack Based on Finish Time**:
49+
- Traverse the graph using DFS. Maintain a stack to store the vertices in the order of their "finish time" (i.e., when DFS finishes for a vertex).
50+
- Nodes finishing last are placed on top of the stack.
51+
52+
**Example**:
53+
- Start from node (0):
54+
- Visit (0 to 1 to 2) and push (2 to 1 to 0) to the stack (DFS backtracking order).
55+
- Visit (3) (not visited yet) and push it to the stack.
56+
- Visit (4) (not visited yet) and push it to the stack.
57+
- Stack order: ([4, 3, 0, 1, 2]).
58+
59+
2. **Transpose the Graph**:
60+
- Reverse the direction of all edges in the graph. This creates a new graph where edges point in the opposite direction.
61+
62+
**Original Edges**:
63+
(0 to 1, 1 to 2, 2 to 0, 1 to 3, 3 to 4)
64+
65+
**Transposed Edges**:
66+
(1 to 0, 2 to 1, 0 to 2, 3 to 1, 4 to 3)
67+
68+
**Transposed Graph**:
69+
```
70+
1 → 0 → 2
71+
72+
3 ← 4
73+
```
74+
75+
3. **Perform DFS on Transposed Graph**:
76+
- Pop nodes from the stack (based on finish times in the original graph) and perform DFS on the transposed graph.
77+
- Each DFS traversal identifies one SCC.
78+
79+
**Example**:
80+
- Pop (4) from the stack → Start DFS → Finds SCC ({4}).
81+
- Pop (3) → Start DFS → Finds SCC ({3}).
82+
- Pop (0) → Start DFS → Traverses (0, 1, 2) → Finds SCC ({0, 1, 2}).
83+
84+
4. **Return the SCC Count**:
85+
- The total number of SCCs is the number of DFS calls made in step 3.
86+
87+
### Kosaraju’s Approach: Intuition
88+
89+
The algorithm relies on the following properties:
90+
1. **Finish Time Order**:
91+
- DFS ensures that nodes which are deeply connected in a graph are processed last.
92+
- By reversing the graph, SCCs become isolated components when processed in reverse order of finish time.
93+
94+
2. **Graph Transposition**:
95+
- Reversing edges isolates SCCs. Nodes in a single SCC remain reachable only to each other.
96+
97+
### Example Walkthrough
98+
99+
#### Graph:
100+
```
101+
0 → 1 → 3 → 4
102+
↑ ↓
103+
2 ← → 2
104+
```
105+
106+
1. **First DFS (Order Nodes by Finish Time)**:
107+
- Visit nodes (0 to 1 to 2) → Stack ([2, 1, 0]).
108+
- Visit (3) → Stack ([3, 2, 1, 0]).
109+
- Visit (4) → Stack ([4, 3, 2, 1, 0]).
110+
111+
2. **Transpose Graph**:
112+
```
113+
1 → 0 → 2
114+
115+
3 ← 4
116+
```
117+
118+
3. **Second DFS (On Transposed Graph)**:
119+
- Pop (4): Start DFS → ({4}).
120+
- Pop (3): Start DFS → ({3}).
121+
- Pop (0): Start DFS → ({0, 1, 2}).
122+
123+
4. **Result**:
124+
SCCs: ({0, 1, 2}, {3}, {4}).
125+
126+
127+
### Why Kosaraju’s Algorithm Works?
128+
1. **Reverse Finish Time**:
129+
- Processing nodes in reverse order ensures that the first DFS isolates SCCs effectively.
130+
131+
2. **Transposed Graph**:
132+
- SCCs in the original graph are strongly connected in the transposed graph as well.
133+
134+
## Problem Solution
135+
```cpp
136+
class Solution
137+
{
138+
public:
139+
// Function to perform Depth First Search (DFS) and populate the stack with nodes in order of their finish time
140+
void DFS(int node, vector<int>& visited, vector<vector<int>>& adjacencyList, stack<int>& nodesOrder) {
141+
// Mark the current node as visited
142+
visited[node] = true;
143+
144+
// Explore all unvisited neighbors of the current node
145+
for (auto neighbour : adjacencyList[node]) {
146+
if (!visited[neighbour])
147+
DFS(neighbour, visited, adjacencyList, nodesOrder);
148+
}
149+
150+
// After visiting all neighbors, push the current node onto the stack
151+
nodesOrder.push(node);
152+
}
153+
154+
// Function to perform DFS on the transposed graph
155+
void reverseDFS(int node, vector<vector<int>>& adjacencyList, vector<int>& visited) {
156+
// Mark the current node as visited
157+
visited[node] = true;
158+
159+
// Explore all unvisited neighbors of the current node in the transposed graph
160+
for (auto neighbour : adjacencyList[node]) {
161+
if (!visited[neighbour])
162+
reverseDFS(neighbour, adjacencyList, visited);
163+
}
164+
}
165+
166+
// Function to find the number of strongly connected components (SCCs) using Kosaraju's Algorithm
167+
int kosaraju(int V, vector<vector<int>>& adj) {
168+
// Step 1: Initialize visited vector and stack to store nodes in order of their finish time
169+
vector<int> visited(V, false);
170+
stack<int> nodesOrder;
171+
vector<vector<int>> transpose(V); // Adjacency list for the transposed graph
172+
int totalSCCs = 0; // Counter for SCCs
173+
174+
// Step 2: Perform DFS on the original graph to populate the stack
175+
for (int i = 0; i < V; i++) {
176+
if (!visited[i])
177+
DFS(i, visited, adj, nodesOrder);
178+
}
179+
180+
// Step 3: Create the transposed graph
181+
for (int i = 0; i < V; i++) {
182+
for (auto neighbour : adj[i]) {
183+
// Reverse the direction of edges
184+
transpose[neighbour].push_back(i);
185+
}
186+
}
187+
188+
// Step 4: Reset visited array for use in the second DFS
189+
fill(visited.begin(), visited.end(), false);
190+
191+
// Step 5: Perform DFS on the transposed graph in the order of nodes in the stack
192+
while (!nodesOrder.empty()) {
193+
int node = nodesOrder.top();
194+
nodesOrder.pop();
195+
196+
// If the node is not visited, it means we've found a new SCC
197+
if (!visited[node]) {
198+
totalSCCs++; // Increment SCC count
199+
reverseDFS(node, transpose, visited); // Explore all nodes in this SCC
200+
}
201+
}
202+
203+
// Step 6: Return the total number of SCCs
204+
return totalSCCs;
205+
}
206+
};
207+
208+
```
209+
## Problem Solution Explanation
210+
211+
Here’s a detailed, line-by-line explanation of the code and its time and space complexities.
212+
213+
#### 1. **DFS Function (Original Graph)**
214+
215+
```cpp
216+
void DFS(int node, vector<int>& visited, vector<vector<int>>& adjacencyList, stack<int>& nodesOrder) {
217+
visited[node] = true;
218+
for (auto neighbour : adjacencyList[node]) {
219+
if (!visited[neighbour])
220+
DFS(neighbour, visited, adjacencyList, nodesOrder);
221+
}
222+
nodesOrder.push(node);
223+
}
224+
```
225+
226+
**Purpose**:
227+
This function performs a **Depth-First Search (DFS)** on the graph to record nodes in their **finish time order**. It pushes nodes to a stack after exploring all their neighbors.
228+
229+
**Example**:
230+
For the graph:
231+
```
232+
0 → 1 → 2
233+
↑ ↓
234+
4 ← 3 → 5
235+
```
236+
237+
- Start DFS at (0): Visits (1), then (2), and stops as there are no more unvisited neighbors.
238+
- Push (2 to 1 to 0) into the stack in reverse order of finish time.
239+
- Next DFS call starts from unvisited (3), visiting (5) and (4), pushing (4 to 5 to 3).
240+
- Stack after all DFS: ([3, 5, 4, 0, 1, 2]).
241+
242+
**Time Complexity**: (O(V + E))
243+
- (V): Number of vertices.
244+
- (E): Number of edges.
245+
246+
247+
248+
#### 2. **reverseDFS Function (Transposed Graph)**
249+
250+
```cpp
251+
void reverseDFS(int node, vector<vector<int>>& adjacencyList, vector<int>& visited) {
252+
visited[node] = true;
253+
for (auto neighbour : adjacencyList[node]) {
254+
if (!visited[neighbour])
255+
reverseDFS(neighbour, adjacencyList, visited);
256+
}
257+
}
258+
```
259+
260+
**Purpose**:
261+
Performs DFS on the **transposed graph** to explore all nodes in a single Strongly Connected Component (SCC). Each invocation corresponds to finding one SCC.
262+
263+
**Example**:
264+
For the transposed graph of the above example:
265+
```
266+
1 ← 0 ← 2
267+
↓ ↑
268+
5 → 3 ← 4
269+
```
270+
271+
- Start reverseDFS from stack’s top: (3), which explores (3 to 5 to 4).
272+
- This marks the SCC ({3, 4, 5}).
273+
- Continue with the next unvisited node from the stack: (0), exploring (0 to 1 to 2), forming ({0, 1, 2}).
274+
275+
**Time Complexity**: (O(V + E)).
276+
277+
278+
279+
#### 3. **kosaraju Function**
280+
281+
```cpp
282+
int kosaraju(int V, vector<vector<int>>& adj) {
283+
vector<int> visited(V, false);
284+
stack<int> nodesOrder;
285+
vector<vector<int>> transpose(V);
286+
int totalSCCs = 0;
287+
```
288+
289+
- **Input**:
290+
- `V`: Number of vertices.
291+
- `adj`: Adjacency list representation of the graph.
292+
- **Output**:
293+
- Returns the number of SCCs.
294+
295+
296+
297+
##### Step 1: Populate Stack with Finish Times
298+
```cpp
299+
for (int i = 0; i < V; i++) {
300+
if (!visited[i])
301+
DFS(i, visited, adj, nodesOrder);
302+
}
303+
```
304+
- Traverses the graph using DFS, pushing nodes to the stack after visiting all their neighbors. This ensures nodes are stored in reverse finish time order.
305+
306+
**Example Stack**: After DFS on the graph, stack = ([3, 5, 4, 0, 1, 2]).
307+
308+
**Time Complexity**: (O(V + E)).
309+
310+
311+
312+
##### Step 2: Transpose the Graph
313+
```cpp
314+
for (int i = 0; i < V; i++) {
315+
for (auto neighbour : adj[i]) {
316+
transpose[neighbour].push_back(i);
317+
}
318+
}
319+
```
320+
- Reverse all edges in the graph.
321+
322+
**Example**:
323+
Original graph edges:
324+
(0 to 1, 1 to 2, 2 to 0, 3 to 5, 5 to 4, 4 to 3)
325+
326+
Transposed graph edges:
327+
(1 to 0, 2 to 1, 0 to 2, 5 to 3, 4 to 5, 3 to 4)
328+
329+
**Time Complexity**: (O(V + E)).
330+
331+
332+
333+
##### Step 3: Reset Visited Array
334+
```cpp
335+
fill(visited.begin(), visited.end(), false);
336+
```
337+
- Clears the visited array for reuse in the second DFS.
338+
339+
340+
341+
##### Step 4: Find SCCs Using Reverse DFS
342+
```cpp
343+
while (!nodesOrder.empty()) {
344+
int node = nodesOrder.top();
345+
nodesOrder.pop();
346+
347+
if (!visited[node]) {
348+
totalSCCs++;
349+
reverseDFS(node, transpose, visited);
350+
}
351+
}
352+
```
353+
- Process nodes in reverse finish time order from the stack.
354+
- Each DFS call identifies one SCC.
355+
356+
**Example**:
357+
- Stack = ([3, 5, 4, 0, 1, 2]).
358+
- Pop (3): Finds ({3, 4, 5}).
359+
- Pop (0): Finds ({0, 1, 2}).
360+
361+
**Time Complexity**: (O(V + E)).
362+
363+
364+
365+
#### 5. **Return Total SCCs**
366+
```cpp
367+
return totalSCCs;
368+
```
369+
Returns the number of SCCs.
370+
371+
**Output for Example Graph**: (2) SCCs: ({0, 1, 2}, {3, 4, 5}).
372+
373+
374+
375+
### Complexity Analysis
376+
377+
#### **Time Complexity**
378+
1. First DFS (original graph): (O(V + E)).
379+
2. Transpose graph: (O(V + E)).
380+
3. Second DFS (transposed graph): (O(V + E)).
381+
382+
**Total**: (O(V + E)).
383+
384+
#### **Space Complexity**
385+
1. Adjacency list storage: (O(V + E)).
386+
2. Transposed graph storage: (O(V + E)).
387+
3. Visited array and stack: (O(V)).
388+
389+
**Total**: (O(V + E)).
390+
391+
392+
393+
### Intuition Behind Kosaraju's Algorithm
394+
395+
- The **finish time order** ensures that nodes in one SCC are processed together in the transposed graph.
396+
- Transposing the graph isolates SCCs since edges between SCCs are reversed, breaking cross-SCC connections.

0 commit comments

Comments
 (0)