mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-05 16:27:33 +08:00
Enhance docs, add tests in TarjansAlgorithm
(#5970)
This commit is contained in:
@ -5,66 +5,73 @@ import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Java program that implements Tarjan's Algorithm.
|
||||
* @author <a href="https://github.com/shivu2002a">Shivanagouda S A</a>
|
||||
* Java program that implements Tarjan's Algorithm to find Strongly Connected Components (SCCs) in a directed graph.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <h3>Algorithm Overview:</h3>
|
||||
* <ul>
|
||||
* <li>DFS Search: A depth-first search (DFS) is performed on the graph to generate a DFS tree.</li>
|
||||
* <li>Identification of SCCs: SCCs correspond to subtrees within this DFS tree.</li>
|
||||
* <li>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.</li>
|
||||
* <li>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.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Example of a directed graph:
|
||||
* <pre>
|
||||
* 0 --------> 1 -------> 3 --------> 4
|
||||
* ^ /
|
||||
* | /
|
||||
* | /
|
||||
* | /
|
||||
* | /
|
||||
* | /
|
||||
* | /
|
||||
* | /
|
||||
* | /
|
||||
* | /
|
||||
* V
|
||||
* 2
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* For the above graph, the SCC list is as follows:
|
||||
* <ul>
|
||||
* <li>1, 2, 0</li>
|
||||
* <li>3</li>
|
||||
* <li>4</li>
|
||||
* </ul>
|
||||
* The order of nodes in an SCC does not matter as they form cycles.
|
||||
*
|
||||
* <h3>Comparison with Kosaraju's Algorithm:</h3>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
public class TarjansAlgorithm {
|
||||
|
||||
// Timer for tracking lowtime and insertion time
|
||||
// Timer for tracking low time and insertion time
|
||||
private int time;
|
||||
|
||||
private final List<List<Integer>> sccList = new ArrayList<List<Integer>>();
|
||||
// List to store all strongly connected components
|
||||
private final List<List<Integer>> 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<List<Integer>> stronglyConnectedComponents(int v, List<List<Integer>> 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<Integer> st = new Stack<Integer>();
|
||||
// Stack to hold nodes during DFS
|
||||
Stack<Integer> 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<Integer> st, List<List<Integer>> 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<Integer>();
|
||||
List<Integer> 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);
|
||||
|
@ -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<List<Integer>>();
|
||||
for (int i = 0; i < v; i++) {
|
||||
graph.add(new ArrayList<>());
|
||||
@ -32,23 +32,20 @@ public class TarjansAlgorithmTest {
|
||||
4
|
||||
*/
|
||||
List<List<Integer>> 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<List<Integer>>(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<List<Integer>>();
|
||||
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<List<Integer>> 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<List<Integer>>();
|
||||
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<List<Integer>> 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<List<Integer>>();
|
||||
graph.add(new ArrayList<>());
|
||||
|
||||
var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph);
|
||||
List<List<Integer>> 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<List<Integer>>();
|
||||
|
||||
var actualResult = tarjansAlgo.stronglyConnectedComponents(v, graph);
|
||||
List<List<Integer>> expectedResult = new ArrayList<>(); // No SCCs in an empty graph
|
||||
assertEquals(expectedResult, actualResult);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user