diff --git a/src/main/java/com/thealgorithms/graph/Edge.java b/src/main/java/com/thealgorithms/graph/Edge.java new file mode 100644 index 000000000000..279a6662b7cc --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/Edge.java @@ -0,0 +1,35 @@ +package com.thealgorithms.graph; + +/** + * Represents an edge in an undirected weighted graph. + */ +public class Edge implements Comparable { + + public final int source; + public final int destination; + public final int weight; + + /** + * Constructs an edge with given source, destination, and weight. + * + * @param source the source vertex + * @param destination the destination vertex + * @param weight the weight of the edge + */ + public Edge(final int source, final int destination, final int weight) { + this.source = source; + this.destination = destination; + this.weight = weight; + } + + /** + * Compares edges based on their weight. + * + * @param other the edge to compare with + * @return comparison result + */ + @Override + public int compareTo(final Edge other) { + return Integer.compare(this.weight, other.weight); + } +} diff --git a/src/main/java/com/thealgorithms/graph/KruskalMST.java b/src/main/java/com/thealgorithms/graph/KruskalMST.java new file mode 100644 index 000000000000..211acba79185 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/KruskalMST.java @@ -0,0 +1,95 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Implementation of Kruskal's Algorithm to find + * the Minimum Spanning Tree (MST) of a connected, + * undirected, weighted graph. + */ +public final class KruskalMST { + + private KruskalMST() { + // Utility class + } + + /** + * Finds the Minimum Spanning Tree using Kruskal's Algorithm. + * + * @param vertices number of vertices in the graph + * @param edges list of all edges in the graph + * @return list of edges forming the MST + * @throws IllegalArgumentException if vertices <= 0 + * @throws NullPointerException if edges is null + */ + public static List findMST(final int vertices, final List edges) { + if (vertices <= 0) { + throw new IllegalArgumentException("Number of vertices must be positive"); + } + + Objects.requireNonNull(edges, "Edges list must not be null"); + + final List sortedEdges = new ArrayList<>(edges); + Collections.sort(sortedEdges); + + final List mst = new ArrayList<>(); + final DisjointSetUnion dsu = new DisjointSetUnion(vertices); + + for (final Edge edge : sortedEdges) { + final int rootU = dsu.find(edge.source); + final int rootV = dsu.find(edge.destination); + + if (rootU != rootV) { + mst.add(edge); + dsu.union(rootU, rootV); + + if (mst.size() == vertices - 1) { + break; + } + } + } + + return mst; + } + + /** + * Disjoint Set Union (Union-Find) with + * path compression and union by rank. + */ + private static final class DisjointSetUnion { + + private final int[] parent; + private final int[] rank; + + private DisjointSetUnion(final int size) { + parent = new int[size]; + rank = new int[size]; + + for (int i = 0; i < size; i++) { + parent[i] = i; + rank[i] = 0; + } + } + + private int find(final int node) { + if (parent[node] != node) { + parent[node] = find(parent[node]); + } + return parent[node]; + } + + private void union(final int u, final int v) { + if (rank[u] < rank[v]) { + parent[u] = v; + } else if (rank[u] > rank[v]) { + parent[v] = u; + } else { + parent[v] = u; + rank[u]++; + } + } + } +} diff --git a/src/test/java/com/thealgorithms/graph/KruskalMSTTest.java b/src/test/java/com/thealgorithms/graph/KruskalMSTTest.java new file mode 100644 index 000000000000..9b2506affc29 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/KruskalMSTTest.java @@ -0,0 +1,78 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * Test cases for Kruskal's Minimum Spanning Tree algorithm. + */ +class KruskalMSTTest { + + @Test + void testFindMSTWithSimpleGraph() { + final int vertices = 4; + + final List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 10)); + edges.add(new Edge(0, 2, 6)); + edges.add(new Edge(0, 3, 5)); + edges.add(new Edge(1, 3, 15)); + edges.add(new Edge(2, 3, 4)); + + final List mst = KruskalMST.findMST(vertices, edges); + + assertNotNull(mst, "MST should not be null"); + assertEquals(vertices - 1, mst.size(), "MST should contain V-1 edges"); + + int totalWeight = 0; + for (final Edge edge : mst) { + totalWeight += edge.weight; + } + + assertEquals(19, totalWeight, "Total weight of MST is incorrect"); + } + + @Test + void testFindMSTWithSingleVertex() { + final int vertices = 1; + final List edges = new ArrayList<>(); + + final List mst = KruskalMST.findMST(vertices, edges); + + assertNotNull(mst, "MST should not be null"); + assertEquals(0, mst.size(), "MST of single vertex graph should be empty"); + } + + @Test + void testInvalidVertexCountThrowsException() { + final List edges = new ArrayList<>(); + + assertThrows( + IllegalArgumentException.class, + () -> KruskalMST.findMST(0, edges), + "Expected exception for non-positive vertex count" + ); + } + + @Test + void testPathCompressionScenario() { + final int vertices = 5; + + final List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 1)); + edges.add(new Edge(1, 2, 2)); + edges.add(new Edge(2, 3, 3)); + edges.add(new Edge(3, 4, 4)); + + final List mst = KruskalMST.findMST(vertices, edges); + + assertNotNull(mst); + assertEquals(vertices - 1, mst.size(), "MST should contain V-1 edges"); + } +}