🌳 Building the Best Roads: Minimum Spanning Trees
Imagine you’re the mayor of a town with many villages. You want to connect ALL villages with roads, but you have a limited budget. How do you spend the LEAST money while making sure everyone is connected?
That’s exactly what a Minimum Spanning Tree (MST) does!
🏘️ The Story: Connecting Villages
Picture 5 villages on a map. Between each pair of villages, there’s a possible road with a cost to build it. Your job? Connect all villages using the cheapest total road cost - but here’s the rule: no circular roads (that wastes money!).
graph TD A["Village A"] -->|$4| B["Village B"] A -->|$2| C["Village C"] B -->|$1| C B -->|$5| D["Village D"] C -->|$3| D D -->|$6| E["Village E"] C -->|$4| E
The Minimum Spanning Tree finds the magic combination that:
- ✅ Connects EVERY village
- ✅ Uses the LEAST total cost
- ✅ Has NO circular paths (loops)
🧠 Two Heroes: Prim & Kruskal
Think of two different builders who solve this problem their own way:
| Builder | Strategy |
|---|---|
| Prim | “I’ll start at ONE village and grow outward, always picking the cheapest next road” |
| Kruskal | “I’ll sort ALL roads by price, then pick the cheapest ones that don’t create circles” |
Both builders end up with the SAME cheapest road network!
🌱 Prim’s Algorithm: Growing a Tree
The Simple Idea
Imagine planting a tree seed in one village. The tree grows by always reaching toward the nearest unconnected village.
Step by Step
- Pick any starting village - this is your “tree”
- Look at all roads leading from your tree to villages NOT in your tree
- Pick the CHEAPEST road and add that village to your tree
- Repeat until all villages are in your tree!
🎮 Let’s Play It Out!
Starting from Village A with this graph:
A---4---B
| |
2 1
| |
C---3---D
Round 1: Tree = {A}
- Options: A→B ($4), A→C ($2)
- Pick cheapest: A→C ($2) ✅
- Tree = {A, C}
Round 2: Tree = {A, C}
- Options: A→B ($4), C→D ($3)
- Pick cheapest: C→D ($3) ✅
- Tree = {A, C, D}
Round 3: Tree = {A, C, D}
- Options: A→B ($4), D→B ($1)
- Pick cheapest: D→B ($1) ✅
- Tree = {A, C, D, B} - ALL CONNECTED!
Total Cost: $2 + $3 + $1 = $6 🎉
Java Code (Simple Version)
// Prim's Algorithm - Simple Version
int[] minCost = new int[n]; // Cost to reach each village
boolean[] inTree = new boolean[n];
Arrays.fill(minCost, Integer.MAX_VALUE);
minCost[0] = 0; // Start from village 0
for (int count = 0; count < n; count++) {
// Find cheapest village not in tree
int u = -1;
for (int v = 0; v < n; v++) {
if (!inTree[v] &&
(u == -1 || minCost[v] < minCost[u]))
u = v;
}
inTree[u] = true;
// Update costs to neighbors
for (Edge e : graph[u]) {
if (!inTree[e.to] && e.cost < minCost[e.to])
minCost[e.to] = e.cost;
}
}
⏱️ Speed Check
| Version | Time |
|---|---|
| Simple (nested loops) | O(V²) |
| With Priority Queue | O(E log V) |
Tip: Use Priority Queue version for big graphs!
⚔️ Kruskal’s Algorithm: Sorting & Picking
The Simple Idea
Imagine you have a deck of cards. Each card is a road with its cost written on it. You sort all cards from cheapest to most expensive. Then, you pick cards one by one - but ONLY if it doesn’t create a circle!
Step by Step
- Sort ALL roads from cheapest to most expensive
- Pick the cheapest road that doesn’t create a loop
- Repeat until you have (n-1) roads for n villages
🎮 Let’s Play It Out!
Roads sorted by cost:
- B-D: $1
- A-C: $2
- C-D: $3
- A-B: $4
Round 1: Pick B-D ($1) ✅ No loop! Round 2: Pick A-C ($2) ✅ No loop! Round 3: Pick C-D ($3) ✅ No loop! Now A-C-D-B connected! Round 4: Skip A-B ($4) ❌ Would create A-B-D-C-A loop!
Total Cost: $1 + $2 + $3 = $6 🎉
Same answer as Prim’s! Magic? No - just math! 🧙♂️
But Wait… How Do We Know If Something Creates a Loop?
This is where Union-Find becomes our superpower!
🔗 Union-Find: The Loop Detector
The Problem
When Kruskal picks a road A→B, he needs to know:
“Are A and B already connected through other roads?”
If yes → Adding this road creates a loop! ❌ If no → Safe to add! ✅
The Brilliant Idea: Families
Think of it like family groups:
- At first, every village is its own family (alone)
- When you connect two villages, their families merge
- Two villages in the SAME family = already connected!
🎮 Visual Example
Start: Each alone
[A] [B] [C] [D] [E]
Add edge A-C:
[A,C] [B] [D] [E] // A and C are now family!
Add edge B-D:
[A,C] [B,D] [E] // B and D are now family!
Add edge C-D:
[A,C,B,D] [E] // Families merge!
Now if we try A-B:
A and B same family? YES!
→ LOOP! Don't add!
Two Operations
| Operation | What It Does |
|---|---|
| FIND(x) | “Who is the leader of x’s family?” |
| UNION(x,y) | “Merge x’s family with y’s family” |
Basic Java Code
int[] parent = new int[n];
// Everyone starts as their own parent
for (int i = 0; i < n; i++)
parent[i] = i;
// FIND: Who is my ultimate leader?
int find(int x) {
while (parent[x] != x)
x = parent[x];
return x;
}
// UNION: Merge two families
void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
parent[rootX] = rootY;
}
// Same family? (Loop check!)
boolean connected(int x, int y) {
return find(x) == find(y);
}
🚀 Union-Find Optimizations
The basic version works, but can be SLOW with long chains. Imagine finding your great-great-great-grandparent every time!
Optimization 1: Path Compression
Problem: Long chains make FIND slow
Before: A → B → C → D → E (root)
Finding A's root = 4 steps!
Solution: When finding root, make everyone point DIRECTLY to root!
After finding A:
A → E
B → E
C → E
D → E
Next time = 1 step only!
Java Code with Path Compression
int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]); // Magic line!
return parent[x];
}
Optimization 2: Union by Rank
Problem: Merging can create tall trees
Solution: Always attach the SHORTER tree under the TALLER tree!
Rank = height of tree
Before union(A, B):
Tree A (rank 3) Tree B (rank 1)
A B
/|\
...
After: Attach B under A (not A under B!)
Java Code with Both Optimizations
int[] parent = new int[n];
int[] rank = new int[n];
// Initialize
for (int i = 0; i < n; i++) {
parent[i] = i;
rank[i] = 0;
}
// FIND with path compression
int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
}
// UNION by rank
void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return;
// Attach smaller tree under bigger
if (rank[rootX] < rank[rootY])
parent[rootX] = rootY;
else if (rank[rootX] > rank[rootY])
parent[rootY] = rootX;
else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
⚡ Speed Comparison
| Version | Time per operation |
|---|---|
| Basic | O(n) worst case |
| Path Compression only | O(log n) |
| Both optimizations | Nearly O(1)! |
The technical term is O(α(n)) where α is the inverse Ackermann function - basically constant time for any realistic input! 🤯
🏗️ Kruskal’s Complete Code
Now we can write the full algorithm!
class Edge implements Comparable<Edge> {
int from, to, cost;
public int compareTo(Edge other) {
return this.cost - other.cost;
}
}
int kruskal(List<Edge> edges, int n) {
Collections.sort(edges); // Sort by cost
// Union-Find setup
int[] parent = new int[n];
int[] rank = new int[n];
for (int i = 0; i < n; i++)
parent[i] = i;
int totalCost = 0;
int edgesUsed = 0;
for (Edge e : edges) {
int rootA = find(e.from);
int rootB = find(e.to);
if (rootA != rootB) { // No loop!
union(rootA, rootB);
totalCost += e.cost;
edgesUsed++;
if (edgesUsed == n - 1)
break; // Done!
}
}
return totalCost;
}
🎯 When to Use Which?
graph TD A["Need MST?"] --> B{Dense Graph?} B -->|Many edges| C["Use Prim&#39;s] B -->|Few edges| D[Use Kruskal&#39;s"] C --> E["With Priority Queue"] D --> F["With Union-Find"]
| Situation | Best Choice | Why |
|---|---|---|
| Dense graph (many edges) | Prim’s | Doesn’t need to sort all edges |
| Sparse graph (few edges) | Kruskal’s | Sorting is fast with few edges |
| Edges given as list | Kruskal’s | Natural fit for edge list |
| Graph as adjacency matrix | Prim’s | Easy neighbor lookup |
🎓 Key Takeaways
-
MST = Connect all nodes with minimum total edge weight, no cycles
-
Prim’s = “Grow from one node” - pick cheapest edge to new node
-
Kruskal’s = “Sort & pick” - choose cheapest edges that don’t create loops
-
Union-Find = Fast way to check if two nodes are already connected
-
Path Compression + Union by Rank = Makes Union-Find blazingly fast!
🧠 Remember This!
Prim thinks LOCAL: “What’s the cheapest way to grow MY tree?”
Kruskal thinks GLOBAL: “What’s the cheapest edge in the WHOLE graph that’s still safe?”
Both find the same answer - the Minimum Spanning Tree! 🌳✨
