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)

  1. Start with a graph with only the vertices (no edges).
  2. 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 GG with edge weights ww

Output: A set of edges XX that defines a minimum spanning tree

procedure Kruskal(G,wG, w)

for all vVv \in V do

Makeset(v)Makeset(v)

X={}X = \{\}

Sort the set of edges EE in increasing order by weight

for all edges (u,v)E(u,v) \in E until X=V1|X| = |V| - 1 do

if find(u)find(v)find(u) \neq find(v) then

add(u,v)add(u,v) to XX

union(u,v)union(u,v)

Variables & Data Structures

NameTypePurpose
XSet of edgesThe growing MST — edges accepted so far
E (sorted)Sorted list of edgesThe full edge list, sorted once up front by weight, so the greedy scan just walks it left to right
π, rankDisjoint Sets (Union-Find)Tracks which component each vertex currently belongs to; used to detect cycles via find
u, vVertexThe 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) — puts v into its own singleton set
  • Find(u) — returns the name (root) of the set containing u
  • Union(u,v) — merges the sets containing u and v

Helper Functions / Operations Used

  • Makeset(v) () — initializes v as its own singleton set/component
  • find(u) () — walks parent pointers up to the root to identify u’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 containing u and v; see Union Variants for the by-rank vs. by-size tie-breaking choice that keeps this fast

Low-Level Implementation

Path compression on find is what pushes find/union down 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) and find(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

ComplexityNotes
TimeDominated by sorting the edge list; the union-find operations that follow add only , which is effectively linear
SpaceEdge list plus the π/rank arrays for the disjoint-set structure

Since , , so this is often written as .

Implementation-Dependent Variations

Data Structure ChoiceImpact on TimeImpact on SpaceNotes
Comparison sort (e.g. mergesort) for edgesGeneral-purpose; this is what dominates the overall runtime
Bucket/radix sort for edgesOnly 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/unionStill fine, but slightly worse than adding path compression
Union-Find with union by rank + path compression amortized per find/unionEffectively constant time in practice; standard choice
Union-Find with no optimization (plain linked structure) worst case per findAvoid — 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).

References / Links