Minimum Spanning Trees — Connecting Everything at Minimum Cost

A spanning tree of a connected undirected graph is a subgraph that includes all vertices and is a tree (connected, acyclic). A minimum spanning tree (MST) is the spanning tree with the smallest total edge weight. The key insight: a spanning tree of V vertices always has exactly V-1 edges. The MST problem asks which V-1 edges to keep.

The classic motivation: you have a set of cities. You want to connect all of them with roads using the minimum total road construction cost. You don’t need every possible road — just enough to keep every city reachable. That’s an MST.


Why Greedy Works for MST — The Cut Property

Both MST algorithms are greedy. This works because of a fundamental property called the cut property:

Given any partition of the vertices into two non-empty sets S and V\S (a “cut”), the minimum weight edge crossing the cut is in every MST.

Proof intuition: suppose edge e (the minimum crossing edge) is NOT in some MST T. Then T must use some other crossing edge e’ (to keep the graph connected across the cut). If we swap e’ for e, we get a spanning tree with smaller total weight — contradicting T being an MST. Therefore e must be in T.

The two MST algorithms differ in how they choose which cut to inspect at each step. Kruskal’s processes edges globally by weight. Prim’s grows a single component outward.


Kruskal’s Algorithm

Strategy: sort all edges by weight. Add edges in order of increasing weight, skipping any edge that would create a cycle (i.e., whose endpoints are already connected). Stop when V-1 edges have been added.

The cycle detection step is what Union-Find was built for.

int kruskalMST(int V, int[][] edges) {
    // edges[i] = {u, v, weight}
    Arrays.sort(edges, Comparator.comparingInt(e -> e[2]));  // sort by weight

    UnionFind uf = new UnionFind(V);
    int totalWeight = 0;
    int edgesAdded = 0;

    for (int[] edge : edges) {
        int u = edge[0], v = edge[1], w = edge[2];

        if (uf.union(u, v)) {       // returns false if u, v already connected
            totalWeight += w;
            edgesAdded++;
            if (edgesAdded == V - 1) break;  // MST complete: exactly V-1 edges
        }
    }

    // If edgesAdded < V-1: graph was disconnected, no spanning tree exists
    return totalWeight;
}

Time: O(E log E) for sorting + O(E × α(V)) for Union-Find operations ≈ O(E log E). Space: O(V) for Union-Find.

Kruskal’s is edge-centric: it works naturally from an edge list. The sort dominates the runtime.


Prim’s Algorithm

Strategy: grow an MST from a single starting vertex. Maintain a set of vertices already in the MST. At each step, add the minimum-weight edge that connects a vertex in the MST to a vertex outside it. Repeat until all vertices are included.

This is conceptually similar to Dijkstra: use a min-heap tracking the minimum cost to add each vertex to the growing MST.

int primMST(int V, List<int[]>[] adj) {
    // adj[u] contains int[] {v, weight} for each edge u-v
    boolean[] inMST = new boolean[V];
    int[] minCost = new int[V];     // min edge cost to add vertex i to MST
    Arrays.fill(minCost, Integer.MAX_VALUE);
    minCost[0] = 0;                 // start from vertex 0

    // Min-heap: {cost, vertex}
    PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
    pq.offer(new int[]{0, 0});

    int totalWeight = 0;

    while (!pq.isEmpty()) {
        int[] curr = pq.poll();
        int cost = curr[0], node = curr[1];

        if (inMST[node]) continue;    // stale entry: node already in MST
        inMST[node] = true;
        totalWeight += cost;

        for (int[] edge : adj[node]) {
            int neighbor = edge[0], weight = edge[1];
            if (!inMST[neighbor] && weight < minCost[neighbor]) {
                minCost[neighbor] = weight;
                pq.offer(new int[]{weight, neighbor});
            }
        }\n    }

    return totalWeight;
}

Time: O((V + E) log V). Each vertex is added to the PQ at most once per incoming edge; PQ operations are O(log V). Space: O(V + E).

Prim’s is vertex-centric: it grows a single connected component outward. Better than Kruskal’s on dense graphs (E ≈ V²) where the sort cost of Kruskal dominates.


Kruskal’s vs Prim’s

Kruskal’s

Prim’s

Strategy

Edge-sorted, add globally

Grow single component outward

Data structure

Union-Find

Priority queue

Time

O(E log E)

O((V+E) log V)

Better for

Sparse graphs (E ≈ V)

Dense graphs (E ≈ V²)

Connectivity required

No (handles disconnected)

Must start from a connected component

Implementation

Edge list + sort

Adjacency list + heap

For LeetCode, either works. Kruskal’s with Union-Find is slightly easier to implement correctly because the cycle detection is handled by DSU. Prim’s is worth knowing because it parallels Dijkstra structurally.


MST Variant: Minimum Cost to Connect All Points (LC 1584)

Points on a 2D plane, no explicit edges. You can connect any two points at cost = Manhattan distance. Find the MST of the complete graph.

Kruskal’s approach: generate all C(n,2) = O(n²) edges, sort them, run Kruskal’s. For n ≤ 1000, this is 500,000 edges — manageable.

Prim’s approach: avoid generating all edges. For each vertex not yet in the MST, track the minimum Manhattan distance to any vertex currently in the MST. Update after each MST expansion. This avoids materializing all edges.

int minCostConnectPoints(int[][] points) {
    int n = points.length;
    int[] minDist = new int[n];   // min distance to MST for each point
    Arrays.fill(minDist, Integer.MAX_VALUE);
    minDist[0] = 0;

    boolean[] inMST = new boolean[n];
    int totalCost = 0;

    for (int i = 0; i < n; i++) {
        // Find the non-MST point with minimum distance (linear scan = Prim's without heap)
        int next = -1;
        for (int j = 0; j < n; j++) {
            if (!inMST[j] && (next == -1 || minDist[j] < minDist[next])) {
                next = j;
            }
        }

        inMST[next] = true;
        totalCost += minDist[next];

        // Update distances to all non-MST points
        for (int j = 0; j < n; j++) {
            if (!inMST[j]) {
                int dist = Math.abs(points[next][0] - points[j][0])
                         + Math.abs(points[next][1] - points[j][1]);
                minDist[j] = Math.min(minDist[j], dist);
            }
        }
    }

    return totalCost;
}

This is Prim’s without a heap (O(V²)), which is optimal for the complete graph where E = V² anyway.


Critical Connections and Bridges (LC 1192)

Not strictly MST, but a related concept worth placing here: a bridge is an edge whose removal disconnects the graph. Finding all bridges uses DFS with discovery/low-time tracking.

// Low[u] = minimum discovery time reachable from the subtree rooted at u
// If low[v] > disc[u], then edge u-v is a bridge (v can't reach back above u)

void dfs(int u, int parent, int[] disc, int[] low, boolean[] visited, List<List<Integer>> adj, List<List<Integer>> result) {
    visited[u] = true;
    disc[u] = low[u] = timer++;

    for (int v : adj.get(u)) {
        if (v == parent) continue;
        if (!visited[v]) {
            dfs(v, u, disc, low, visited, adj, result);
            low[u] = Math.min(low[u], low[v]);
            if (low[v] > disc[u]) {    // v can't reach back to u or above: bridge
                result.add(Arrays.asList(u, v));
            }
        } else {
            low[u] = Math.min(low[u], disc[v]);
        }
    }
}

The low[] value propagates upward via DFS, encoding the earliest-discovered node reachable from each subtree.


Applications

  • Network design: minimum cost infrastructure (cable, roads, pipelines) connecting all nodes

  • Clustering: Kruskal’s MST + remove k-1 heaviest edges → k clusters (used in k-means seeding)

  • Approximation algorithms: the 2-approximation for TSP uses an MST as its backbone

  • Image segmentation: group pixels by similarity; minimum spanning tree of the pixel graph, then cut


Practice Problems

#

Problem

Difficulty

Focus

1

LC 1584 — Min Cost to Connect All Points

Medium

Prim’s or Kruskal’s on complete graph

2

LC 1135 — Connecting Cities With Minimum Cost

Medium

Direct MST application

3

LC 1192 — Critical Connections (Bridges)

Hard

DFS with low-time tracking

4

LC 1489 — Find Critical and Pseudo-Critical Edges

Hard

MST + edge classification

Problem 1 is the essential MST problem on LeetCode. Solve it with both Kruskal’s and Prim’s. Problem 3 introduces bridge-finding — a DFS technique that extends the graph toolkit beyond MST proper.