From f28197e94c9a36473bab1361aa37e4f33a094db3 Mon Sep 17 00:00:00 2001 From: William Fiset Date: Tue, 24 Mar 2026 21:37:44 -0700 Subject: [PATCH] Refactor TarjanSccSolver, remove TarjanAdjacencyMatrix Co-Authored-By: Claude Opus 4.6 --- .../williamfiset/algorithms/graphtheory/BUILD | 6 - .../graphtheory/TarjanAdjacencyMatrix.java | 115 ------------------ .../TarjanSccSolverAdjacencyList.java | 97 +++++++-------- 3 files changed, 43 insertions(+), 175 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD index 4a5e0ef1f..111ce2c23 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BUILD @@ -181,12 +181,6 @@ java_binary( runtime_deps = [":graphtheory"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:TarjanAdjacencyMatrix -java_binary( - name = "TarjanAdjacencyMatrix", - main_class = "com.williamfiset.algorithms.graphtheory.TarjanAdjacencyMatrix", - runtime_deps = [":graphtheory"], -) # bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:TarjanSccSolverAdjacencyList java_binary( diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java deleted file mode 100644 index a053ec2cc..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * An implementation of Tarjan's SCC algorithm for a directed graph. Time Complexity: O(V^2) - * - * @author Micah Stairs - */ -package com.williamfiset.algorithms.graphtheory; - -import java.util.*; - -class TarjanAdjacencyMatrix { - - public static void main(String[] args) { - - // As an example we create a graph with four strongly connected components - - final int NUM_NODES = 10; - - boolean[][] adjMatrix = new boolean[NUM_NODES][NUM_NODES]; - - // SCC 1 with nodes 0,1,2 - adjMatrix[0][1] = true; - adjMatrix[1][2] = true; - adjMatrix[2][0] = true; - - // SCC 2 with nodes 3,4,5,6 - adjMatrix[5][4] = true; - adjMatrix[5][6] = true; - adjMatrix[3][5] = true; - adjMatrix[4][3] = true; - adjMatrix[4][5] = true; - adjMatrix[6][4] = true; - - // SCC 3 with nodes 7,8 - adjMatrix[7][8] = true; - adjMatrix[8][7] = true; - - // SCC 4 is node 9 all alone by itself - // Add a few more edges to make things interesting - adjMatrix[1][5] = true; - adjMatrix[1][7] = true; - adjMatrix[2][7] = true; - adjMatrix[6][8] = true; - adjMatrix[9][8] = true; - adjMatrix[9][4] = true; - - Tarjan sccs = new Tarjan(adjMatrix); - - System.out.println( - "Strong connected component count: " + sccs.countStronglyConnectedComponents()); - System.out.println( - "Strong connected components:\n" + Arrays.toString(sccs.getStronglyConnectedComponents())); - - // Output: - // Strong connected component count: 4 - // Strong connected components: - // [2, 2, 2, 1, 1, 1, 1, 0, 0, 3] - - } - - // Tarjan is used to find/count the Strongly Connected - // Components (SCCs) in a directed graph in O(V+E). - static class Tarjan { - - private int n, pre, count = 0; - private int[] id, low; - private boolean[] marked; - private boolean[][] adj; - private Stack stack = new Stack<>(); - - // Tarjan input requires an NxN adjacency matrix - public Tarjan(boolean[][] adj) { - n = adj.length; - this.adj = adj; - marked = new boolean[n]; - id = new int[n]; - low = new int[n]; - for (int u = 0; u < n; u++) if (!marked[u]) dfs(u); - } - - private void dfs(int u) { - marked[u] = true; - low[u] = pre++; - int min = low[u]; - stack.push(u); - for (int v = 0; v < n; v++) { - if (adj[u][v]) { - if (!marked[v]) dfs(v); - if (low[v] < min) min = low[v]; - } - } - if (min < low[u]) { - low[u] = min; - return; - } - int v; - do { - v = stack.pop(); - id[v] = count; - low[v] = n; - } while (v != u); - count++; - } - - // Returns the id array with the strongly connected components. - // If id[i] == id[j] then nodes i and j are part of the same strongly connected component. - public int[] getStronglyConnectedComponents() { - return id.clone(); - } - - // Returns the number of strongly connected components in this graph - public int countStronglyConnectedComponents() { - return count; - } - } -} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java index 18f547b43..9e1a6bb0e 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java @@ -1,5 +1,5 @@ /** - * An implementation of Tarjan's Strongly Connected Components algorithm using an adjacency list. + * Implementation of Tarjan's Strongly Connected Components algorithm using an adjacency list. * *

Verified against: * @@ -9,7 +9,9 @@ *

  • https://www.hackerearth.com/practice/algorithms/graphs/strongly-connected-components/tutorial * * - *

    Time complexity: O(V+E) + *

    Time: O(V + E) + * + *

    Space: O(V) * * @author William Fiset, william.alexandre.fiset@gmail.com */ @@ -21,51 +23,62 @@ public class TarjanSccSolverAdjacencyList { - private int n; - private List> graph; + private final int n; + private final List> graph; private boolean solved; private int sccCount, id; - private boolean[] visited; + private boolean[] onStack; private int[] ids, low, sccs; private Deque stack; private static final int UNVISITED = -1; + /** + * Creates a Tarjan SCC solver for the given directed graph. + * + * @param graph adjacency list representation of a directed graph. + * @throws IllegalArgumentException if the graph is null. + */ public TarjanSccSolverAdjacencyList(List> graph) { - if (graph == null) throw new IllegalArgumentException("Graph cannot be null."); + if (graph == null) + throw new IllegalArgumentException("Graph cannot be null."); n = graph.size(); this.graph = graph; } - // Returns the number of strongly connected components in the graph. + /** Returns the number of strongly connected components in the graph. */ public int sccCount() { - if (!solved) solve(); + if (!solved) + solve(); return sccCount; } - // Get the connected components of this graph. If two indexes - // have the same value then they're in the same SCC. + /** + * Returns the SCC id for each node. If two nodes have the same value, they belong to the same + * SCC. + */ public int[] getSccs() { - if (!solved) solve(); + if (!solved) + solve(); return sccs; } + /** Runs Tarjan's algorithm. */ public void solve() { - if (solved) return; + if (solved) + return; ids = new int[n]; low = new int[n]; sccs = new int[n]; - visited = new boolean[n]; + onStack = new boolean[n]; stack = new ArrayDeque<>(); Arrays.fill(ids, UNVISITED); - for (int i = 0; i < n; i++) { - if (ids[i] == UNVISITED) { + for (int i = 0; i < n; i++) + if (ids[i] == UNVISITED) dfs(i); - } - } solved = true; } @@ -73,59 +86,41 @@ public void solve() { private void dfs(int at) { ids[at] = low[at] = id++; stack.push(at); - visited[at] = true; + onStack[at] = true; for (int to : graph.get(at)) { - if (ids[to] == UNVISITED) { + if (ids[to] == UNVISITED) dfs(to); - } - if (visited[to]) { + if (onStack[to]) low[at] = min(low[at], low[to]); - } - /* - TODO(william): investigate whether the proper way to update the lowlinks - is the following bit of code. From my experience this doesn't seem to - matter if the output is placed in a separate output array, but this needs - further investigation. - - if (ids[to] == UNVISITED) { - dfs(to); - low[at] = min(low[at], low[to]); - } - if (visited[to]) { - low[at] = min(low[at], ids[to]); - } - */ - } - // On recursive callback, if we're at the root node (start of SCC) - // empty the seen stack until back to root. + // If we're at the root of an SCC, pop all nodes in this component off the stack. if (ids[at] == low[at]) { for (int node = stack.pop(); ; node = stack.pop()) { - visited[node] = false; + onStack[node] = false; sccs[node] = sccCount; - if (node == at) break; + if (node == at) + break; } sccCount++; } } - // Initializes adjacency list with n nodes. + /** Creates an adjacency list with n nodes. */ public static List> createGraph(int n) { List> graph = new ArrayList<>(n); - for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); + for (int i = 0; i < n; i++) + graph.add(new ArrayList<>()); return graph; } - // Adds a directed edge from node 'from' to node 'to' + /** Adds a directed edge from node 'from' to node 'to'. */ public static void addEdge(List> graph, int from, int to) { graph.get(from).add(to); } - /* Example usage: */ - - public static void main(String[] arg) { + public static void main(String[] args) { int n = 8; List> graph = createGraph(n); @@ -148,15 +143,9 @@ public static void main(String[] arg) { int[] sccs = solver.getSccs(); Map> multimap = new HashMap<>(); for (int i = 0; i < n; i++) { - if (!multimap.containsKey(sccs[i])) multimap.put(sccs[i], new ArrayList<>()); - multimap.get(sccs[i]).add(i); + multimap.computeIfAbsent(sccs[i], k -> new ArrayList<>()).add(i); } - // Prints: - // Number of Strongly Connected Components: 3 - // Nodes: [0, 1, 2] form a Strongly Connected Component. - // Nodes: [3, 7] form a Strongly Connected Component. - // Nodes: [4, 5, 6] form a Strongly Connected Component. System.out.printf("Number of Strongly Connected Components: %d\n", solver.sccCount()); for (List scc : multimap.values()) { System.out.println("Nodes: " + scc + " form a Strongly Connected Component.");