Abstract
Kruskal’s Algorithm is a Greedy Algorithm that builds a Minimum Spanning Tree (MST) by sorting all edges by weight and adding each one that doesn’t create a cycle, using a disjoint-set (union-find) structure to check for cycles in per edge.
- Category: Minimum Spanning Tree / Greedy Algorithm
- Input: Undirected, connected graph with edge weights
- Output: A set of edges forming a minimum spanning tree of
- Paradigm: Greedy, global edge sort + Disjoint Sets (Union-Find)
- Typical use cases: network design on sparse graphs, MST as a subroutine (e.g. clustering, approximation algorithms), situations where the edge list is already sorted or streamed
Core Logic (High-Level)
- Start with a graph with only the vertices (no edges).
- Repeatedly add the next lightest edge that does not form a cycle.
Key Idea
Sort every edge in the graph once, globally, by weight. Then walk the sorted list and greedily take an edge whenever its two endpoints are still in different components — this is checked with
find, not by searching the graph. Skipping an edge that would form a cycle is safe because both endpoints are already connected by cheaper edges, so the skipped edge can never be the minimum crossing edge for any cut (see Cut Property).
Pseudocode (Mid-Level Implementation)
Algorithm 38 Kruskal
Input: Undirected graph with edge weights
Output: A set of edges that defines a minimum spanning tree
procedure Kruskal()
for all do
Sort the set of edges in increasing order by weight
for all edges until do
if then
to
Variables & Data Structures
| Name | Type | Purpose |
|---|---|---|
X | Set of edges | The growing MST — edges accepted so far |
E (sorted) | Sorted list of edges | The full edge list, sorted once up front by weight, so the greedy scan just walks it left to right |
π, rank | Disjoint Sets (Union-Find) | Tracks which component each vertex currently belongs to; used to detect cycles via find |
u, v | Vertex | The two endpoints of the edge currently being considered |
Kruskal’s cycle check relies on the Disjoint Sets & Up-Trees data structure — see that note for the full operations, proofs, path compression, and amortized analysis. The short version used here:
Makeset(v)— putsvinto its own singleton setFind(u)— returns the name (root) of the set containinguUnion(u,v)— merges the sets containinguandv
Helper Functions / Operations Used
Makeset(v)() — initializesvas its own singleton set/componentfind(u)() — walks parent pointers up to the root to identifyu’s current component; used as the cycle check (find(u) ≠ find(v)means adding edge(u,v)can’t create a cycle)union(u,v)() — merges the two components containinguandv; see Union Variants for the by-rank vs. by-size tie-breaking choice that keeps this fast
Low-Level Implementation
Path compression on
findis what pushesfind/uniondown to amortized each — see Path Compression and Amortized Cost Analysis for the implementation and the proof of why.
Proof of Correctness
Claim: Upon termination, is a minimum spanning tree of .
Loop Invariant: At the start of each iteration, is a subset of some minimum spanning tree of .
- Initialization: starts empty, which is trivially a subset of any MST.
- Maintenance: Consider an edge with . Let be ‘s current component (as tracked by the disjoint-set structure). The cut separates from , and crosses it. Because edges are processed in increasing weight order and no earlier (cheaper) edge crossing this cut has been added — otherwise
find(u)andfind(v)would already agree — is the cheapest edge crossing seen so far. By the Cut Property, this edge belongs to some MST, so adding it keeps a subset of some MST. - Termination: Each accepted edge merges two components into one via
union, reducing the number of components by exactly one. Starting from singleton components, the loop stops once edges have been added, at which point (for a connected graph) all vertices are in a single component.
Why it doesn’t create cycles or miss vertices: The find(u) ≠ find(v) check rejects any edge that would connect two vertices already in the same component — exactly the definition of a cycle-forming edge — so stays a forest at every step. Since is connected and every edge is eventually considered, the forest ends up spanning all of .
Time & Space Complexity Analysis
General Case
| Complexity | Notes | |
|---|---|---|
| Time | Dominated by sorting the edge list; the union-find operations that follow add only , which is effectively linear | |
| Space | Edge list plus the π/rank arrays for the disjoint-set structure |
Since , , so this is often written as .
Implementation-Dependent Variations
| Data Structure Choice | Impact on Time | Impact on Space | Notes |
|---|---|---|---|
| Comparison sort (e.g. mergesort) for edges | General-purpose; this is what dominates the overall runtime | ||
| Bucket/radix sort for edges | Only usable when weights are small bounded integers — drops the runtime to near-linear, dominated instead by the union-find term | ||
| Union-Find with union by rank only | per find/union | Still fine, but slightly worse than adding path compression | |
| Union-Find with union by rank + path compression | amortized per find/union | Effectively constant time in practice; standard choice | |
| Union-Find with no optimization (plain linked structure) | worst case per find | Avoid — makes the union-find term dominate over the sort |
Best / Worst / Average Case
- Best / Worst / Average case: All — the edge sort has to happen regardless of graph shape, and it dominates the union-find work either way. There’s a mild early exit (
until |X| = |V|-1) once the tree is complete, but it doesn’t change the worst-case bound since the sort itself already touched every edge.
Drawbacks / Constraints
- Preconditions: must be connected for the output to be a single spanning tree (otherwise the loop ends with , having produced a minimum spanning forest instead); requires the full edge list up front to sort it.
- Like Prim’s, negative edge weights are fine — Kruskal’s also only ever compares individual edge weights, never cumulative path weights, so the greedy cut-property argument still holds.
- Not suitable for: Very dense graphs, where the sort becomes expensive relative to Prim’s array-based — use Prim’s Algorithm instead when .
- Alternatives to consider: Prim’s Algorithm for dense graphs or when growing a single connected tree incrementally is more natural (e.g. streaming vertices rather than a static edge list).