Skip to content

Commit b6462bd

Browse files
williamfisetclaude
andauthored
Refactor BridgesAdjacencyList: add docs, clean up code (williamfiset#1297)
* Refactor BridgesAdjacencyList: add docs, clean up code Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Change findBridges return type from flat List<Integer> to List<int[]> Each bridge is now returned as an int[2] pair instead of two consecutive elements in a flat list. Updated both test files accordingly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Clarify findBridges Javadoc with concrete example Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Document parallel-edge limitation in BridgesAdjacencyList Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove BridgesAdjacencyListIterative and its test Redundant with the recursive BridgesAdjacencyList implementation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7985f28 commit b6462bd

File tree

6 files changed

+95
-375
lines changed

6 files changed

+95
-375
lines changed

src/main/java/com/williamfiset/algorithms/graphtheory/BUILD

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,6 @@ java_binary(
6969
runtime_deps = [":graphtheory"],
7070
)
7171

72-
# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BridgesAdjacencyListIterative
73-
java_binary(
74-
name = "BridgesAdjacencyListIterative",
75-
main_class = "com.williamfiset.algorithms.graphtheory.BridgesAdjacencyListIterative",
76-
runtime_deps = [":graphtheory"],
77-
)
78-
7972
# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ChinesePostmanProblem
8073
java_binary(
8174
name = "ChinesePostmanProblem",
Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
11
/**
2-
* Finds all the bridges on an undirected graph.
2+
* Bridge Edges (Cut Edges) — Adjacency List
33
*
4-
* <p>Test against HackerEarth online judge at:
4+
* <p>A bridge is an edge whose removal disconnects the graph (or increases
5+
* the number of connected components). This implementation uses Tarjan's
6+
* DFS-based algorithm with low-link values.
7+
*
8+
* <p>An edge (u, v) is a bridge if no vertex in the subtree rooted at v
9+
* (in the DFS tree) has a back edge to u or any ancestor of u:
10+
*
11+
* <pre> ids[u] &lt; low[v]</pre>
12+
*
13+
* <p><strong>Limitation:</strong> This implementation assumes a simple graph (no
14+
* parallel/multi-edges between the same pair of nodes). If parallel edges exist,
15+
* the algorithm may incorrectly report an edge as a bridge even though a second
16+
* edge still connects the two nodes. This is because the parent-skip logic
17+
* ({@code to == parent}) skips <em>all</em> occurrences of the parent in the
18+
* adjacency list, rather than only the single tree edge used to arrive at the
19+
* current node.
20+
*
21+
* <p>Works on disconnected graphs by running DFS from every unvisited node.
22+
*
23+
* <p>See also: {@link ArticulationPointsAdjacencyList} for finding cut vertices.
24+
*
25+
* <p>Tested against HackerEarth online judge at:
526
* https://www.hackerearth.com/practice/algorithms/graphs/articulation-points-and-bridges/tutorial
627
*
28+
* <p>Time: O(V + E)
29+
* <p>Space: O(V)
30+
*
731
* @author William Fiset, william.alexandre.fiset@gmail.com
832
*/
933
package com.williamfiset.algorithms.graphtheory;
@@ -15,64 +39,99 @@
1539

1640
public class BridgesAdjacencyList {
1741

18-
private int n, id;
19-
private int[] low, ids;
42+
private final int n;
43+
private final List<List<Integer>> graph;
2044
private boolean solved;
45+
private int id;
46+
private int[] low, ids;
2147
private boolean[] visited;
22-
private List<List<Integer>> graph;
23-
private List<Integer> bridges;
48+
private List<int[]> bridges;
2449

2550
public BridgesAdjacencyList(List<List<Integer>> graph, int n) {
26-
if (graph == null || n <= 0 || graph.size() != n) throw new IllegalArgumentException();
51+
if (graph == null || n <= 0 || graph.size() != n) {
52+
throw new IllegalArgumentException();
53+
}
2754
this.graph = graph;
2855
this.n = n;
2956
}
3057

31-
// Returns a list of pairs of nodes indicating which nodes form bridges.
32-
// The returned list is always of even length and indexes (2*i, 2*i+1) form a
33-
// pair. For example, nodes at indexes (0, 1) are a pair, (2, 3) are another
34-
// pair, etc...
35-
public List<Integer> findBridges() {
36-
if (solved) return bridges;
58+
/**
59+
* Returns a list of bridge edges. Each element is an {@code int[]} of length 2
60+
* where {@code [0]} and {@code [1]} are the node indices on either side of the bridge.
61+
* For example, if node 2 and node 5 are connected by a bridge, the entry is {@code {2, 5}}.
62+
*/
63+
public List<int[]> findBridges() {
64+
if (solved) {
65+
return bridges;
66+
}
3767

3868
id = 0;
39-
low = new int[n]; // Low link values
40-
ids = new int[n]; // Nodes ids
69+
low = new int[n];
70+
ids = new int[n];
4171
visited = new boolean[n];
42-
4372
bridges = new ArrayList<>();
4473

45-
// Finds all bridges in the graph across various connected components.
46-
for (int i = 0; i < n; i++) if (!visited[i]) dfs(i, -1, bridges);
74+
// Run DFS from each unvisited node to handle disconnected components.
75+
for (int i = 0; i < n; i++) {
76+
if (!visited[i]) {
77+
dfs(i, -1);
78+
}
79+
}
4780

4881
solved = true;
4982
return bridges;
5083
}
5184

52-
private void dfs(int at, int parent, List<Integer> bridges) {
53-
85+
private void dfs(int at, int parent) {
5486
visited[at] = true;
5587
low[at] = ids[at] = ++id;
5688

57-
for (Integer to : graph.get(at)) {
58-
if (to == parent) continue;
89+
for (int to : graph.get(at)) {
90+
if (to == parent) {
91+
continue;
92+
}
5993
if (!visited[to]) {
60-
dfs(to, at, bridges);
94+
dfs(to, at);
6195
low[at] = min(low[at], low[to]);
96+
// If no vertex in the subtree rooted at 'to' can reach 'at' or above,
97+
// then removing edge (at, to) would disconnect the graph.
6298
if (ids[at] < low[to]) {
63-
bridges.add(at);
64-
bridges.add(to);
99+
bridges.add(new int[] {at, to});
65100
}
66101
} else {
102+
// Back edge: update low-link to the earliest reachable ancestor.
67103
low[at] = min(low[at], ids[to]);
68104
}
69105
}
70106
}
71107

72-
/* Example usage: */
108+
/* Graph helpers */
73109

74-
public static void main(String[] args) {
110+
public static List<List<Integer>> createGraph(int n) {
111+
List<List<Integer>> graph = new ArrayList<>(n);
112+
for (int i = 0; i < n; i++) {
113+
graph.add(new ArrayList<>());
114+
}
115+
return graph;
116+
}
75117

118+
public static void addEdge(List<List<Integer>> graph, int from, int to) {
119+
graph.get(from).add(to);
120+
graph.get(to).add(from);
121+
}
122+
123+
// ==================== Main ====================
124+
125+
//
126+
// 0 --- 1
127+
// | /
128+
// 2 -------- 5 --- 6
129+
// | | |
130+
// 3 --- 4 8 --- 7
131+
//
132+
// Bridges: (2,3), (3,4), (2,5)
133+
//
134+
public static void main(String[] args) {
76135
int n = 9;
77136
List<List<Integer>> graph = createGraph(n);
78137

@@ -88,29 +147,10 @@ public static void main(String[] args) {
88147
addEdge(graph, 8, 5);
89148

90149
BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n);
91-
List<Integer> bridges = solver.findBridges();
92-
93-
// Prints:
94-
// Bridge between nodes: 3 and 4
95-
// Bridge between nodes: 2 and 3
96-
// Bridge between nodes: 2 and 5
97-
for (int i = 0; i < bridges.size() / 2; i++) {
98-
int node1 = bridges.get(2 * i);
99-
int node2 = bridges.get(2 * i + 1);
100-
System.out.printf("Bridge between nodes: %d and %d\n", node1, node2);
101-
}
102-
}
103-
104-
// Initialize graph with 'n' nodes.
105-
public static List<List<Integer>> createGraph(int n) {
106-
List<List<Integer>> graph = new ArrayList<>();
107-
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
108-
return graph;
109-
}
150+
List<int[]> bridges = solver.findBridges();
110151

111-
// Add undirected edge to graph.
112-
public static void addEdge(List<List<Integer>> graph, int from, int to) {
113-
graph.get(from).add(to);
114-
graph.get(to).add(from);
152+
for (int[] bridge : bridges) {
153+
System.out.printf("Bridge between nodes: %d and %d\n", bridge[0], bridge[1]);
154+
}
115155
}
116156
}

src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java

Lines changed: 0 additions & 136 deletions
This file was deleted.

src/test/java/com/williamfiset/algorithms/graphtheory/BUILD

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,6 @@ java_test(
5757
deps = TEST_DEPS,
5858
)
5959

60-
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BridgesAdjacencyListIterativeTest
61-
java_test(
62-
name = "BridgesAdjacencyListIterativeTest",
63-
srcs = ["BridgesAdjacencyListIterativeTest.java"],
64-
main_class = "org.junit.platform.console.ConsoleLauncher",
65-
use_testrunner = False,
66-
args = ["--select-class=com.williamfiset.algorithms.graphtheory.BridgesAdjacencyListIterativeTest"],
67-
runtime_deps = JUNIT5_RUNTIME_DEPS,
68-
deps = TEST_DEPS,
69-
)
70-
7160
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BridgesAdjacencyListTest
7261
java_test(
7362
name = "BridgesAdjacencyListTest",

0 commit comments

Comments
 (0)