Skip to content

Commit f829082

Browse files
williamfisetclaude
andauthored
Refactor Boruvkas (#1298)
* Refactor Boruvkas: add docs, fix compareTo overflow, clean up Fix integer overflow bug in Edge.compareTo (cost - other.cost) by using Integer.compare. Add algorithm Javadoc, remove unused UnionFind methods, and simplify main example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Simplify UnionFind find to recursive implementation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove unused Edge methods, replace example with planar grid graph Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Simplify solve loop and move mst init to constructor Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Return Optional instead of null for MST and cost Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b6462bd commit f829082

2 files changed

Lines changed: 146 additions & 160 deletions

File tree

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

Lines changed: 100 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,162 @@
1+
/**
2+
* Boruvka's Minimum Spanning Tree Algorithm — Edge List
3+
*
4+
* <p>Finds the MST of a weighted undirected graph by repeatedly selecting the
5+
* cheapest outgoing edge from each connected component and merging components.
6+
*
7+
* <p>Algorithm:
8+
* <ol>
9+
* <li>Start with each node as its own component (using Union-Find).</li>
10+
* <li>For each component, find the minimum-weight edge crossing to another component.</li>
11+
* <li>Add all such cheapest edges to the MST and merge the components.</li>
12+
* <li>Repeat until only one component remains, or no more merges are possible.</li>
13+
* </ol>
14+
*
15+
* <p>If the graph is disconnected, no MST exists and the solver returns null.
16+
*
17+
* <p>Time: O(E log V)
18+
* <p>Space: O(V + E)
19+
*
20+
* @author William Fiset, william.alexandre.fiset@gmail.com
21+
*/
122
package com.williamfiset.algorithms.graphtheory;
223

3-
import java.util.*;
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
import java.util.Optional;
27+
import java.util.OptionalLong;
428

529
public class Boruvkas {
630

7-
static class Edge implements Comparable<Edge> {
31+
static class Edge {
832
int u, v, cost;
933

1034
public Edge(int u, int v, int cost) {
1135
this.u = u;
1236
this.v = v;
1337
this.cost = cost;
1438
}
15-
16-
@Override
17-
public String toString() {
18-
return String.format("%d %d, cost: %d", u, v, cost);
19-
}
20-
21-
@Override
22-
public int compareTo(Edge other) {
23-
int cmp = cost - other.cost;
24-
// Break ties by picking lexicographically smallest edge pair.
25-
if (cmp == 0) {
26-
cmp = u - other.u;
27-
if (cmp == 0) return v - other.v;
28-
return cmp;
29-
}
30-
return cmp;
31-
}
3239
}
3340

34-
// Inputs
35-
private final int n; // Number of nodes
36-
private final Edge[] graph; // Edge list
37-
38-
// Internal
41+
private final int n;
42+
private final Edge[] graph;
3943
private boolean solved;
4044
private boolean mstExists;
41-
42-
// Outputs
4345
private long minCostSum;
4446
private List<Edge> mst;
4547

4648
public Boruvkas(int n, Edge[] graph) {
47-
if (graph == null) throw new IllegalArgumentException();
49+
if (graph == null) {
50+
throw new IllegalArgumentException();
51+
}
4852
this.graph = graph;
4953
this.n = n;
54+
this.mst = new ArrayList<>();
5055
}
5156

52-
// Returns the edges used in finding the minimum spanning tree, or returns
53-
// null if no MST exists.
54-
public List<Edge> getMst() {
57+
/**
58+
* Returns the edges in the MST, or empty if the graph is disconnected.
59+
*/
60+
public Optional<List<Edge>> getMst() {
5561
solve();
56-
return mstExists ? mst : null;
62+
return mstExists ? Optional.of(mst) : Optional.empty();
5763
}
5864

59-
public Long getMstCost() {
65+
/**
66+
* Returns the total cost of the MST, or empty if the graph is disconnected.
67+
*/
68+
public OptionalLong getMstCost() {
6069
solve();
61-
return mstExists ? minCostSum : null;
70+
return mstExists ? OptionalLong.of(minCostSum) : OptionalLong.empty();
6271
}
6372

64-
// Given a graph represented as an edge list this method finds
65-
// the Minimum Spanning Tree (MST) cost if there exists
66-
// a MST, otherwise it returns null.
6773
private void solve() {
68-
if (solved) return;
74+
if (solved) {
75+
return;
76+
}
6977

70-
mst = new ArrayList<>();
7178
UnionFind uf = new UnionFind(n);
7279

7380
while (uf.components > 1) {
74-
boolean stop = true;
7581
Edge[] cheapest = new Edge[n];
7682

77-
// Find the cheapest edge for each component
83+
// For each edge, track the cheapest crossing edge for each component.
7884
for (Edge e : graph) {
7985
int root1 = uf.find(e.u);
8086
int root2 = uf.find(e.v);
81-
if (root1 == root2) continue;
82-
87+
if (root1 == root2) {
88+
continue;
89+
}
8390
if (cheapest[root1] == null || e.cost < cheapest[root1].cost) {
8491
cheapest[root1] = e;
85-
stop = false;
8692
}
8793
if (cheapest[root2] == null || e.cost < cheapest[root2].cost) {
8894
cheapest[root2] = e;
89-
stop = false;
9095
}
9196
}
9297

93-
if (stop) break;
94-
95-
// Add the cheapest edges to the MST
96-
for (int i = 0; i < n; i++) {
97-
Edge e = cheapest[i];
98-
if (e == null) {
99-
continue;
100-
}
101-
int root1 = uf.find(e.u);
102-
int root2 = uf.find(e.v);
103-
if (root1 != root2) {
104-
uf.union(root1, root2);
98+
// Merge components using their cheapest crossing edges.
99+
int prevComponents = uf.components;
100+
for (Edge e : cheapest) {
101+
if (e != null && uf.find(e.u) != uf.find(e.v)) {
102+
uf.union(e.u, e.v);
105103
mst.add(e);
106104
minCostSum += e.cost;
107105
}
108106
}
107+
108+
if (uf.components == prevComponents) {
109+
break;
110+
}
109111
}
110112

111113
mstExists = (mst.size() == n - 1);
112114
solved = true;
113115
}
114116

117+
// ==================== Main ====================
118+
119+
//
120+
// 1 7 2
121+
// 0 --------------- 1 --------------- 2 --------------- 3
122+
// | | | |
123+
// | | | |
124+
// 4 | 3 | 5 | 6 |
125+
// | | | |
126+
// | | | |
127+
// 4 --------------- 5 --------------- 6 --------------- 7
128+
// 8 2 9
129+
//
130+
// MST cost: 23
131+
//
115132
public static void main(String[] args) {
116-
117-
int n = 10, m = 18, i = 0;
118-
Edge[] g = new Edge[m];
119-
120-
// Edges are treated as undirected
121-
g[i++] = new Edge(0, 1, 5);
122-
g[i++] = new Edge(0, 3, 4);
123-
g[i++] = new Edge(0, 4, 1);
124-
g[i++] = new Edge(1, 2, 4);
125-
g[i++] = new Edge(1, 3, 2);
126-
g[i++] = new Edge(2, 7, 4);
127-
g[i++] = new Edge(2, 8, 1);
128-
g[i++] = new Edge(2, 9, 2);
129-
g[i++] = new Edge(3, 6, 11);
130-
g[i++] = new Edge(3, 7, 2);
131-
g[i++] = new Edge(4, 3, 2);
132-
g[i++] = new Edge(4, 5, 1);
133-
g[i++] = new Edge(5, 3, 5);
134-
g[i++] = new Edge(5, 6, 7);
135-
g[i++] = new Edge(6, 7, 1);
136-
g[i++] = new Edge(6, 8, 4);
137-
g[i++] = new Edge(7, 8, 6);
138-
g[i++] = new Edge(9, 8, 0);
139-
140-
Boruvkas solver = new Boruvkas(n, g);
141-
142-
Long ans = solver.getMstCost();
143-
if (ans != null) {
144-
System.out.println("MST cost: " + ans);
145-
for (Edge e : solver.getMst()) {
146-
System.out.println(e);
133+
Edge[] g = {
134+
new Edge(0, 1, 1),
135+
new Edge(1, 2, 7),
136+
new Edge(2, 3, 2),
137+
new Edge(0, 4, 4),
138+
new Edge(1, 5, 3),
139+
new Edge(2, 6, 5),
140+
new Edge(3, 7, 6),
141+
new Edge(4, 5, 8),
142+
new Edge(5, 6, 2),
143+
new Edge(6, 7, 9),
144+
};
145+
146+
Boruvkas solver = new Boruvkas(8, g);
147+
148+
OptionalLong cost = solver.getMstCost();
149+
if (cost.isPresent()) {
150+
System.out.println("MST cost: " + cost.getAsLong()); // 23
151+
for (Edge e : solver.getMst().get()) {
152+
System.out.printf("Edge %d-%d, cost: %d%n", e.u, e.v, e.cost);
147153
}
148154
} else {
149155
System.out.println("No MST exists");
150156
}
151157
}
152158

153-
// Union find data structure
159+
// Union-Find with path compression and union by size.
154160
private static class UnionFind {
155161
int components;
156162
int[] id, sz;
@@ -166,27 +172,17 @@ public UnionFind(int n) {
166172
}
167173

168174
public int find(int p) {
169-
int root = p;
170-
while (root != id[root]) root = id[root];
171-
while (p != root) { // Do path compression
172-
int next = id[p];
173-
id[p] = root;
174-
p = next;
175+
if (id[p] != p) {
176+
id[p] = find(id[p]);
175177
}
176-
return root;
177-
}
178-
179-
public boolean connected(int p, int q) {
180-
return find(p) == find(q);
181-
}
182-
183-
public int size(int p) {
184-
return sz[find(p)];
178+
return id[p];
185179
}
186180

187181
public void union(int p, int q) {
188182
int root1 = find(p), root2 = find(q);
189-
if (root1 == root2) return;
183+
if (root1 == root2) {
184+
return;
185+
}
190186
if (sz[root1] < sz[root2]) {
191187
sz[root2] += sz[root1];
192188
id[root1] = root2;

0 commit comments

Comments
 (0)