|
| 1 | +package com.thealgorithms.graph; |
| 2 | + |
| 3 | +import java.util.ArrayList; |
| 4 | +import java.util.List; |
| 5 | + |
| 6 | +/** |
| 7 | + * Implementation of Tarjan's Bridge-Finding Algorithm for undirected graphs. |
| 8 | + * |
| 9 | + * <p>A <b>bridge</b> (also called a cut-edge) is an edge in an undirected graph whose removal |
| 10 | + * increases the number of connected components. Bridges represent critical links |
| 11 | + * in a network — if any bridge is removed, part of the network becomes unreachable.</p> |
| 12 | + * |
| 13 | + * <p>The algorithm performs a single Depth-First Search (DFS) traversal, tracking two |
| 14 | + * values for each vertex:</p> |
| 15 | + * <ul> |
| 16 | + * <li><b>discoveryTime</b> — the time step at which the vertex was first visited.</li> |
| 17 | + * <li><b>lowLink</b> — the smallest discovery time reachable from the subtree rooted |
| 18 | + * at that vertex (via back edges).</li> |
| 19 | + * </ul> |
| 20 | + * |
| 21 | + * <p>An edge (u, v) is a bridge if and only if {@code lowLink[v] > discoveryTime[u]}, |
| 22 | + * meaning there is no back edge from the subtree of v that can reach u or any ancestor of u.</p> |
| 23 | + * |
| 24 | + * <p>Time Complexity: O(V + E), where V is the number of vertices and E is the number of edges.</p> |
| 25 | + * <p>Space Complexity: O(V + E) for the adjacency list, discovery/low arrays, and recursion stack.</p> |
| 26 | + * |
| 27 | + * @see <a href="https://en.wikipedia.org/wiki/Bridge_(graph_theory)">Wikipedia: Bridge (graph theory)</a> |
| 28 | + */ |
| 29 | +public final class TarjanBridges { |
| 30 | + |
| 31 | + private TarjanBridges() { |
| 32 | + throw new UnsupportedOperationException("Utility class"); |
| 33 | + } |
| 34 | + |
| 35 | + /** |
| 36 | + * Finds all bridge edges in an undirected graph. |
| 37 | + * |
| 38 | + * <p>The graph is represented as an adjacency list where each vertex is identified by |
| 39 | + * an integer in the range {@code [0, vertexCount)}. For each undirected edge (u, v), |
| 40 | + * v must appear in {@code adjacencyList.get(u)} and u must appear in |
| 41 | + * {@code adjacencyList.get(v)}.</p> |
| 42 | + * |
| 43 | + * @param vertexCount the total number of vertices in the graph (must be non-negative) |
| 44 | + * @param adjacencyList the adjacency list representation of the graph; must contain |
| 45 | + * exactly {@code vertexCount} entries (one per vertex) |
| 46 | + * @return a list of bridge edges, where each bridge is represented as an {@code int[]} |
| 47 | + * of length 2 with {@code edge[0] < edge[1]}; returns an empty list if no bridges exist |
| 48 | + * @throws IllegalArgumentException if {@code vertexCount} is negative, or if |
| 49 | + * {@code adjacencyList} is null or its size does not match |
| 50 | + * {@code vertexCount} |
| 51 | + */ |
| 52 | + public static List<int[]> findBridges(int vertexCount, List<List<Integer>> adjacencyList) { |
| 53 | + if (vertexCount < 0) { |
| 54 | + throw new IllegalArgumentException("vertexCount must be non-negative"); |
| 55 | + } |
| 56 | + if (adjacencyList == null || adjacencyList.size() != vertexCount) { |
| 57 | + throw new IllegalArgumentException("adjacencyList size must equal vertexCount"); |
| 58 | + } |
| 59 | + |
| 60 | + List<int[]> bridges = new ArrayList<>(); |
| 61 | + |
| 62 | + if (vertexCount == 0) { |
| 63 | + return bridges; |
| 64 | + } |
| 65 | + |
| 66 | + BridgeFinder finder = new BridgeFinder(vertexCount, adjacencyList, bridges); |
| 67 | + |
| 68 | + // Run DFS from every unvisited vertex to handle disconnected graphs |
| 69 | + for (int i = 0; i < vertexCount; i++) { |
| 70 | + if (!finder.visited[i]) { |
| 71 | + finder.dfs(i, -1); |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + return bridges; |
| 76 | + } |
| 77 | + |
| 78 | + private static class BridgeFinder { |
| 79 | + private final List<List<Integer>> adjacencyList; |
| 80 | + private final List<int[]> bridges; |
| 81 | + private final int[] discoveryTime; |
| 82 | + private final int[] lowLink; |
| 83 | + boolean[] visited; |
| 84 | + private int timer; |
| 85 | + |
| 86 | + BridgeFinder(int vertexCount, List<List<Integer>> adjacencyList, List<int[]> bridges) { |
| 87 | + this.adjacencyList = adjacencyList; |
| 88 | + this.bridges = bridges; |
| 89 | + this.discoveryTime = new int[vertexCount]; |
| 90 | + this.lowLink = new int[vertexCount]; |
| 91 | + this.visited = new boolean[vertexCount]; |
| 92 | + this.timer = 0; |
| 93 | + } |
| 94 | + |
| 95 | + /** |
| 96 | + * Performs DFS from the given vertex, computing discovery times and low-link values, |
| 97 | + * and collects any bridge edges found. |
| 98 | + * |
| 99 | + * @param u the current vertex being explored |
| 100 | + * @param parent the parent of u in the DFS tree (-1 if u is a root) |
| 101 | + */ |
| 102 | + void dfs(int u, int parent) { |
| 103 | + visited[u] = true; |
| 104 | + discoveryTime[u] = timer; |
| 105 | + lowLink[u] = timer; |
| 106 | + timer++; |
| 107 | + |
| 108 | + for (int v : adjacencyList.get(u)) { |
| 109 | + if (!visited[v]) { |
| 110 | + dfs(v, u); |
| 111 | + lowLink[u] = Math.min(lowLink[u], lowLink[v]); |
| 112 | + |
| 113 | + if (lowLink[v] > discoveryTime[u]) { |
| 114 | + bridges.add(new int[] {Math.min(u, v), Math.max(u, v)}); |
| 115 | + } |
| 116 | + } else if (v != parent) { |
| 117 | + lowLink[u] = Math.min(lowLink[u], discoveryTime[v]); |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | +} |
0 commit comments