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+ */
122package 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
529public 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