Enhance docs, add more tests in Kosaraju (#5966)

This commit is contained in:
Hardik Pawar
2024-10-24 11:08:08 +05:30
committed by GitHub
parent 13be2501c2
commit 578e5a73df
2 changed files with 143 additions and 86 deletions

View File

@ -5,82 +5,91 @@ import java.util.List;
import java.util.Stack; import java.util.Stack;
/** /**
* Java program that implements Kosaraju Algorithm. * This class implements the Kosaraju Algorithm to find all the Strongly Connected Components (SCCs)
* @author <a href="https://github.com/shivu2002a">Shivanagouda S A</a> * 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.
*
* <p> * <p>
* Kosaraju algorithm is a linear time algorithm to find the strongly connected components of a * A strongly connected component (SCC) of a directed graph is a subgraph where every vertex
directed graph, which, from here onwards will be referred by SCC. It leverages the fact that the * is reachable from every other vertex in the subgraph. The Kosaraju algorithm is particularly
transpose graph (same graph with all the edges reversed) has exactly the same SCCs as the original * efficient for finding SCCs because it performs two Depth First Search (DFS) passes on the
graph. * graph and its transpose.
* </p>
* 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 * <p><strong>Algorithm:</strong></p>
connected. Single node is always a SCC. * <ol>
* <li>Perform DFS on the original graph and push nodes to a stack in the order of their finishing time.</li>
* Example: * <li>Generate the transpose (reversed edges) of the original graph.</li>
* <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>
0 <--- 2 -------> 3 -------- > 4 ---- > 7 * </ol>
| ^ | ^ ^ *
| / | \ / * <p><strong>Example Graph:</strong></p>
| / | \ / * <pre>
v / v \ / * 0 <--- 2 -------> 3 -------- > 4 ---- > 7
1 5 --> 6 * | ^ | ^ ^
* | / | \ /
For the above graph, the SCC list goes as follows: * | / | \ /
0, 1, 2 * v / v \ /
3 * 1 5 --> 6
4, 5, 6 * </pre>
7 *
* <p><strong>SCCs in the example:</strong></p>
We can also see that order of the nodes in an SCC doesn't matter since they are in cycle. * <ul>
* <li>{0, 1, 2}</li>
{@summary} * <li>{3}</li>
* Kosaraju Algorithm: * <li>{4, 5, 6}</li>
1. Perform DFS traversal of the graph. Push node to stack before returning. This gives edges * <li>{7}</li>
sorted by lowest finish time. * </ul>
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. * <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>
*
The transpose graph of the above graph: * <p><strong>Graph Transpose Example:</strong></p>
0 ---> 2 <------- 3 <------- 4 <------ 7 * <pre>
^ / ^ \ / * 0 ---> 2 <------- 3 <------- 4 <------ 7
| / | \ / * ^ / ^ \ /
| / | \ / * | / | \ /
| v | v v * | / | \ /
1 5 <--- 6 * | v | v v
* 1 5 <--- 6
We can observe that this graph has the same SCC as that of original graph. * </pre>
*
* The SCCs of this transpose graph are the same as the original graph.
*/ */
public class Kosaraju { public class Kosaraju {
// Sort edges according to lowest finish time // Stack to sort edges by the lowest finish time (used in the first DFS)
Stack<Integer> stack = new Stack<Integer>(); private final Stack<Integer> stack = new Stack<>();
// Store each component // Store each strongly connected component
private List<Integer> scc = new ArrayList<>(); private List<Integer> scc = new ArrayList<>();
// All the strongly connected components // List of all SCCs
private List<List<Integer>> sccsList = new ArrayList<>(); private final List<List<Integer>> 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 v the number of vertices in the graph
* @param list Adjacency list of graph * @param list the adjacency list representing the directed graph
* @return List of SCCs * @return a list of SCCs where each SCC is a list of vertices
*/ */
public List<List<Integer>> kosaraju(int v, List<List<Integer>> list) { public List<List<Integer>> kosaraju(int v, List<List<Integer>> list) {
sortEdgesByLowestFinishTime(v, list); sortEdgesByLowestFinishTime(v, list);
List<List<Integer>> transposeGraph = createTransposeMatrix(v, list); List<List<Integer>> transposeGraph = createTransposeMatrix(v, list);
findStronglyConnectedComponents(v, transposeGraph); findStronglyConnectedComponents(v, transposeGraph);
return sccsList; 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<Integer>> list) { private void sortEdgesByLowestFinishTime(int v, List<List<Integer>> list) {
int[] vis = new int[v]; int[] vis = new int[v];
for (int i = 0; i < v; i++) { 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<List<Integer>> createTransposeMatrix(int v, List<List<Integer>> list) { private List<List<Integer>> createTransposeMatrix(int v, List<List<Integer>> list) {
var transposeGraph = new ArrayList<List<Integer>>(v); List<List<Integer>> transposeGraph = new ArrayList<>(v);
for (int i = 0; i < v; i++) { for (int i = 0; i < v; i++) {
transposeGraph.add(new ArrayList<>()); transposeGraph.add(new ArrayList<>());
} }
@ -104,14 +119,14 @@ public class Kosaraju {
} }
/** /**
* * Finds the strongly connected components (SCCs) by performing DFS on the transposed graph.
* @param v Node count * @param v the number of vertices in the graph
* @param transposeGraph Transpose of the given adjacency list * @param transposeGraph the adjacency list representing the transposed graph
*/ */
public void findStronglyConnectedComponents(int v, List<List<Integer>> transposeGraph) { public void findStronglyConnectedComponents(int v, List<List<Integer>> transposeGraph) {
int[] vis = new int[v]; int[] vis = new int[v];
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
var node = stack.pop(); int node = stack.pop();
if (vis[node] == 0) { if (vis[node] == 0) {
dfs2(node, vis, transposeGraph); dfs2(node, vis, transposeGraph);
sccsList.add(scc); 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<Integer>> list) { private void dfs(int node, int[] vis, List<List<Integer>> list) {
vis[node] = 1; vis[node] = 1;
for (Integer neighbour : list.get(node)) { for (Integer neighbour : list.get(node)) {
@ -131,7 +151,12 @@ public class Kosaraju {
stack.push(node); 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<Integer>> list) { private void dfs2(int node, int[] vis, List<List<Integer>> list) {
vis[node] = 1; vis[node] = 1;
for (Integer neighbour : list.get(node)) { for (Integer neighbour : list.get(node)) {

View File

@ -1,6 +1,6 @@
package com.thealgorithms.datastructures.graphs; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -9,14 +9,13 @@ import org.junit.jupiter.api.Test;
public class KosarajuTest { public class KosarajuTest {
private Kosaraju kosaraju = new Kosaraju(); private final Kosaraju kosaraju = new Kosaraju();
@Test @Test
public void findStronglyConnectedComps() { public void testFindStronglyConnectedComponents() {
// Create a adjacency list of graph // Create a graph using adjacency list
var n = 8; int n = 8;
var adjList = new ArrayList<List<Integer>>(n); List<List<Integer>> adjList = new ArrayList<>(n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
adjList.add(new ArrayList<>()); adjList.add(new ArrayList<>());
} }
@ -36,24 +35,24 @@ public class KosarajuTest {
List<List<Integer>> expectedResult = new ArrayList<>(); List<List<Integer>> expectedResult = new ArrayList<>();
/* /*
Expected result: Expected result:
0, 1, 2 {0, 1, 2}
3 {3}
5, 4, 6 {5, 4, 6}
7 {7}
*/ */
expectedResult.add(Arrays.asList(1, 2, 0)); 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(5, 6, 4));
expectedResult.add(Arrays.asList(7)); expectedResult.add(List.of(7));
assertTrue(expectedResult.equals(actualResult));
assertEquals(expectedResult, actualResult);
} }
@Test @Test
public void findStronglyConnectedCompsShouldGetSingleNodes() { public void testFindSingleNodeSCC() {
// Create a adjacency list of graph // Create a simple graph using adjacency list
var n = 8; int n = 8;
var adjList = new ArrayList<List<Integer>>(n); List<List<Integer>> adjList = new ArrayList<>(n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
adjList.add(new ArrayList<>()); adjList.add(new ArrayList<>());
} }
@ -71,9 +70,42 @@ public class KosarajuTest {
List<List<Integer>> expectedResult = new ArrayList<>(); List<List<Integer>> expectedResult = new ArrayList<>();
/* /*
Expected result: 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)); 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<List<Integer>> 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<List<Integer>> actualResult = kosaraju.kosaraju(n, adjList);
List<List<Integer>> 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);
} }
} }