Minimum Spanning Tree (MST)
Overview
This document provides a comprehensive guide to Minimum Spanning Trees, including detailed step-by-step visualizations of Prim’s and Kruskal’s algorithms. We’ll also explore the concept of greedy algorithms and their applications in graph theory.
What is a Minimum Spanning Tree?
A Minimum Spanning Tree (MST) of a connected, undirected, weighted graph is a subset of the graph’s edges that:
- Connects all vertices together
- Contains no cycles
- Has the minimum possible total edge weight
Key Properties
- Connected: All vertices are reachable from each other
- Acyclic: No cycles in the tree
- Minimum Weight: Sum of edge weights is minimized
- Unique: If all edge weights are distinct, the MST is unique
Visual Example
Consider the following weighted undirected graph:
A
/|\
2 | 3
/ | \
B---1---C
| |
4 5
| |
D-------E
6
Weight Matrix:
A B C D E
A 0 2 3 ∞ ∞
B 2 0 1 4 ∞
C 3 1 0 ∞ 5
D ∞ 4 ∞ 0 6
E ∞ ∞ 5 6 0
The MST for this graph would be:
A
/
2
/
B---1---C
| |
4 5
| |
D E
Total Weight: 2 + 1 + 4 + 5 = 12
Greedy Algorithm Concept
What is a Greedy Algorithm?
A greedy algorithm is an algorithmic paradigm that follows the problem-solving heuristic of making the locally optimal choice at each stage with the hope of finding a global optimum.
Key Characteristics
- Local Optimization: Makes the best choice at each step
- No Backtracking: Once a choice is made, it’s never reconsidered
- Heuristic Nature: May not always find the global optimum
- Efficiency: Often simple and fast to implement
Why Greedy Works for MST?
The greedy approach works for MST because of the cut property:
- For any cut in the graph, the minimum weight edge crossing the cut must be in the MST
- This allows us to safely add the minimum weight edge at each step
Prim’s Algorithm
Algorithm Overview
Prim’s algorithm grows the MST one vertex at a time, always adding the minimum weight edge that connects a vertex in the MST to a vertex outside it.
Step-by-Step Visualization
Initial Graph:
A(0)
/|\
2 | 3
/ | \
B---1---C
| |
4 5
| |
D-------E
6
Step 1: Start with vertex A
- Add A to MST
- Update distances: B(2), C(3), D(∞), E(∞)
- Choose minimum: B(2)
A(0)---2---B
Step 2: Add vertex B
- Add B to MST
- Update distances: C(1), D(4), E(∞)
- Choose minimum: C(1)
A(0)---2---B---1---C
Step 3: Add vertex C
- Add C to MST
- Update distances: D(∞), E(5)
- Choose minimum: D(4) (from B)
A(0)---2---B---1---C
|
4
|
D
Step 4: Add vertex D
- Add D to MST
- Update distances: E(6)
- Choose minimum: E(5) (from C)
A(0)---2---B---1---C---5---E
|
4
|
D
Final MST:
A---2---B---1---C---5---E
|
4
|
D
Total Weight: 2 + 1 + 4 + 5 = 12
Java Implementation (Prim’s Algorithm)
import java.util.*;
class PrimMST {
private int V;
private int[][] graph;
public PrimMST(int V) {
this.V = V;
graph = new int[V][V];
}
public void addEdge(int u, int v, int weight) {
graph[u][v] = weight;
graph[v][u] = weight;
}
public void primMST() {
// Array to store constructed MST
int[] parent = new int[V];
// Key values used to pick minimum weight edge
int[] key = new int[V];
// To represent set of vertices included in MST
boolean[] mstSet = new boolean[V];
// Initialize all keys as INFINITE
Arrays.fill(key, Integer.MAX_VALUE);
// Always include first vertex in MST
key[0] = 0;
parent[0] = -1;
for (int count = 0; count < V - 1; count++) {
// Pick the minimum key vertex from the set of vertices
// not yet included in MST
int u = minKey(key, mstSet);
// Add the picked vertex to the MST Set
mstSet[u] = true;
// Update key value and parent index of the adjacent
// vertices of the picked vertex
for (int v = 0; v < V; v++) {
if (graph[u][v] != 0 && !mstSet[v] &&
graph[u][v] < key[v]) {
parent[v] = u;
key[v] = graph[u][v];
}
}
}
printMST(parent);
}
private int minKey(int[] key, boolean[] mstSet) {
int min = Integer.MAX_VALUE, minIndex = -1;
for (int v = 0; v < V; v++) {
if (!mstSet[v] && key[v] < min) {
min = key[v];
minIndex = v;
}
}
return minIndex;
}
private void printMST(int[] parent) {
System.out.println("Edge \tWeight");
for (int i = 1; i < V; i++) {
System.out.println(parent[i] + " - " + i + "\t" +
graph[i][parent[i]]);
}
}
}
Kruskal’s Algorithm
Algorithm Overview
Kruskal’s algorithm sorts all edges by weight and adds them to the MST in ascending order, avoiding cycles using a disjoint-set data structure.
Step-by-Step Visualization
Initial Graph (same as before):
A
/|\
2 | 3
/ | \
B---1---C
| |
4 5
| |
D-------E
6
Step 1: Sort edges by weight
- B-C: 1
- A-B: 2
- A-C: 3
- B-D: 4
- C-E: 5
- D-E: 6
Step 2: Add edge B-C (weight 1)
- No cycle formed
- Add to MST
B---1---C
Step 3: Add edge A-B (weight 2)
- No cycle formed
- Add to MST
A---2---B---1---C
Step 4: Add edge A-C (weight 3)
- Would form cycle A-B-C-A
- Skip this edge
Step 5: Add edge B-D (weight 4)
- No cycle formed
- Add to MST
A---2---B---1---C
|
4
|
D
Step 6: Add edge C-E (weight 5)
- No cycle formed
- Add to MST
A---2---B---1---C---5---E
|
4
|
D
Step 7: Add edge D-E (weight 6)
- Would form cycle D-B-C-E-D
- Skip this edge
Final MST:
A---2---B---1---C---5---E
|
4
|
D
Total Weight: 1 + 2 + 4 + 5 = 12
Java Implementation (Kruskal’s Algorithm)
import java.util.*;
class Edge implements Comparable<Edge> {
int src, dest, weight;
public Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
@Override
public int compareTo(Edge other) {
return this.weight - other.weight;
}
}
class KruskalMST {
private int V, E;
private Edge[] edges;
public KruskalMST(int V, int E) {
this.V = V;
this.E = E;
edges = new Edge[E];
}
public void addEdge(int index, int src, int dest, int weight) {
edges[index] = new Edge(src, dest, weight);
}
// Find set of an element i (uses path compression)
private int find(int[] parent, int i) {
if (parent[i] != i) {
parent[i] = find(parent, parent[i]);
}
return parent[i];
}
// Union of two sets (uses union by rank)
private void union(int[] parent, int[] rank, int x, int y) {
int xRoot = find(parent, x);
int yRoot = find(parent, y);
if (rank[xRoot] < rank[yRoot]) {
parent[xRoot] = yRoot;
} else if (rank[xRoot] > rank[yRoot]) {
parent[yRoot] = xRoot;
} else {
parent[yRoot] = xRoot;
rank[xRoot]++;
}
}
public void kruskalMST() {
Edge[] result = new Edge[V - 1];
int e = 0; // Index for result[]
int i = 0; // Index for sorted edges
// Sort edges by weight
Arrays.sort(edges);
// Allocate memory for creating V subsets
int[] parent = new int[V];
int[] rank = new int[V];
// Create V subsets with single elements
for (int v = 0; v < V; v++) {
parent[v] = v;
rank[v] = 0;
}
while (e < V - 1 && i < E) {
Edge nextEdge = edges[i++];
int x = find(parent, nextEdge.src);
int y = find(parent, nextEdge.dest);
// If including this edge doesn't cause cycle
if (x != y) {
result[e++] = nextEdge;
union(parent, rank, x, y);
}
}
printMST(result);
}
private void printMST(Edge[] result) {
System.out.println("Edge \tWeight");
for (int i = 0; i < V - 1; i++) {
System.out.println(result[i].src + " - " + result[i].dest +
"\t" + result[i].weight);
}
}
}
Algorithm Comparison
| Aspect | Prim’s Algorithm | Kruskal’s Algorithm |
|---|---|---|
| Approach | Vertex-based | Edge-based |
| Data Structure | Priority Queue | Disjoint Set |
| Time Complexity | O(E log V) | O(E log E) |
| Space Complexity | O(V) | O(E) |
| Best For | Dense graphs | Sparse graphs |
| Implementation | More complex | Simpler |
Mathematical Foundation
Cut Property
For any cut in the graph, the minimum weight edge crossing the cut must be in the MST.
Cycle Property
For any cycle in the graph, the maximum weight edge in the cycle is not in the MST.
Proof of Correctness
Both algorithms work because they respect the cut property:
- Prim’s: Always adds the minimum weight edge crossing the cut between MST and non-MST vertices
- Kruskal’s: Always adds the minimum weight edge that doesn’t create a cycle
Applications
Real-World Applications
- Network Design: Connecting cities with minimum cable cost
- Clustering: Grouping similar data points
- Image Segmentation: Connecting pixels with similar properties
- Circuit Design: Minimizing wire length
- Water Supply: Connecting water sources to consumers
LeetCode Problems
- 1584. Min Cost to Connect All Points: Direct MST application
- 1135. Connecting Cities With Minimum Cost: MST with custom weights
- 1489. Find Critical and Pseudo-Critical Edges in Minimum Spanning Tree: Advanced MST concepts
Example: LeetCode 1584
Problem
Given an array of points where points[i] = [xi, yi] represents a point on the X-Y plane, return the minimum cost to make all points connected.
Solution using Prim’s Algorithm
class Solution {
public int minCostConnectPoints(int[][] points) {
int n = points.length;
boolean[] visited = new boolean[n];
int[] minDist = new int[n];
Arrays.fill(minDist, Integer.MAX_VALUE);
minDist[0] = 0;
int totalCost = 0;
for (int i = 0; i < n; i++) {
// Find unvisited vertex with minimum distance
int u = -1;
int min = Integer.MAX_VALUE;
for (int j = 0; j < n; j++) {
if (!visited[j] && minDist[j] < min) {
min = minDist[j];
u = j;
}
}
visited[u] = true;
totalCost += min;
// Update distances to unvisited neighbors
for (int v = 0; v < n; v++) {
if (!visited[v]) {
int dist = Math.abs(points[u][0] - points[v][0]) +
Math.abs(points[u][1] - points[v][1]);
if (dist < minDist[v]) {
minDist[v] = dist;
}
}
}
}
return totalCost;
}
}
Summary Table
| Algorithm | Time Complexity | Space Complexity | Approach | Best For |
|---|---|---|---|---|
| Prim’s | O(E log V) | O(V) | Vertex-based | Dense graphs |
| Kruskal’s | O(E log E) | O(E) | Edge-based | Sparse graphs |
| Borůvka’s | O(E log V) | O(V) | Component | Parallel |
Key Takeaways
- Greedy Nature: Both algorithms make locally optimal choices that lead to globally optimal solutions
- Cut Property: The mathematical foundation that makes greedy approach work
- Implementation Choice:
- Use Prim’s for dense graphs (E ≈ V²)
- Use Kruskal’s for sparse graphs (E « V²)
- Disjoint Set: Essential data structure for Kruskal’s algorithm
- Priority Queue: Essential data structure for Prim’s algorithm
- Applications: Network design, clustering, and optimization problems
Advanced Topics
Multiple MSTs
When edge weights are not unique, there may be multiple MSTs with the same total weight.
Second Best MST
Finding the second minimum spanning tree using edge replacement.
Dynamic MST
Maintaining MST under edge insertions and deletions.
Parallel MST Algorithms
Borůvka’s algorithm is naturally parallelizable and used in distributed systems.