DSA DSA In Depth
0%
lesson mstkruskalprim

Minimum Spanning Trees: Cut & Cycle Optimality Properties, Kruskal's, Prim's & Borůvka's

7 min read Module 5.2: Disjoint Sets, Minimum Spanning Trees & Shortest Paths Updated Oct 23, 2026
📄 Cheat Sheet
Minimum Spanning Tree Algorithms (Kruskal's & Prim's)
William Fiset Minimum Spanning Tree Algorithms (Kruskal's & Prim's) Watch on YouTube ↗

Explains the Cut and Cycle optimality properties and contrasts edge-centric Kruskal's with vertex-centric Prim's.

0:00 / --:--

The Two Fundamental MST Optimality Laws

Robert Tarjan termed these the Blue Rule (Cut Property) and Red Rule (Cycle Property): applying them in any order colors exactly $|V| - 1$ edges Blue (in the MST).

Formal Theorems — Cut Property & Cycle Property
  1. Cut Property (Inclusion): Let $S \subset V$ ($\emptyset \neq S \neq V$) be any cut of $G$, and let $e = (u, v)$ with $u \in S, v \in V \setminus S$ be a strictly minimum-weight edge crossing the cut $(S, V \setminus S)$. Then $e$ belongs to every MST of $G$ (if ties exist, $e$ belongs to some MST).
    Exchange Proof: Suppose an MST $T$ omits $e$. Adding $e$ to $T$ creates a unique fundamental cycle $C$ that must cross the cut $(S, V \setminus S)$ at least one other time via some edge $e' \in T$ with $w(e') \ge w(e)$. Replacing $e'$ with $e$ yields a spanning tree $T' = T \setminus \{e'\} \cup \{e\}$ with $w(T') = w(T) + w(e) - w(e') \le w(T)$.
  2. Cycle Property (Exclusion): Let $C$ be any simple cycle in $G$, and let $e_{\max} \in C$ be a strictly maximum-weight edge on $C$. Then $e_{\max}$ cannot belong to any MST of $G$.
  3. Minimax Path Corollary: For any two vertices $x, y \in V$, the unique path between $x$ and $y$ inside an MST $T$ minimizes the maximum edge weight $\max_{e \in P} w(e)$ over all paths $P$ in $G$.

Comparing Kruskal's, Prim's, and Borůvka's Algorithms

  • Kruskal's Algorithm ($O(E \log E)$): Sorts all edges by weight and adds $e = (u, v)$ if $\text{dsu.unite}(u, v)$ succeeds. When $e$ connects components $C_u$ and $C_v$, $e$ is the lightest edge crossing the cut $(C_u, V \setminus C_u)$. Ideal for sparse graphs where edges are already in a flat buffer.
  • Prim's Algorithm ($O(E \log V)$ or $O(V^2)$): Grows a single component $S$ starting from root $0$, repeatedly extracting the lightest edge crossing $(S, V \setminus S)$. On dense graphs ($E \approx V^2$), an array-based Prim without a heap runs in optimal $O(V^2)$ time (beating $O(V^2 \log V)$ binary heaps!).
  • Borůvka's Algorithm ($O(E \log V)$ in $\le \lceil \log_2 V \rceil$ rounds): In each round, every current component simultaneously selects its cheapest outgoing edge and merges. Because every active component merges with at least one neighbor, the number of components at least halves each round ($\le \log_2 V$ rounds). Essential for parallel/GPU MST and implicit graphs (e.g., finding the MST where $w(u, v) = a_u \oplus a_v$ using a binary Trie).
Cut Exchange Argument: Adding e = (u, v) & Removing e' on Cut (S, V S) Cut S V S u v x y e (min cut weight = 3) e' in T (weight = 9 ≥ 3)
flowchart LR Graph["Weighted Undirected Graph G=(V,E)"] --> Density{"Dense (E ≈ V²)?"} Density -- "Yes" --> PrimArr["Array-Based Prim: O(V²) time, O(V) space"] Density -- "No (Sparse)" --> Implicit{"Implicit / Parallel?"} Implicit -- "No" --> Krusk["Kruskal + DSU: O(E log E)"] Implicit -- "Yes" --> Boruvka["Borůvka: <= log₂(V) rounds of cheapest component exits"]
Minimum Spanning Tree: Kruskal’s DSU Scan, Cut & Cycle Optimality
O(E log E) · Cut & Cycle Properties

Trace Kruskal’s weight-sorted edge scan alongside DSU component merging: accept every light edge crossing a cut (find(u) != find(v)) and reject any cycle-closing heaviest edge (find(u) == find(v)).

Step 1 / 9
Custom Input:
Timeline

Invariant & State

Accepted (v0—v1, w=1) and (v1—v2, w=2) by Cut Property (find(u) != find(v)). Merged {v0,v1,v2} into root 0.
w=1 w=2 w=3 w=4 w=5 w=6 v0 p=0 v1 p=0 v2 p=0 v3 p=3 v4 p=4
DSU parent[]
[0, 0, 0, 3, 4]
Connected Comps
3
Total MST Weight
3
[Step 1] Accepted (v0—v1, w=1) and (v1—v2, w=2) by Cut Property (find(u) != find(v)). Merged {v0,v1,v2} into root 0.
Hardware Reality — Why Kruskal Beats Heap-Prim on Sparse Graphs

Although Prim's $O(E \log V)$ looks asymptotically equivalent to Kruskal's $O(E \log E)$, Kruskal spends $90\%$ of its runtime inside std::sort (Introsort / PDQSort) over a contiguous array of 12-byte {w, u, v} structs—maximizing L1 cache hits and SIMD-friendly comparisons—followed by a sequential scan with $O(1)$-cached DSU lookups, terminating early as soon as $V - 1$ edges are added.

Complexity Matrix

Algorithm Time Complexity Auxiliary Space Optimal Regime
Kruskal's (Sort + DSU) O(E log E) O(V) Sparse graphs ($E = O(V)$), edge lists
Prim's (Binary Heap) O(E log V) O(V) Adjacency list graphs
Prim's (Flat Array, No Heap) O(V²) O(V) Dense / geometric graphs ($E = \Theta(V^2)$)
Borůvka's Algorithm O(E log V) O(V) Parallel / GPU & implicit/XOR edge weights

Production Implementation: Kruskal's MST & Dense $O(V^2)$ Prim's MST

#include <vector>
#include <algorithm>
#include <limits>

struct WeightedEdge {
    int u, v;
    long long weight;
    bool operator<(const WeightedEdge& other) const noexcept {
        return weight < other.weight;
    }
};

// Returns (totalMSTWeight, selectedEdges). If disconnected, selectedEdges.size() < n - 1.
std::pair<long long, std::vector<WeightedEdge>> kruskalMST(int n, std::vector<WeightedEdge> edges) {
    std::sort(edges.begin(), edges.end());
    std::vector<int> p(n, -1);
    auto find = [&](int x) {
        int r = x;
        while (p[r] >= 0) r = p[r];
        while (x != r) { int nxt = p[x]; p[x] = r; x = nxt; }
        return r;
    };

    long long totalWeight = 0;
    std::vector<WeightedEdge> mst;
    mst.reserve(n > 0 ? n - 1 : 0);

    for (const auto& e : edges) {
        int ru = find(e.u), rv = find(e.v);
        if (ru != rv) {
            if (p[ru] > p[rv]) std::swap(ru, rv);
            p[ru] += p[rv];
            p[rv] = ru;
            totalWeight += e.weight;
            mst.push_back(e);
            if (static_cast<int>(mst.size()) == n - 1) break;
        }
    }
    return {totalWeight, mst};
}
Failure Mode — Tie-Breaking CycleCreation in Parallel Borůvka Rounds

If a graph has duplicate edge weights (e.g., a triangle $A-B-C$ where all three edges have weight $5$), and in a Borůvka round component $A$ picks $(A, B)$, $B$ picks $(B, C)$, and $C$ picks $(C, A)$, adding all three simultaneously creates a cycle! Always break weight ties deterministically using the lexicographical tuple (weight, min(u, v), max(u, v)) so all edge weights become strictly distinct.

Check yourself Question 1 of 4
Concept Check The Cut Property vs The Cycle Property recall
Section: Cut & Cycle Optimality

Which statement accurately captures the duality between the Cut Property and the Cycle Property for Minimum Spanning Trees?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

In Borůvka's MST algorithm, every connected component simultaneously selects its minimum-weight outgoing edge. If edge weights are not unique and ties are broken inconsistently, what bug can occur in a single Borůvka phase?

Key Takeaways

The Cut Property proves that the strictly lightest edge crossing any cut (S, V \ S) belongs to every MST; the Cycle Property proves the strictly heaviest edge on any cycle belongs to no MST.
Every MST is automatically a Minimum Bottleneck Spanning Tree: the unique tree path between u and v minimizes the maximum edge weight along the path.
Kruskal's O(E log E) algorithm excels on sparse edge lists due to cache-local sorting and O(alpha(V)) DSU merges, while heapless O(V^2) Prim's is optimal for dense graphs.
Borůvka's algorithm halves the number of connected components in each of at most ceil(log_2 V) rounds, making it ideal for parallel execution and implicit XOR/geometric edge weights.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue