Kruskal's Algorithm: Navigating Minimum Spanning Trees
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Introduction
Kruskal's Algorithm: Your Coding Interview Compass for Navigating Minimum Spanning Trees
Welcome to the fascinating realm of graph algorithms! Today, we're diving into Kruskal's Algorithm—a topic that often surfaces in coding interviews. Why? Because it's like a Swiss Army knife for showcasing your mastery of computer science basics.
Introduction: Why Kruskal's Algorithm?
Your Ticket to a Minimum Spanning Tree
Imagine you're an architect, and you need to build a network of roads that connects several cities but with the minimum amount of asphalt. Kruskal's algorithm helps you do just that, but for graphs!
- How: It calculates the minimum spanning tree (MST) of a connected, weighted graph.
- Why: The MST is the subset of edges that connects all vertices with the smallest total weight.

A Coding Interview Goldmine
Picture Kruskal's algorithm as a multi-level video game that tests various skills.
- How: Implementing it involves grappling with graphs, greedy algorithms, union-find data structures, and time complexity.
- Why: Navigating through these challenges proves you're well-versed in fundamental computer science concepts, making you a desirable candidate.
Background: Setting the Stage

What is a Minimum Spanning Tree?
Think of an MST as a power grid that connects all houses in a neighborhood with the least amount of wire.
- How: An MST is a tree extracted from a connected, weighted graph, where the sum of edge weights is minimized.
- Why: It ensures all vertices are connected without any cycles, optimizing the total cost.
The Real-World Magic of MSTs
Imagine you're tasked with laying down internet cables across a city or designing a circuit board. Here's where MSTs come to the rescue!
- How: MSTs have a wide range of applications in networks, circuit design, and facility location.
- Why: They allow you to connect various nodes (like cities or circuit components) while minimizing cost or resource usage.
So, ready to learn how to implement Kruskal's algorithm and ace your coding interviews? Let's roll up our sleeves and get started!
Fill In the Blank
An MST is a tree extracted from a connected, weighted graph, where the sum of edge weights is _________.
Overview to Implementation
Kruskal's Algorithm: A Hands-On Guide with Visuals
Ready to dig into Kruskal's Algorithm? Let's break it down, from the big picture to the nitty-gritty, with code snippets and ASCII diagrams to help visualize each step.
Quick Recap: The Three Pillars of Kruskal's Algorithm
Core Principles
- Empty Canvas: Start with no edges, just vertices.
- Bargain Hunt: Add edges by ascending weight.
- No Loops: Avoid cycles.
Why It Works
- Strategy: Greedy approach—grab the cheapest edge.
- Outcome: A Minimum Spanning Tree (MST) without cycles.
Step-By-Step: Building the MST
Let's use a sample graph for this walkthrough. Here's our original graph:
A--2--B
| |
4 5
| |
D--3--C
1. Sort Edges by Weight
First, list edges in ascending order by weight.
edges = [(2, 'A', 'B'), (3, 'C', 'D'), (4, 'A', 'D'), (5, 'B', 'C')]
edges.sort()
ArrayList<Edge> edges = new ArrayList<Edge>();
edges.add(new Edge(2, "A", "B"));
edges.add(new Edge(3, "C", "D"));
edges.add(new Edge(4, "A", "D"));
edges.add(new Edge(5, "B", "C"));
Collections.sort(edges);
let edges = [['2', 'A', 'B'], ['3', 'C', 'D'], ['4', 'A', 'D'], ['5', 'B', 'C']];
edges.sort();
vector<tuple<int, string, string>> edges {
{2, "A", "B"},
{3, "C", "D"},
{4, "A", "D"},
{5, "B", "C"},
};
sort(edges.begin(), edges.end());
List<Tuple<int, string, string>> edges = new List<Tuple<int, string, string>>(){
Tuple.Create(2, "A", "B"),
Tuple.Create(3, "C", "D"),
Tuple.Create(4, "A", "D"),
Tuple.Create(5, "B", "C"),
};
edges.Sort();
type Edge struct {
weight int
start, end string
}
edges := []Edge{{2, "A", "B"}, {3, "C", "D"}, {4, "A", "D"}, {5, "B", "C"}}
sort.Slice(edges, func(i, j int) bool {return edges[i].weight < edges[j].weight})
2. Add Edges While Avoiding Cycles
Go through the sorted list and add edges to the MST, skipping any that would create a cycle.
mst = set()
# For each edge in sorted list
for weight, u, v in edges:
# Check for cycle and add if safe
Set<Integer> mst = new HashSet<>();
// For each edge in sorted list
for (Edge edge : edges) {
int weight = edge.getWeight();
int u = edge.getU();
int v = edge.getV();
// Check for cycle and add if safe
}
let mst = new Set();
// For each edge in sorted list
for (let edge of edges) {
let weight = edge.weight;
let u = edge.u;
let v = edge.v;
// Check for cycle and add if safe
}
std::set<int> mst;
// For each edge in sorted list
for(auto &edge : edges){
int weight = edge.weight;
int u = edge.u;
int v = edge.v;
// Check for cycle and add if safe
}
HashSet<int> mst = new HashSet<int>();
// For each edge in sorted list
foreach (Edge edge in edges)
{
int weight = edge.Weight;
int u = edge.U;
int v = edge.V;
// Check for cycle and add if safe
}
mst := map[int]struct{}{}
// For each edge in sorted list
for _, edge := range edges {
weight := edge.Weight
u := edge.U
v := edge.V
// Check for cycle and add if safe
}
For example:
- Add edge A-B (weight 2). MST becomes:
A--B
- Next, add edge C-D (weight 3). MST becomes:
A--B
C--D
- Add A-D: This edge will connect the two separate trees and does not form a cycle.
A--B
|
D--C
- Skip B-C: This would create a cycle, so it's skipped.
3. Terminate When MST is Complete
Stop once the MST has ( V - 1 ) edges.
if len(mst) == len(vertices) - 1:
break
if (mst.size() == vertices.size() - 1) {
break;
}
if (mst.length === vertices.length - 1) {
break;
}
if (mst.size() == vertices.size() - 1) {
break;
}
if (mst.Count == vertices.Count - 1) {
break;
}
if len(mst) == len(vertices) - 1 {
break
}
MST In Action: Seeing the Pieces Come Together
As you add each edge, the MST takes shape:

After adding A-B:
text A--BAfter adding C-D:
```text A--B
C--D ```
- After adding B-C:
text A--B | D--C
With these steps, you've built an MST from scratch.
Implementing Kruskal's Algorithm
Implementing Kruskal's Algorithm: A Practical Guide
Let's start by discussing the major components you'll need.
Harness the Power of Union-Find
Think of the union-find data structure as a network of social circles, helping us keep track of connected components.
- How: Maintain disjoint sets to verify whether vertices are connected.
- Why: This enables us to quickly determine whether adding an edge will form a cycle.
# Union-Find initialization
parent = {vertex: vertex for vertex in vertices}
def find(vertex):
if parent[vertex] != vertex:
parent[vertex] = find(parent[vertex])
return parent[vertex]
def union(u, v):
root_u = find(u)
root_v = find(v)
if root_u != root_v:
parent[root_u] = root_v
// Union-Find initialization
HashMap<Character, Character> parent = new HashMap<>();
for (char vertex : vertices) {
parent.put(vertex, vertex);
}
char find(char vertex) {
if (parent.get(vertex) != vertex) {
parent.put(vertex, find(parent.get(vertex)));
}
return parent.get(vertex);
}
void union(char u, char v) {
char root_u = find(u);
char root_v = find(v);
if (root_u != root_v) {
parent.put(root_u, root_v);
}
}
// Union-Find initialization
let parent = {}
for(let vertex of vertices) {
parent[vertex] = vertex;
}
function find(vertex) {
if (parent[vertex] !== vertex) {
parent[vertex] = find(parent[vertex]);
}
return parent[vertex];
}
function union(u, v) {
let root_u = find(u);
let root_v = find(v);
if (root_u !== root_v) {
parent[root_u] = root_v;
}
}
// Union-Find initialization
std::map<char, char> parent;
for (char vertex : vertices) {
parent[vertex] = vertex;
}
char find(char vertex) {
if (parent[vertex] != vertex) {
parent[vertex] = find(parent[vertex]);
}
return parent[vertex];
}
void union(char u, char v) {
char root_u = find(u);
char root_v = find(v);
if (root_u != root_v) {
parent[root_u] = root_v;
}
}
// Union-Find initialization
Dictionary<char, char> parent = new Dictionary<char, char>();
foreach (char vertex in vertices)
{
parent[vertex] = vertex;
}
char find(char vertex)
{
if (parent[vertex] != vertex)
{
parent[vertex] = find(parent[vertex]);
}
return parent[vertex];
}
void union(char u, char v)
{
char root_u = find(u);
char root_v = find(v);
if (root_u != root_v)
{
parent[root_u] = root_v;
}
}
// Union-Find initialization
parent := make(map[rune]rune)
for _, vertex := range vertices {
parent[vertex] = vertex
}
func find(vertex rune) rune {
if parent[vertex] != vertex {
parent[vertex] = find(parent[vertex])
}
return parent[vertex]
}
func union(u, v rune) {
root_u := find(u)
root_v := find(v)
if root_u != root_v {
parent[root_u] = root_v
}
}
Storing Edges: The Priority Queue Way
Imagine a priority queue as your shopping basket, always giving you the cheapest item first.
- How: Store edges in a priority queue, sorted by weight.
- Why: This ensures we always pick the edge with the lowest weight.
import heapq
# Initialize the priority queue with edges
priority_queue = [(weight, u, v) for u, v, weight in edges]
heapq.heapify(priority_queue)
import java.util.PriorityQueue;
// Initialize the priority queue with edges
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>((a, b) -> a.weight - b.weight);
for(int i = 0; i < edges.size(); i++){
priorityQueue.add(edges.get(i));
}
let Edge = require('collections/edge');
// Initialize the priority queue with edges
let priorityQueue = new PriorityQueue(function(a, b) {
return a.weight - b.weight;
});
edges.forEach(edge => {
priorityQueue.add(edge);
});
#include <queue>
#include <vector>
// Initialize priority_queue with edges
std::priority_queue<Edge, std::vector<Edge>, compare> priority_queue;
for(Edge edge: edges) priority_queue.push(edge);
using System.Collections.Generic;
// Initialize the priority queue with edges
var priorityQueue = new PriorityQueue<Edge>();
foreach (var edge in edges)
{
priorityQueue.Add(edge);
}
import "container/heap"
// Edge is a struct of an edge
type Edge struct {
u, v, weight int
}
// Priority Queue
type PriorityQueue []*Edge
// Set up functions for heap interface
func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].weight < pq[j].weight
}
func (pq PriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueue) Push(x interface{}) {
item := x.(*Edge)
*pq = append(*pq, item)
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
old[n-1] = nil // avoid memory leak
*pq = old[0 : n-1]
return item
}
// Initialize the priority queue with edges
var pq PriorityQueue
heap.Init(&pq)
for _, edge := range edges {
heap.Push(&pq, edge)
}
The Edge-Adding Logic
It's like solving a puzzle; you only fit in the pieces that don't disrupt the existing picture.
- How: Check if adding an edge connects two different trees in the forest of disjoint sets.
- Why: This prevents cycles while ensuring connectivity.
mst = set()
while priority_queue:
weight, u, v = heapq.heappop(priority_queue)
if find(u) != find(v):
mst.add((u, v, weight))
union(u, v)
Set<Tuple> mst = new HashSet<>();
while (!priorityQueue.isEmpty()) {
Tuple tuple = priorityQueue.poll();
int weight = tuple.getWeight();
String u = tuple.getU();
String v = tuple.getV();
if (find(u) != find(v)) {
mst.add(new Tuple(u, v, weight));
union(u, v);
}
}
let mst = new Set();
while (priorityQueue.length) {
let {weight, u, v} = priorityQueue.shift();
if (find(u) !== find(v)) {
mst.add({u, v, weight});
union(u, v);
}
}
std::set<std::tuple<int, std::string, std::string>> mst;
while (!priority_queue.empty()) {
auto [weight, u, v] = priority_queue.top();
priority_queue.pop();
if (find(u) != find(v)) {
mst.insert({weight, u, v});
union(u, v);
}
}
var mst = new HashSet<(string, string, int)>();
while (priorityQueue.Count > 0)
{
var (weight, u, v) = priorityQueue.Dequeue();
if (Find(u) != Find(v))
{
mst.Add((u, v, weight));
Union(u, v);
}
}
mst := make(map[Edge]struct{})
for len(priorityQueue) > 0 {
edge := heap.Pop(&priorityQueue).(Edge)
weight := edge.weight
u := edge.u
v := edge.v
if find(u) != find(v) {
mst[Edge{u, v, weight}] = struct{}{}
union(u, v)
}
}
Optimizing the Algorithm: Performance Tweaks
Using Union by Rank
Think of union by rank as a merger between companies, where the smaller company gets absorbed into the larger one.
- How: Maintain a rank variable for each set to make the union operation faster.
- Why: This keeps the tree balanced, reducing the time complexity.
Time Complexity: How Fast is Fast?
- How: The algorithm's time complexity is $$O(E \log E)$$.
- Why: Sorting edges takes $$O(E \log E)$$ and with optimized union-find, checking for cycles is $$O(\log E)$$ per operation.
Kruskal's Algorithm Complexity Analysis
Time Complexity
- $$O(E \log E)$$: Dominated by sorting edges and union-find operations.
Space Complexity
- $$O(V)$$: Mainly for storing the parent pointers in the union-find data structure.
Pros, Cons, and Alternatives: The Final Verdict
Pros: The Good Stuff
- Conceptually Simple: The algorithm is intuitive and provides an optimal solution.
Cons: The Not-So-Good
- Potentially Slow: If you sort all edges upfront, it could be a bottleneck.
Alternatives: Meet the Cousins
- Prim's Algorithm: Another option for finding a Minimum Spanning Tree, with different trade-offs.
Kruskal Decision Flow
Before moving on, anchor the implementation to the actual decision loop: sort edges by weight, then ask Union-Find whether each edge connects two different components.
In the visual, notice the two possible outcomes for every edge: accept it if it merges components, or skip it if both endpoints already share the same root.

Kruskal State Snapshot
Before wrapping up, pause on the two moving parts working together: the sorted edge list decides what we try next, and union-find decides whether that edge safely connects two separate components.
In the visual, notice the repeated rhythm: peek smallest edge → compare roots → accept or reject → update components. Kruskal's algorithm is simple once you can see that loop.

Conclusion
Conclusion
Implementing Kruskal's algorithm demonstrates core CS abilities like working with graphs, greedy algorithms, union-find structures, and analyzing time complexity. These fundamentals are at the heart of many technical interviews.
At a high level, Kruskal's works by processing edges from lowest to highest weight, adding them to the MST one-by-one, skipping any that form cycles. This greedy approach results in the overall minimum spanning tree.
Thank you for following along with this tutorial on Kruskal's algorithm.
One Pager Cheat Sheet
One Pager Cheat Sheet
- Kruskal's Algorithm is a coding interview topic known for demonstrating mastery of computer science basics, as it calculates the minimum spanning tree (MST) of a connected, weighted graph, testing skills like understanding of graphs, greedy algorithms,
union-find data structures, and time complexity; MSTs, which connect all vertices without cycles and minimize total cost, have applications innetworks,circuit design, andfacility location. - In graph theory, a Minimum Spanning Tree (MST) is a subset of a graph's edges that connects all vertices without cycles and with the minimum total edge weight, accomplished by using a
minimizationprinciple and a greedy approach in algorithms like Kruskal's. -
Kruskal's Algorithm constructs a Minimum Spanning Tree (MST) without cycles by starting with an
empty canvas,sorting edges by weight,adding edgesthat do not create a cycle, and terminating when the MST is complete (hasV - 1 edges), with the process illustrated throughASCII diagramsandcode snippetsin multiple languages. - The guide details the implementation of Kruskal's Algorithm using union-find and priority queue data structures to prevent cycles, ensure connectivity, and always select edges with the lowest weight, demonstrating the procedure in multiple
programming languages. The algorithm's time complexity is O(E log E) due to sorting edges and checking for cycles, while its space complexity is O(V) for storing parent pointers, with an additional performance tweak option provided: union by rank which balances the tree and reduces time complexity. The pros include simplicity and optimality, while the cons suggest potential slowness, alongside an alternative: Prim's Algorithm. - - Kruskal is a repeated choice: take the next-lightest edge only when Union-Find says it will not create a cycle.
- - Kruskal = sorted edges plus union-find cycle checks until the MST has
V - 1edges. -
Implementing Kruskal's algorithm showcases essential computer science skills including working with
graphs, usinggreedy algorithmsandunion-find structures, andanalyzing time complexity, with the algorithm functioning by processing edges from lowest to highest weight to create the minimum spanning tree.