Skip to content

Commit 87ef61f

Browse files
feat: Add Kruskal's Algorithm to Greedy Algorithms section
- Implemented Kruskal's MST algorithm with efficient Union-Find - Added comprehensive JUnit tests covering edge cases - Includes proper documentation and complexity analysis - Resolves #7067
1 parent fba6292 commit 87ef61f

3 files changed

Lines changed: 297 additions & 93 deletions

File tree

src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java

Lines changed: 0 additions & 93 deletions
This file was deleted.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package com.thealgorithms.greedyalgorithms;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* Implementation of Kruskal's Algorithm for finding the Minimum Spanning Tree (MST)
8+
* of a connected, undirected, weighted graph.
9+
*
10+
* <p>Kruskal's algorithm is a greedy algorithm that finds an MST by:
11+
* <ol>
12+
* <li>Sorting all edges by weight in ascending order</li>
13+
* <li>Iteratively adding edges with minimum weight that don't create a cycle</li>
14+
* <li>Using Union-Find (Disjoint Set Union) to efficiently detect cycles</li>
15+
* </ol>
16+
*
17+
* <p><strong>Time Complexity:</strong> O(E log E) or O(E log V), where E is the number of edges
18+
* and V is the number of vertices. Sorting edges dominates the complexity.
19+
*
20+
* <p><strong>Space Complexity:</strong> O(V) for the Union-Find data structure.
21+
*
22+
* @author guillermolara01
23+
*/
24+
public final class KruskalAlgorithm {
25+
26+
private KruskalAlgorithm() {
27+
// Utility class, prevent instantiation
28+
}
29+
30+
/**
31+
* Represents a weighted edge in an undirected graph.
32+
*/
33+
public static class Edge implements Comparable<Edge> {
34+
private final int source;
35+
private final int destination;
36+
private final int weight;
37+
38+
/**
39+
* Creates an edge with the specified endpoints and weight.
40+
*
41+
* @param source the source vertex
42+
* @param destination the destination vertex
43+
* @param weight the weight of the edge
44+
*/
45+
public Edge(int source, int destination, int weight) {
46+
this.source = source;
47+
this.destination = destination;
48+
this.weight = weight;
49+
}
50+
51+
public int getSource() {
52+
return source;
53+
}
54+
55+
public int getDestination() {
56+
return destination;
57+
}
58+
59+
public int getWeight() {
60+
return weight;
61+
}
62+
63+
@Override
64+
public int compareTo(Edge other) {
65+
return Integer.compare(this.weight, other.weight);
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return String.format("(%d -> %d, weight: %d)", source, destination, weight);
71+
}
72+
}
73+
74+
/**
75+
* Union-Find (Disjoint Set Union) data structure with path compression
76+
* and union by rank for efficient cycle detection.
77+
*/
78+
private static class UnionFind {
79+
private final int[] parent;
80+
private final int[] rank;
81+
82+
/**
83+
* Initializes the Union-Find structure with n elements.
84+
*
85+
* @param n the number of elements
86+
*/
87+
UnionFind(int n) {
88+
parent = new int[n];
89+
rank = new int[n];
90+
for (int i = 0; i < n; i++) {
91+
parent[i] = i;
92+
rank[i] = 0;
93+
}
94+
}
95+
96+
/**
97+
* Finds the representative (root) of the set containing element x.
98+
* Uses path compression for optimization.
99+
*
100+
* @param x the element to find
101+
* @return the representative of the set
102+
*/
103+
int find(int x) {
104+
if (parent[x] != x) {
105+
parent[x] = find(parent[x]); // Path compression
106+
}
107+
return parent[x];
108+
}
109+
110+
/**
111+
* Unites the sets containing elements x and y.
112+
* Uses union by rank for optimization.
113+
*
114+
* @param x the first element
115+
* @param y the second element
116+
* @return true if the sets were merged, false if they were already in the same set
117+
*/
118+
boolean union(int x, int y) {
119+
int rootX = find(x);
120+
int rootY = find(y);
121+
122+
if (rootX == rootY) {
123+
return false; // Already in the same set (would create a cycle)
124+
}
125+
126+
// Union by rank
127+
if (rank[rootX] < rank[rootY]) {
128+
parent[rootX] = rootY;
129+
} else if (rank[rootX] > rank[rootY]) {
130+
parent[rootY] = rootX;
131+
} else {
132+
parent[rootY] = rootX;
133+
rank[rootX]++;
134+
}
135+
return true;
136+
}
137+
}
138+
139+
/**
140+
* Finds the Minimum Spanning Tree of a graph using Kruskal's algorithm.
141+
*
142+
* @param vertices the number of vertices in the graph
143+
* @param edges the list of edges in the graph
144+
* @return a list of edges that form the MST
145+
* @throws IllegalArgumentException if vertices is less than 1 or edges is null
146+
*/
147+
public static List<Edge> kruskal(int vertices, List<Edge> edges) {
148+
if (vertices < 1) {
149+
throw new IllegalArgumentException("Number of vertices must be at least 1");
150+
}
151+
if (edges == null) {
152+
throw new IllegalArgumentException("Edges list cannot be null");
153+
}
154+
155+
List<Edge> mst = new ArrayList<>();
156+
if (edges.isEmpty()) {
157+
return mst;
158+
}
159+
160+
// Sort edges by weight
161+
edges.sort(Edge::compareTo);
162+
163+
UnionFind uf = new UnionFind(vertices);
164+
165+
// Iterate through sorted edges
166+
for (Edge edge : edges) {
167+
// If adding this edge doesn't create a cycle, include it in MST
168+
if (uf.union(edge.getSource(), edge.getDestination())) {
169+
mst.add(edge);
170+
171+
// MST is complete when we have (V-1) edges
172+
if (mst.size() == vertices - 1) {
173+
break;
174+
}
175+
}
176+
}
177+
178+
return mst;
179+
}
180+
181+
/**
182+
* Calculates the total weight of the MST.
183+
*
184+
* @param mst the list of edges in the MST
185+
* @return the total weight of all edges in the MST
186+
*/
187+
public static int getMSTWeight(List<Edge> mst) {
188+
return mst.stream().mapToInt(Edge::getWeight).sum();
189+
}
190+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.thealgorithms.greedyalgorithms;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import org.junit.jupiter.api.Test;
10+
11+
class KruskalAlgorithmTest {
12+
13+
@Test
14+
void testSimpleGraph() {
15+
// Graph with 4 vertices
16+
// 1
17+
// / | \
18+
// 2 3 4
19+
// 0---1---2
20+
// | |
21+
// 3-------4
22+
List<KruskalAlgorithm.Edge> edges = new ArrayList<>();
23+
edges.add(new KruskalAlgorithm.Edge(0, 1, 10));
24+
edges.add(new KruskalAlgorithm.Edge(0, 2, 6));
25+
edges.add(new KruskalAlgorithm.Edge(0, 3, 5));
26+
edges.add(new KruskalAlgorithm.Edge(1, 3, 15));
27+
edges.add(new KruskalAlgorithm.Edge(2, 3, 4));
28+
29+
List<KruskalAlgorithm.Edge> mst = KruskalAlgorithm.kruskal(4, edges);
30+
31+
assertEquals(3, mst.size(), "MST should have V-1 edges");
32+
assertEquals(19, KruskalAlgorithm.getMSTWeight(mst), "Total MST weight should be 19");
33+
}
34+
35+
@Test
36+
void testDisconnectedGraph() {
37+
// Two disconnected components
38+
List<KruskalAlgorithm.Edge> edges = new ArrayList<>();
39+
edges.add(new KruskalAlgorithm.Edge(0, 1, 1));
40+
edges.add(new KruskalAlgorithm.Edge(2, 3, 2));
41+
42+
List<KruskalAlgorithm.Edge> mst = KruskalAlgorithm.kruskal(4, edges);
43+
44+
assertEquals(2, mst.size(), "MST should include both components");
45+
assertEquals(3, KruskalAlgorithm.getMSTWeight(mst), "Total MST weight should be 3");
46+
}
47+
48+
@Test
49+
void testSingleVertex() {
50+
List<KruskalAlgorithm.Edge> edges = new ArrayList<>();
51+
List<KruskalAlgorithm.Edge> mst = KruskalAlgorithm.kruskal(1, edges);
52+
53+
assertTrue(mst.isEmpty(), "MST of single vertex should be empty");
54+
assertEquals(0, KruskalAlgorithm.getMSTWeight(mst));
55+
}
56+
57+
@Test
58+
void testCompleteGraph() {
59+
// Complete graph with 4 vertices (K4)
60+
List<KruskalAlgorithm.Edge> edges = new ArrayList<>();
61+
edges.add(new KruskalAlgorithm.Edge(0, 1, 1));
62+
edges.add(new KruskalAlgorithm.Edge(0, 2, 2));
63+
edges.add(new KruskalAlgorithm.Edge(0, 3, 3));
64+
edges.add(new KruskalAlgorithm.Edge(1, 2, 4));
65+
edges.add(new KruskalAlgorithm.Edge(1, 3, 5));
66+
edges.add(new KruskalAlgorithm.Edge(2, 3, 6));
67+
68+
List<KruskalAlgorithm.Edge> mst = KruskalAlgorithm.kruskal(4, edges);
69+
70+
assertEquals(3, mst.size(), "MST should have V-1 edges");
71+
assertEquals(6, KruskalAlgorithm.getMSTWeight(mst), "Total MST weight should be 6 (1+2+3)");
72+
}
73+
74+
@Test
75+
void testEqualWeights() {
76+
// Graph where all edges have the same weight
77+
List<KruskalAlgorithm.Edge> edges = new ArrayList<>();
78+
edges.add(new KruskalAlgorithm.Edge(0, 1, 1));
79+
edges.add(new KruskalAlgorithm.Edge(1, 2, 1));
80+
edges.add(new KruskalAlgorithm.Edge(2, 0, 1));
81+
82+
List<KruskalAlgorithm.Edge> mst = KruskalAlgorithm.kruskal(3, edges);
83+
84+
assertEquals(2, mst.size(), "MST should have V-1 edges");
85+
assertEquals(2, KruskalAlgorithm.getMSTWeight(mst), "Total MST weight should be 2");
86+
}
87+
88+
@Test
89+
void testEmptyGraph() {
90+
List<KruskalAlgorithm.Edge> edges = new ArrayList<>();
91+
List<KruskalAlgorithm.Edge> mst = KruskalAlgorithm.kruskal(5, edges);
92+
93+
assertTrue(mst.isEmpty(), "MST of empty graph should be empty");
94+
}
95+
96+
@Test
97+
void testInvalidVertexCount() {
98+
List<KruskalAlgorithm.Edge> edges = new ArrayList<>();
99+
assertThrows(IllegalArgumentException.class, () -> KruskalAlgorithm.kruskal(0, edges));
100+
assertThrows(IllegalArgumentException.class, () -> KruskalAlgorithm.kruskal(-1, edges));
101+
}
102+
103+
@Test
104+
void testNullEdges() {
105+
assertThrows(IllegalArgumentException.class, () -> KruskalAlgorithm.kruskal(5, null));
106+
}
107+
}

0 commit comments

Comments
 (0)