From c6571382d9d816a93ded8b63b888b2ee905ccf41 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sat, 26 Oct 2024 23:21:13 +0530
Subject: [PATCH] Enhance docs, add tests in `TarjansAlgorithm` (#5970)
---
.../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 de5004425..91974ba13 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.List;
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:
+ *
+ * - DFS Search: A depth-first search (DFS) is performed on the graph to generate a DFS tree.
+ * - Identification of SCCs: SCCs correspond to subtrees within this DFS tree.
+ * - Low-Link Values: For each node, a low-link value is maintained, which indicates the earliest visited
+ * vertex (the one with the minimum insertion time) that can be reached from that subtree.
+ * - Stack Usage: Nodes are stored in a stack during DFS. When an SCC is identified, nodes are popped from
+ * the stack until the head of the SCC is reached.
+ *
+ *
+ *
+ * Example of a directed graph:
+ *
+ * 0 --------> 1 -------> 3 --------> 4
+ * ^ /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * V
+ * 2
+ *
+ *
+ *
+ * For the above graph, the SCC list is as follows:
+ *
+ * - 1, 2, 0
+ * - 3
+ * - 4
+ *
+ * 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 class TarjansAlgorithm {
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 class TarjansAlgorithm {
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 dc81d99dd..314cc4158 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 @@ import org.junit.jupiter.api.Test;
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 class TarjansAlgorithmTest {
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 class TarjansAlgorithmTest {
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);
}
}