From 16a0067d4e3889455710027fd2a5937e5baa4071 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Thu, 24 Oct 2024 11:09:28 +0530 Subject: [PATCH] refactor: Enhance docs, add tests in `TarjansAlgorithm` --- .../graphs/TarjansAlgorithm.java | 149 ++++++++++-------- .../graphs/TarjansAlgorithmTest.java | 90 +++++++++-- 2 files changed, 158 insertions(+), 81 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java index de50044256c6..91974ba13319 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java @@ -5,66 +5,73 @@ import java.util.Stack; /** - * Java program that implements Tarjan's Algorithm. - * @author Shivanagouda S A + * Java program that implements Tarjan's Algorithm to find Strongly Connected Components (SCCs) in a directed graph. + * *

- * Tarjan's algorithm is a linear time algorithm to find the strongly connected components of a -directed graph, which, from here onwards will be referred as SCC. - - * 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 --------> 1 -------> 3 --------> 4 -^ / -| / -| / -| / -| / -| / -| / -| / -| / -| / -|V -2 - -For the above graph, the SCC list goes as follows: -1, 2, 0 -3 -4 - -We can also see that order of the nodes in an SCC doesn't matter since they are in cycle. - -{@summary} -Tarjan's Algorithm: - * DFS search produces a DFS tree - * Strongly Connected Components form subtrees of the DFS tree. - * If we can find the head of these subtrees, we can get all the nodes in that subtree (including -the head) and that will be one SCC. - * There is no back edge from one SCC to another (here can be cross edges, but they will not be -used). - - * Kosaraju Algorithm aims at doing the same but uses two DFS traversalse whereas Tarjan’s -algorithm does the same in a single DFS, which leads to much lower constant factors in the latter. - + * Tarjan's algorithm is a linear time algorithm (O(V + E)) that identifies the SCCs of a directed graph. + * An SCC is a maximal subgraph where every vertex is reachable from every other vertex within the subgraph. + * + *

Algorithm Overview:

+ * + * + *

+ * Example of a directed graph: + *

+ *  0 --------> 1 -------> 3 --------> 4
+ *  ^          /
+ *  |         /
+ *  |        /
+ *  |       /
+ *  |      /
+ *  |     /
+ *  |    /
+ *  |   /
+ *  |  /
+ *  | /
+ *  V
+ *  2
+ * 
+ * + *

+ * For the above graph, the SCC list is as follows: + *

+ * The order of nodes in an SCC does not matter as they form cycles. + * + *

Comparison with Kosaraju's Algorithm:

+ *

+ * Kosaraju's algorithm also identifies SCCs but does so using two DFS traversals. + * In contrast, Tarjan's algorithm achieves this in a single DFS traversal, leading to improved performance + * in terms of constant factors. + *

*/ public class TarjansAlgorithm { - // Timer for tracking lowtime and insertion time + // Timer for tracking low time and insertion time private int time; - private final List> sccList = new ArrayList>(); + // List to store all strongly connected components + private final List> sccList = new ArrayList<>(); + /** + * Finds and returns the strongly connected components (SCCs) of the directed graph. + * + * @param v the number of vertices in the graph + * @param graph the adjacency list representation of the graph + * @return a list of lists, where each inner list represents a strongly connected component + */ public List> stronglyConnectedComponents(int v, List> graph) { - - // Initially all vertices as unvisited, insertion and low time are undefined - - // insertionTime:Time when a node is visited 1st time while DFS traversal - - // lowTime: indicates the earliest visited vertex (the vertex with minimum insertion time) - // that can be reached from a subtree rooted with a particular node. + // Initialize arrays for insertion time and low-link values int[] lowTime = new int[v]; int[] insertionTime = new int[v]; for (int i = 0; i < v; i++) { @@ -72,11 +79,11 @@ public List> stronglyConnectedComponents(int v, List lowTime[i] = -1; } - // To check if element is present in stack + // Track if vertices are in the stack boolean[] isInStack = new boolean[v]; - // Store nodes during DFS - Stack st = new Stack(); + // Stack to hold nodes during DFS + Stack st = new Stack<>(); for (int i = 0; i < v; i++) { if (insertionTime[i] == -1) { @@ -87,36 +94,44 @@ public List> stronglyConnectedComponents(int v, List return sccList; } + /** + * A utility function to perform DFS and find SCCs. + * + * @param u the current vertex being visited + * @param lowTime array to keep track of the low-link values + * @param insertionTime array to keep track of the insertion times + * @param isInStack boolean array indicating if a vertex is in the stack + * @param st the stack used for DFS + * @param graph the adjacency list representation of the graph + */ private void stronglyConnCompsUtil(int u, int[] lowTime, int[] insertionTime, boolean[] isInStack, Stack st, List> graph) { - - // Initialize insertion time and lowTime value of current node + // Set insertion time and low-link value insertionTime[u] = time; lowTime[u] = time; - time += 1; + time++; - // Push current node into stack + // Push current node onto the stack isInStack[u] = true; st.push(u); - // Go through all vertices adjacent to this + // Explore adjacent vertices for (Integer vertex : graph.get(u)) { - // If the adjacent node is unvisited, do DFS if (insertionTime[vertex] == -1) { stronglyConnCompsUtil(vertex, lowTime, insertionTime, isInStack, st, graph); - // update lowTime for the current node comparing lowtime of adj node + // Update low-link value lowTime[u] = Math.min(lowTime[u], lowTime[vertex]); } else if (isInStack[vertex]) { - // If adj node is in stack, update low + // Vertex is in the stack; update low-link value lowTime[u] = Math.min(lowTime[u], insertionTime[vertex]); } } - // If lowtime and insertion time are same, current node is the head of an SCC - // head node found, get all the nodes in this SCC + + // Check if the current vertex is the root of an SCC if (lowTime[u] == insertionTime[u]) { int w = -1; - var scc = new ArrayList(); + List scc = new ArrayList<>(); - // Stack has all the nodes of the current SCC + // Pop vertices from the stack until the root is found while (w != u) { w = st.pop(); scc.add(w); diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java index dc81d99dd0bf..314cc415815d 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.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,11 +9,11 @@ public class TarjansAlgorithmTest { - TarjansAlgorithm tarjansAlgo = new TarjansAlgorithm(); + private final TarjansAlgorithm tarjansAlgo = new TarjansAlgorithm(); @Test - public void findStronglyConnectedComps() { - var v = 5; + public void testFindStronglyConnectedComponents() { + int v = 5; var graph = new ArrayList>(); for (int i = 0; i < v; i++) { graph.add(new ArrayList<>()); @@ -32,23 +32,20 @@ public void findStronglyConnectedComps() { 4 */ List> expectedResult = new ArrayList<>(); - - expectedResult.add(Arrays.asList(4)); - expectedResult.add(Arrays.asList(3)); + expectedResult.add(List.of(4)); + expectedResult.add(List.of(3)); expectedResult.add(Arrays.asList(2, 1, 0)); - assertTrue(expectedResult.equals(actualResult)); + assertEquals(expectedResult, actualResult); } @Test - public void findStronglyConnectedCompsShouldGetSingleNodes() { - // Create a adjacency list of graph - var n = 8; + public void testFindStronglyConnectedComponentsWithSingleNodes() { + // Create a graph where each node is its own SCC + int n = 8; var adjList = new ArrayList>(n); - for (int i = 0; i < n; i++) { adjList.add(new ArrayList<>()); } - adjList.get(0).add(1); adjList.get(1).add(2); adjList.get(2).add(3); @@ -65,6 +62,71 @@ public void findStronglyConnectedCompsShouldGetSingleNodes() { 7, 6, 5, 4, 3, 2, 1, 0 */ expectedResult.add(Arrays.asList(7, 6, 5, 4, 3, 2, 1, 0)); - assertTrue(expectedResult.equals(actualResult)); + assertEquals(expectedResult, actualResult); + } + + @Test + public void testGraphWithMultipleSCCs() { + int v = 6; + var graph = new ArrayList>(); + for (int i = 0; i < v; i++) { + graph.add(new ArrayList<>()); + } + graph.get(0).add(1); + graph.get(1).add(2); + graph.get(2).add(0); + graph.get(3).add(4); + graph.get(4).add(5); + graph.get(5).add(3); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); + expectedResult.add(Arrays.asList(2, 1, 0)); // SCC containing 0, 1, 2 + expectedResult.add(Arrays.asList(5, 4, 3)); // SCC containing 3, 4, 5 + assertEquals(expectedResult, actualResult); + } + + @Test + public void testDisconnectedGraph() { + int v = 7; + var graph = new ArrayList>(); + for (int i = 0; i < v; i++) { + graph.add(new ArrayList<>()); + } + graph.get(0).add(1); + graph.get(1).add(0); + graph.get(2).add(3); + graph.get(3).add(4); + graph.get(4).add(2); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); + expectedResult.add(Arrays.asList(1, 0)); // SCC containing 0, 1 + expectedResult.add(Arrays.asList(4, 3, 2)); // SCC containing 2, 3, 4 + expectedResult.add(List.of(5)); // SCC containing 5 + expectedResult.add(List.of(6)); // SCC containing 6 + assertEquals(expectedResult, actualResult); + } + + @Test + public void testSingleNodeGraph() { + int v = 1; + var graph = new ArrayList>(); + graph.add(new ArrayList<>()); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); + expectedResult.add(List.of(0)); // SCC with a single node + assertEquals(expectedResult, actualResult); + } + + @Test + public void testEmptyGraph() { + int v = 0; + var graph = new ArrayList>(); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + List> expectedResult = new ArrayList<>(); // No SCCs in an empty graph + assertEquals(expectedResult, actualResult); } }