Skip to content

Commit 578e5a7

Browse files
authored
Enhance docs, add more tests in Kosaraju (#5966)
1 parent 13be250 commit 578e5a7

File tree

2 files changed

+143
-86
lines changed

2 files changed

+143
-86
lines changed

src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java

+90-65
Original file line numberDiff line numberDiff line change
@@ -5,82 +5,91 @@
55
import java.util.Stack;
66

77
/**
8-
* Java program that implements Kosaraju Algorithm.
9-
* @author <a href="https://github.com/shivu2002a">Shivanagouda S A</a>
8+
* This class implements the Kosaraju Algorithm to find all the Strongly Connected Components (SCCs)
9+
* of a directed graph. Kosaraju's algorithm runs in linear time and leverages the concept that
10+
* the SCCs of a directed graph remain the same in its transpose (reverse) graph.
11+
*
1012
* <p>
11-
* Kosaraju algorithm is a linear time algorithm to find the strongly connected components of a
12-
directed graph, which, from here onwards will be referred by SCC. It leverages the fact that the
13-
transpose graph (same graph with all the edges reversed) has exactly the same SCCs as the original
14-
graph.
15-
16-
* A graph is said to be strongly connected if every vertex is reachable from every other vertex.
17-
The SCCs of a directed graph form a partition into subgraphs that are themselves strongly
18-
connected. Single node is always a SCC.
19-
20-
* Example:
21-
22-
0 <--- 2 -------> 3 -------- > 4 ---- > 7
23-
| ^ | ^ ^
24-
| / | \ /
25-
| / | \ /
26-
v / v \ /
27-
1 5 --> 6
28-
29-
For the above graph, the SCC list goes as follows:
30-
0, 1, 2
31-
3
32-
4, 5, 6
33-
7
34-
35-
We can also see that order of the nodes in an SCC doesn't matter since they are in cycle.
36-
37-
{@summary}
38-
* Kosaraju Algorithm:
39-
1. Perform DFS traversal of the graph. Push node to stack before returning. This gives edges
40-
sorted by lowest finish time.
41-
2. Find the transpose graph by reversing the edges.
42-
3. Pop nodes one by one from the stack and again to DFS on the modified graph.
43-
44-
The transpose graph of the above graph:
45-
0 ---> 2 <------- 3 <------- 4 <------ 7
46-
^ / ^ \ /
47-
| / | \ /
48-
| / | \ /
49-
| v | v v
50-
1 5 <--- 6
51-
52-
We can observe that this graph has the same SCC as that of original graph.
53-
13+
* A strongly connected component (SCC) of a directed graph is a subgraph where every vertex
14+
* is reachable from every other vertex in the subgraph. The Kosaraju algorithm is particularly
15+
* efficient for finding SCCs because it performs two Depth First Search (DFS) passes on the
16+
* graph and its transpose.
17+
* </p>
18+
*
19+
* <p><strong>Algorithm:</strong></p>
20+
* <ol>
21+
* <li>Perform DFS on the original graph and push nodes to a stack in the order of their finishing time.</li>
22+
* <li>Generate the transpose (reversed edges) of the original graph.</li>
23+
* <li>Perform DFS on the transpose graph, using the stack from the first DFS. Each DFS run on the transpose graph gives a SCC.</li>
24+
* </ol>
25+
*
26+
* <p><strong>Example Graph:</strong></p>
27+
* <pre>
28+
* 0 <--- 2 -------> 3 -------- > 4 ---- > 7
29+
* | ^ | ^ ^
30+
* | / | \ /
31+
* | / | \ /
32+
* v / v \ /
33+
* 1 5 --> 6
34+
* </pre>
35+
*
36+
* <p><strong>SCCs in the example:</strong></p>
37+
* <ul>
38+
* <li>{0, 1, 2}</li>
39+
* <li>{3}</li>
40+
* <li>{4, 5, 6}</li>
41+
* <li>{7}</li>
42+
* </ul>
43+
*
44+
* <p>The order of nodes in an SCC does not matter because every node in an SCC is reachable from every other node within the same SCC.</p>
45+
*
46+
* <p><strong>Graph Transpose Example:</strong></p>
47+
* <pre>
48+
* 0 ---> 2 <------- 3 <------- 4 <------ 7
49+
* ^ / ^ \ /
50+
* | / | \ /
51+
* | / | \ /
52+
* | v | v v
53+
* 1 5 <--- 6
54+
* </pre>
55+
*
56+
* The SCCs of this transpose graph are the same as the original graph.
5457
*/
55-
5658
public class Kosaraju {
5759

58-
// Sort edges according to lowest finish time
59-
Stack<Integer> stack = new Stack<Integer>();
60+
// Stack to sort edges by the lowest finish time (used in the first DFS)
61+
private final Stack<Integer> stack = new Stack<>();
6062

61-
// Store each component
63+
// Store each strongly connected component
6264
private List<Integer> scc = new ArrayList<>();
6365

64-
// All the strongly connected components
65-
private List<List<Integer>> sccsList = new ArrayList<>();
66+
// List of all SCCs
67+
private final List<List<Integer>> sccsList = new ArrayList<>();
6668

6769
/**
70+
* Main function to perform Kosaraju's Algorithm.
71+
* Steps:
72+
* 1. Sort nodes by the lowest finishing time
73+
* 2. Create the transpose (reverse edges) of the original graph
74+
* 3. Find SCCs by performing DFS on the transpose graph
75+
* 4. Return the list of SCCs
6876
*
69-
* @param v Node count
70-
* @param list Adjacency list of graph
71-
* @return List of SCCs
77+
* @param v the number of vertices in the graph
78+
* @param list the adjacency list representing the directed graph
79+
* @return a list of SCCs where each SCC is a list of vertices
7280
*/
7381
public List<List<Integer>> kosaraju(int v, List<List<Integer>> list) {
74-
7582
sortEdgesByLowestFinishTime(v, list);
76-
7783
List<List<Integer>> transposeGraph = createTransposeMatrix(v, list);
78-
7984
findStronglyConnectedComponents(v, transposeGraph);
80-
8185
return sccsList;
8286
}
8387

88+
/**
89+
* Performs DFS on the original graph to sort nodes by their finishing times.
90+
* @param v the number of vertices in the graph
91+
* @param list the adjacency list representing the original graph
92+
*/
8493
private void sortEdgesByLowestFinishTime(int v, List<List<Integer>> list) {
8594
int[] vis = new int[v];
8695
for (int i = 0; i < v; i++) {
@@ -90,8 +99,14 @@ private void sortEdgesByLowestFinishTime(int v, List<List<Integer>> list) {
9099
}
91100
}
92101

102+
/**
103+
* Creates the transpose (reverse) of the original graph.
104+
* @param v the number of vertices in the graph
105+
* @param list the adjacency list representing the original graph
106+
* @return the adjacency list representing the transposed graph
107+
*/
93108
private List<List<Integer>> createTransposeMatrix(int v, List<List<Integer>> list) {
94-
var transposeGraph = new ArrayList<List<Integer>>(v);
109+
List<List<Integer>> transposeGraph = new ArrayList<>(v);
95110
for (int i = 0; i < v; i++) {
96111
transposeGraph.add(new ArrayList<>());
97112
}
@@ -104,14 +119,14 @@ private List<List<Integer>> createTransposeMatrix(int v, List<List<Integer>> lis
104119
}
105120

106121
/**
107-
*
108-
* @param v Node count
109-
* @param transposeGraph Transpose of the given adjacency list
122+
* Finds the strongly connected components (SCCs) by performing DFS on the transposed graph.
123+
* @param v the number of vertices in the graph
124+
* @param transposeGraph the adjacency list representing the transposed graph
110125
*/
111126
public void findStronglyConnectedComponents(int v, List<List<Integer>> transposeGraph) {
112127
int[] vis = new int[v];
113128
while (!stack.isEmpty()) {
114-
var node = stack.pop();
129+
int node = stack.pop();
115130
if (vis[node] == 0) {
116131
dfs2(node, vis, transposeGraph);
117132
sccsList.add(scc);
@@ -120,7 +135,12 @@ public void findStronglyConnectedComponents(int v, List<List<Integer>> transpose
120135
}
121136
}
122137

123-
// Dfs to store the nodes in order of lowest finish time
138+
/**
139+
* Performs DFS on the original graph and pushes nodes onto the stack in order of their finish time.
140+
* @param node the current node being visited
141+
* @param vis array to keep track of visited nodes
142+
* @param list the adjacency list of the graph
143+
*/
124144
private void dfs(int node, int[] vis, List<List<Integer>> list) {
125145
vis[node] = 1;
126146
for (Integer neighbour : list.get(node)) {
@@ -131,7 +151,12 @@ private void dfs(int node, int[] vis, List<List<Integer>> list) {
131151
stack.push(node);
132152
}
133153

134-
// Dfs to find all the nodes of each strongly connected component
154+
/**
155+
* Performs DFS on the transposed graph to find the strongly connected components.
156+
* @param node the current node being visited
157+
* @param vis array to keep track of visited nodes
158+
* @param list the adjacency list of the transposed graph
159+
*/
135160
private void dfs2(int node, int[] vis, List<List<Integer>> list) {
136161
vis[node] = 1;
137162
for (Integer neighbour : list.get(node)) {

src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java

+53-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.thealgorithms.datastructures.graphs;
22

3-
import static org.junit.jupiter.api.Assertions.assertTrue;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
44

55
import java.util.ArrayList;
66
import java.util.Arrays;
@@ -9,14 +9,13 @@
99

1010
public class KosarajuTest {
1111

12-
private Kosaraju kosaraju = new Kosaraju();
12+
private final Kosaraju kosaraju = new Kosaraju();
1313

1414
@Test
15-
public void findStronglyConnectedComps() {
16-
// Create a adjacency list of graph
17-
var n = 8;
18-
var adjList = new ArrayList<List<Integer>>(n);
19-
15+
public void testFindStronglyConnectedComponents() {
16+
// Create a graph using adjacency list
17+
int n = 8;
18+
List<List<Integer>> adjList = new ArrayList<>(n);
2019
for (int i = 0; i < n; i++) {
2120
adjList.add(new ArrayList<>());
2221
}
@@ -36,24 +35,24 @@ public void findStronglyConnectedComps() {
3635
List<List<Integer>> expectedResult = new ArrayList<>();
3736
/*
3837
Expected result:
39-
0, 1, 2
40-
3
41-
5, 4, 6
42-
7
38+
{0, 1, 2}
39+
{3}
40+
{5, 4, 6}
41+
{7}
4342
*/
4443
expectedResult.add(Arrays.asList(1, 2, 0));
45-
expectedResult.add(Arrays.asList(3));
44+
expectedResult.add(List.of(3));
4645
expectedResult.add(Arrays.asList(5, 6, 4));
47-
expectedResult.add(Arrays.asList(7));
48-
assertTrue(expectedResult.equals(actualResult));
46+
expectedResult.add(List.of(7));
47+
48+
assertEquals(expectedResult, actualResult);
4949
}
5050

5151
@Test
52-
public void findStronglyConnectedCompsShouldGetSingleNodes() {
53-
// Create a adjacency list of graph
54-
var n = 8;
55-
var adjList = new ArrayList<List<Integer>>(n);
56-
52+
public void testFindSingleNodeSCC() {
53+
// Create a simple graph using adjacency list
54+
int n = 8;
55+
List<List<Integer>> adjList = new ArrayList<>(n);
5756
for (int i = 0; i < n; i++) {
5857
adjList.add(new ArrayList<>());
5958
}
@@ -71,9 +70,42 @@ public void findStronglyConnectedCompsShouldGetSingleNodes() {
7170
List<List<Integer>> expectedResult = new ArrayList<>();
7271
/*
7372
Expected result:
74-
0, 1, 2, 3, 4, 5, 6, 7
73+
{0, 1, 2, 3, 4, 5, 6, 7}
7574
*/
7675
expectedResult.add(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 0));
77-
assertTrue(expectedResult.equals(actualResult));
76+
77+
assertEquals(expectedResult, actualResult);
78+
}
79+
80+
@Test
81+
public void testDisconnectedGraph() {
82+
// Create a disconnected graph (two separate components)
83+
int n = 5;
84+
List<List<Integer>> adjList = new ArrayList<>(n);
85+
for (int i = 0; i < n; i++) {
86+
adjList.add(new ArrayList<>());
87+
}
88+
89+
// Add edges for first component
90+
adjList.get(0).add(1);
91+
adjList.get(1).add(2);
92+
adjList.get(2).add(0);
93+
94+
// Add edges for second component
95+
adjList.get(3).add(4);
96+
adjList.get(4).add(3);
97+
98+
List<List<Integer>> actualResult = kosaraju.kosaraju(n, adjList);
99+
100+
List<List<Integer>> expectedResult = new ArrayList<>();
101+
/*
102+
Expected result:
103+
{0, 1, 2}
104+
{3, 4}
105+
*/
106+
expectedResult.add(Arrays.asList(4, 3));
107+
expectedResult.add(Arrays.asList(1, 2, 0));
108+
109+
assertEquals(expectedResult, actualResult);
78110
}
79111
}

0 commit comments

Comments
 (0)