Skip to content

Commit 10870a2

Browse files
williamfisetclaude
andauthored
Refactor ConnectedComponents: rename to UnionFind, clean up (#1299)
* Refactor ConnectedComponentsAdjacencyList: rename, clean up, add docs - Rename to ConnectedComponentsUnionFind to distinguish from DFS solver - Replace Map<Integer, List<Edge>> with List<List<Integer>> adjacency list - Remove unnecessary Edge class with cost field - Move package-level UnionFind to private inner class - Add Javadoc with complexity, graph diagram, and 0-indexed component ids - Fix misleading "strongly connected" reference in old Javadoc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Simplify component IDs by reusing Union-Find roots directly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add componentSize method and comprehensive tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update README link for ConnectedComponentsUnionFind rename Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f829082 commit 10870a2

6 files changed

Lines changed: 347 additions & 171 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
207207
- [:movie_camera:](https://www.youtube.com/watch?v=oDqjPvD54Ss) [Breadth first search (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyList.java) **- O(V+E)**
208208
- [Bridges/cut edges (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java) **- O(V+E)**
209209
- [Boruvkas (adjacency list, min spanning tree algorithm)](src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java) **- O(Elog(V))**
210-
- [Find connected components (adjacency list, union find)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java) **- O(Elog(E))**
210+
- [Find connected components (adjacency list, union find)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsUnionFind.java) **- O(V+E)**
211211
- [Find connected components (adjacency list, DFS)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java) **- O(V+E)**
212212
- [Depth first search (adjacency list, iterative)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java) **- O(V+E)**
213213
- [Depth first search (adjacency list, iterative, fast stack)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java) **- O(V+E)**

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ java_binary(
7676
runtime_deps = [":graphtheory"],
7777
)
7878

79-
# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ConnectedComponentsAdjacencyList
79+
# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ConnectedComponentsUnionFind
8080
java_binary(
81-
name = "ConnectedComponentsAdjacencyList",
82-
main_class = "com.williamfiset.algorithms.graphtheory.ConnectedComponentsAdjacencyList",
81+
name = "ConnectedComponentsUnionFind",
82+
main_class = "com.williamfiset.algorithms.graphtheory.ConnectedComponentsUnionFind",
8383
runtime_deps = [":graphtheory"],
8484
)
8585

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

Lines changed: 0 additions & 167 deletions
This file was deleted.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* Connected Components — Adjacency List (Union-Find)
3+
*
4+
* <p>Finds all connected components of an undirected graph using a Union-Find
5+
* data structure. Each edge merges the two endpoint components; the final number
6+
* of disjoint sets is the component count.
7+
*
8+
* <p>For directed graphs, use Tarjan's or Kosaraju's algorithm to find
9+
* <em>strongly</em> connected components instead.
10+
*
11+
* <p>Time: O(V + E * α(V)) ≈ O(V + E)
12+
* <p>Space: O(V)
13+
*
14+
* @author William Fiset, william.alexandre.fiset@gmail.com
15+
*/
16+
package com.williamfiset.algorithms.graphtheory;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
public class ConnectedComponentsUnionFind {
22+
23+
private final int n;
24+
private final List<List<Integer>> graph;
25+
private boolean solved;
26+
private UnionFind uf;
27+
28+
public ConnectedComponentsUnionFind(List<List<Integer>> graph) {
29+
if (graph == null) {
30+
throw new IllegalArgumentException();
31+
}
32+
this.n = graph.size();
33+
this.graph = graph;
34+
}
35+
36+
/**
37+
* Returns the number of connected components.
38+
*/
39+
public int countComponents() {
40+
solve();
41+
return uf.components;
42+
}
43+
44+
/**
45+
* Returns the component id (root node) of the given node.
46+
*/
47+
public int componentId(int node) {
48+
solve();
49+
return uf.find(node);
50+
}
51+
52+
/**
53+
* Returns the size of the component that the given node belongs to.
54+
*/
55+
public int componentSize(int node) {
56+
solve();
57+
return uf.sz[uf.find(node)];
58+
}
59+
60+
private void solve() {
61+
if (solved) {
62+
return;
63+
}
64+
65+
uf = new UnionFind(n);
66+
for (int u = 0; u < n; u++) {
67+
for (int v : graph.get(u)) {
68+
uf.union(u, v);
69+
}
70+
}
71+
72+
solved = true;
73+
}
74+
75+
// ==================== Main ====================
76+
77+
//
78+
// 0 --- 1 3 --- 6
79+
// | / | |
80+
// | / | |
81+
// 2 4 9
82+
//
83+
// 5 7 --- 8 10
84+
//
85+
// Components: {0,1,2}, {3,4,6,9}, {5}, {7,8}, {10}
86+
// Count: 5
87+
//
88+
public static void main(String[] args) {
89+
int n = 11;
90+
List<List<Integer>> graph = createGraph(n);
91+
92+
addUndirectedEdge(graph, 0, 1);
93+
addUndirectedEdge(graph, 0, 2);
94+
addUndirectedEdge(graph, 1, 2);
95+
addUndirectedEdge(graph, 3, 4);
96+
addUndirectedEdge(graph, 3, 6);
97+
addUndirectedEdge(graph, 6, 9);
98+
addUndirectedEdge(graph, 7, 8);
99+
100+
ConnectedComponentsUnionFind solver =
101+
new ConnectedComponentsUnionFind(graph);
102+
103+
System.out.printf("Number of components: %d%n", solver.countComponents());
104+
105+
for (int i = 0; i < n; i++) {
106+
System.out.printf("Node %d -> component %d%n", i, solver.componentId(i));
107+
}
108+
}
109+
110+
private 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+
}
117+
118+
private static void addUndirectedEdge(List<List<Integer>> graph, int from, int to) {
119+
graph.get(from).add(to);
120+
graph.get(to).add(from);
121+
}
122+
123+
// Union-Find with path compression and union by size.
124+
private static class UnionFind {
125+
int components;
126+
private final int[] id;
127+
private final int[] sz;
128+
129+
UnionFind(int n) {
130+
components = n;
131+
id = new int[n];
132+
sz = new int[n];
133+
for (int i = 0; i < n; i++) {
134+
id[i] = i;
135+
sz[i] = 1;
136+
}
137+
}
138+
139+
int find(int p) {
140+
if (id[p] != p) {
141+
id[p] = find(id[p]);
142+
}
143+
return id[p];
144+
}
145+
146+
void union(int p, int q) {
147+
int root1 = find(p);
148+
int root2 = find(q);
149+
if (root1 == root2) {
150+
return;
151+
}
152+
if (sz[root1] < sz[root2]) {
153+
sz[root2] += sz[root1];
154+
id[root1] = root2;
155+
} else {
156+
sz[root1] += sz[root2];
157+
id[root2] = root1;
158+
}
159+
components--;
160+
}
161+
}
162+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ TEST_DEPS = [
2424

2525
# Core graphtheory tests
2626

27+
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:ConnectedComponentsUnionFindTest
28+
java_test(
29+
name = "ConnectedComponentsUnionFindTest",
30+
srcs = ["ConnectedComponentsUnionFindTest.java"],
31+
main_class = "org.junit.platform.console.ConsoleLauncher",
32+
use_testrunner = False,
33+
args = ["--select-class=com.williamfiset.algorithms.graphtheory.ConnectedComponentsUnionFindTest"],
34+
runtime_deps = JUNIT5_RUNTIME_DEPS,
35+
deps = TEST_DEPS,
36+
)
37+
2738
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BoruvkasTest
2839
java_test(
2940
name = "BoruvkasTest",

0 commit comments

Comments
 (0)