From 578e5a73df2c2499f5e2b38cddb58138f72eebc0 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Thu, 24 Oct 2024 11:08:08 +0530
Subject: [PATCH] Enhance docs, add more tests in `Kosaraju` (#5966)
---
.../datastructures/graphs/Kosaraju.java | 155 ++++++++++--------
.../datastructures/graphs/KosarajuTest.java | 74 ++++++---
2 files changed, 143 insertions(+), 86 deletions(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
index c5f15839f..78a184f04 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
@@ -5,82 +5,91 @@ import java.util.List;
import java.util.Stack;
/**
- * Java program that implements Kosaraju Algorithm.
- * @author Shivanagouda S A
+ * This class implements the Kosaraju Algorithm to find all the Strongly Connected Components (SCCs)
+ * of a directed graph. Kosaraju's algorithm runs in linear time and leverages the concept that
+ * the SCCs of a directed graph remain the same in its transpose (reverse) graph.
+ *
*
- * Kosaraju algorithm is a linear time algorithm to find the strongly connected components of a
-directed graph, which, from here onwards will be referred by SCC. It leverages the fact that the
-transpose graph (same graph with all the edges reversed) has exactly the same SCCs as the original
-graph.
-
- * A graph is said to be strongly connected if every vertex is reachable from every other vertex.
-The SCCs of a directed graph form a partition into subgraphs that are themselves strongly
-connected. Single node is always a SCC.
-
- * Example:
-
-0 <--- 2 -------> 3 -------- > 4 ---- > 7
-| ^ | ^ ^
-| / | \ /
-| / | \ /
-v / v \ /
-1 5 --> 6
-
-For the above graph, the SCC list goes as follows:
-0, 1, 2
-3
-4, 5, 6
-7
-
-We can also see that order of the nodes in an SCC doesn't matter since they are in cycle.
-
-{@summary}
- * Kosaraju Algorithm:
-1. Perform DFS traversal of the graph. Push node to stack before returning. This gives edges
-sorted by lowest finish time.
-2. Find the transpose graph by reversing the edges.
-3. Pop nodes one by one from the stack and again to DFS on the modified graph.
-
-The transpose graph of the above graph:
-0 ---> 2 <------- 3 <------- 4 <------ 7
-^ / ^ \ /
-| / | \ /
-| / | \ /
-| v | v v
-1 5 <--- 6
-
-We can observe that this graph has the same SCC as that of original graph.
-
+ * A strongly connected component (SCC) of a directed graph is a subgraph where every vertex
+ * is reachable from every other vertex in the subgraph. The Kosaraju algorithm is particularly
+ * efficient for finding SCCs because it performs two Depth First Search (DFS) passes on the
+ * graph and its transpose.
+ *
+ *
+ * Algorithm:
+ *
+ * - Perform DFS on the original graph and push nodes to a stack in the order of their finishing time.
+ * - Generate the transpose (reversed edges) of the original graph.
+ * - Perform DFS on the transpose graph, using the stack from the first DFS. Each DFS run on the transpose graph gives a SCC.
+ *
+ *
+ * Example Graph:
+ *
+ * 0 <--- 2 -------> 3 -------- > 4 ---- > 7
+ * | ^ | ^ ^
+ * | / | \ /
+ * | / | \ /
+ * v / v \ /
+ * 1 5 --> 6
+ *
+ *
+ * SCCs in the example:
+ *
+ * - {0, 1, 2}
+ * - {3}
+ * - {4, 5, 6}
+ * - {7}
+ *
+ *
+ * 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.
+ *
+ * Graph Transpose Example:
+ *
+ * 0 ---> 2 <------- 3 <------- 4 <------ 7
+ * ^ / ^ \ /
+ * | / | \ /
+ * | / | \ /
+ * | v | v v
+ * 1 5 <--- 6
+ *
+ *
+ * The SCCs of this transpose graph are the same as the original graph.
*/
-
public class Kosaraju {
- // Sort edges according to lowest finish time
- Stack stack = new Stack();
+ // Stack to sort edges by the lowest finish time (used in the first DFS)
+ private final Stack stack = new Stack<>();
- // Store each component
+ // Store each strongly connected component
private List scc = new ArrayList<>();
- // All the strongly connected components
- private List> sccsList = new ArrayList<>();
+ // List of all SCCs
+ private final List> sccsList = new ArrayList<>();
/**
+ * Main function to perform Kosaraju's Algorithm.
+ * Steps:
+ * 1. Sort nodes by the lowest finishing time
+ * 2. Create the transpose (reverse edges) of the original graph
+ * 3. Find SCCs by performing DFS on the transpose graph
+ * 4. Return the list of SCCs
*
- * @param v Node count
- * @param list Adjacency list of graph
- * @return List of SCCs
+ * @param v the number of vertices in the graph
+ * @param list the adjacency list representing the directed graph
+ * @return a list of SCCs where each SCC is a list of vertices
*/
public List> kosaraju(int v, List> list) {
-
sortEdgesByLowestFinishTime(v, list);
-
List> transposeGraph = createTransposeMatrix(v, list);
-
findStronglyConnectedComponents(v, transposeGraph);
-
return sccsList;
}
+ /**
+ * Performs DFS on the original graph to sort nodes by their finishing times.
+ * @param v the number of vertices in the graph
+ * @param list the adjacency list representing the original graph
+ */
private void sortEdgesByLowestFinishTime(int v, List> list) {
int[] vis = new int[v];
for (int i = 0; i < v; i++) {
@@ -90,8 +99,14 @@ public class Kosaraju {
}
}
+ /**
+ * Creates the transpose (reverse) of the original graph.
+ * @param v the number of vertices in the graph
+ * @param list the adjacency list representing the original graph
+ * @return the adjacency list representing the transposed graph
+ */
private List> createTransposeMatrix(int v, List> list) {
- var transposeGraph = new ArrayList>(v);
+ List> transposeGraph = new ArrayList<>(v);
for (int i = 0; i < v; i++) {
transposeGraph.add(new ArrayList<>());
}
@@ -104,14 +119,14 @@ public class Kosaraju {
}
/**
- *
- * @param v Node count
- * @param transposeGraph Transpose of the given adjacency list
+ * Finds the strongly connected components (SCCs) by performing DFS on the transposed graph.
+ * @param v the number of vertices in the graph
+ * @param transposeGraph the adjacency list representing the transposed graph
*/
public void findStronglyConnectedComponents(int v, List> transposeGraph) {
int[] vis = new int[v];
while (!stack.isEmpty()) {
- var node = stack.pop();
+ int node = stack.pop();
if (vis[node] == 0) {
dfs2(node, vis, transposeGraph);
sccsList.add(scc);
@@ -120,7 +135,12 @@ public class Kosaraju {
}
}
- // Dfs to store the nodes in order of lowest finish time
+ /**
+ * Performs DFS on the original graph and pushes nodes onto the stack in order of their finish time.
+ * @param node the current node being visited
+ * @param vis array to keep track of visited nodes
+ * @param list the adjacency list of the graph
+ */
private void dfs(int node, int[] vis, List> list) {
vis[node] = 1;
for (Integer neighbour : list.get(node)) {
@@ -131,7 +151,12 @@ public class Kosaraju {
stack.push(node);
}
- // Dfs to find all the nodes of each strongly connected component
+ /**
+ * Performs DFS on the transposed graph to find the strongly connected components.
+ * @param node the current node being visited
+ * @param vis array to keep track of visited nodes
+ * @param list the adjacency list of the transposed graph
+ */
private void dfs2(int node, int[] vis, List> list) {
vis[node] = 1;
for (Integer neighbour : list.get(node)) {
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java
index c1e68acac..53ed26dff 100644
--- a/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java
@@ -1,6 +1,6 @@
package com.thealgorithms.datastructures.graphs;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
@@ -9,14 +9,13 @@ import org.junit.jupiter.api.Test;
public class KosarajuTest {
- private Kosaraju kosaraju = new Kosaraju();
+ private final Kosaraju kosaraju = new Kosaraju();
@Test
- public void findStronglyConnectedComps() {
- // Create a adjacency list of graph
- var n = 8;
- var adjList = new ArrayList>(n);
-
+ public void testFindStronglyConnectedComponents() {
+ // Create a graph using adjacency list
+ int n = 8;
+ List> adjList = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
adjList.add(new ArrayList<>());
}
@@ -36,24 +35,24 @@ public class KosarajuTest {
List> expectedResult = new ArrayList<>();
/*
Expected result:
- 0, 1, 2
- 3
- 5, 4, 6
- 7
+ {0, 1, 2}
+ {3}
+ {5, 4, 6}
+ {7}
*/
expectedResult.add(Arrays.asList(1, 2, 0));
- expectedResult.add(Arrays.asList(3));
+ expectedResult.add(List.of(3));
expectedResult.add(Arrays.asList(5, 6, 4));
- expectedResult.add(Arrays.asList(7));
- assertTrue(expectedResult.equals(actualResult));
+ expectedResult.add(List.of(7));
+
+ assertEquals(expectedResult, actualResult);
}
@Test
- public void findStronglyConnectedCompsShouldGetSingleNodes() {
- // Create a adjacency list of graph
- var n = 8;
- var adjList = new ArrayList>(n);
-
+ public void testFindSingleNodeSCC() {
+ // Create a simple graph using adjacency list
+ int n = 8;
+ List> adjList = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
adjList.add(new ArrayList<>());
}
@@ -71,9 +70,42 @@ public class KosarajuTest {
List> expectedResult = new ArrayList<>();
/*
Expected result:
- 0, 1, 2, 3, 4, 5, 6, 7
+ {0, 1, 2, 3, 4, 5, 6, 7}
*/
expectedResult.add(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 0));
- assertTrue(expectedResult.equals(actualResult));
+
+ assertEquals(expectedResult, actualResult);
+ }
+
+ @Test
+ public void testDisconnectedGraph() {
+ // Create a disconnected graph (two separate components)
+ int n = 5;
+ List> adjList = new ArrayList<>(n);
+ for (int i = 0; i < n; i++) {
+ adjList.add(new ArrayList<>());
+ }
+
+ // Add edges for first component
+ adjList.get(0).add(1);
+ adjList.get(1).add(2);
+ adjList.get(2).add(0);
+
+ // Add edges for second component
+ adjList.get(3).add(4);
+ adjList.get(4).add(3);
+
+ List> actualResult = kosaraju.kosaraju(n, adjList);
+
+ List> expectedResult = new ArrayList<>();
+ /*
+ Expected result:
+ {0, 1, 2}
+ {3, 4}
+ */
+ expectedResult.add(Arrays.asList(4, 3));
+ expectedResult.add(Arrays.asList(1, 2, 0));
+
+ assertEquals(expectedResult, actualResult);
}
}