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); } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy