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:

+ *
    + *
  1. Perform DFS on the original graph and push nodes to a stack in the order of their finishing time.
  2. + *
  3. Generate the transpose (reversed edges) of the original graph.
  4. + *
  5. Perform DFS on the transpose graph, using the stack from the first DFS. Each DFS run on the transpose graph gives a SCC.
  6. + *
+ * + *

Example Graph:

+ *
+ * 0 <--- 2 -------> 3 -------- > 4 ---- > 7
+ * |     ^                      | ^       ^
+ * |    /                       |  \     /
+ * |   /                        |   \   /
+ * v  /                         v    \ /
+ * 1                            5 --> 6
+ * 
+ * + *

SCCs in the example:

+ * + * + *

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); } }