diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java new file mode 100644 index 00000000..18e43c49 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java @@ -0,0 +1,138 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Stack; + +/** + * Java program that implements Tarjan's Algorithm. + * @author Shivanagouda S A (https://github.com/shivu2002a) + * + */ + +/** + * 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. + + */ +public class TarjansAlgorithm { + + //Timer for tracking lowtime and insertion time + private int Time; + + private List> SCClist = new ArrayList>(); + + 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. + int lowTime[] = new int[V]; + int insertionTime[] = new int[V]; + for (int i = 0; i < V; i++) { + insertionTime[i] = -1; + lowTime[i] = -1; + } + + // To check if element is present in stack + boolean isInStack[] = new boolean[V]; + + // Store nodes during DFS + Stack st = new Stack(); + + for (int i = 0; i < V; i++) { + if (insertionTime[i] == -1) + stronglyConnCompsUtil(i, lowTime, insertionTime, isInStack, st, graph); + } + + return SCClist; + } + + private void stronglyConnCompsUtil(int u, int lowTime[], int insertionTime[], + boolean isInStack[], Stack st, List> graph) { + + // Initialize insertion time and lowTime value of current node + insertionTime[u] = Time; + lowTime[u] = Time; + Time += 1; + + //Push current node into stack + isInStack[u] = true; + st.push(u); + + int n; + + // Go through all vertices adjacent to this + Iterator i = graph.get(u).iterator(); + + while (i.hasNext()) { + n = i.next(); + + //If the adjacent node is unvisited, do DFS + if (insertionTime[n] == -1) { + stronglyConnCompsUtil(n, lowTime, insertionTime, isInStack, st, graph); + //update lowTime for the current node comparing lowtime of adj node + lowTime[u] = Math.min(lowTime[u], lowTime[n]); + } else if (isInStack[n] == true) { + //If adj node is in stack, update low + lowTime[u] = Math.min(lowTime[u], insertionTime[n]); + } + } + //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 + if (lowTime[u] == insertionTime[u]) { + int w = -1; + var scc = new ArrayList(); + + //Stack has all the nodes of the current SCC + while (w != u) { + w = st.pop(); + scc.add(w); + isInStack[w] = false; + } + SCClist.add(scc); + } + } + +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java new file mode 100644 index 00000000..2166b2ef --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java @@ -0,0 +1,72 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +public class TarjansAlgorithmTest { + + TarjansAlgorithm tarjansAlgo = new TarjansAlgorithm(); + + @Test + public void findStronglyConnectedComps(){ + var v = 5; + 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(1).add(3); + graph.get(3).add(4); + + var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph); + /* + Expected result: + 0, 1, 2 + 3 + 4 + */ + List> expectedResult = new ArrayList<>(); + + expectedResult.add(Arrays.asList(4)); + expectedResult.add(Arrays.asList(3)); + expectedResult.add(Arrays.asList(2, 1, 0)); + assertTrue(expectedResult.equals(actualResult)); + } + + @Test + public void findStronglyConnectedCompsShouldGetSingleNodes() { + //Create a adjacency list of graph + var 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); + adjList.get(3).add(4); + adjList.get(4).add(5); + adjList.get(5).add(6); + adjList.get(6).add(7); + adjList.get(7).add(0); + + List> actualResult = tarjansAlgo.stronglyConnectedComponents(n, adjList); + List> expectedResult = new ArrayList<>(); + /* + Expected result: + 7, 6, 5, 4, 3, 2, 1, 0 + */ + expectedResult.add(Arrays.asList(7, 6, 5, 4, 3, 2, 1, 0)); + assertTrue(expectedResult.equals(actualResult)); + } + +}