Graphs · Advanced Graph Algorithms

Minimum Spanning Tree

Building a spanning tree of minimum total edge weight by greedily adding the cheapest edge that doesn't create a cycle - either growing one tree outward from a start node, always picking the cheapest edge leaving the tree so far (Prim's), or considering all edges globally in increasing weight order and skipping any that would connect two nodes already in the same component (Kruskal's) - both greedy strategies are provably optimal for this specific problem.

  • 5 min read
  • 10 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: dense graphs without a heap
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

A minimum spanning tree (MST) connects every node with the cheapest set of edges, like cable between buildings. It is always a tree with V - 1 edges: a cycle would hold an edge you could drop. Two greedy algorithms find it. Kruskal's takes the cheapest edges that don't close a cycle. Prim's grows one tree from a start node.

We'll go from simple to more technical:

  1. The pattern: Kruskal's with union-find, and Prim's with a heap.
  2. A second flavor: array-based Prim's for dense graphs.
  3. Where MSTs are used in production (networks, clustering, approximations).
  4. What breaks it: stale heap entries, slow cycle checks, overwritten keys, disconnected graphs.

The approach

Kruskal's sorts the edges by weight and takes each one whose ends aren't connected yet. Here it is on four nodes with edges 0-1 (1), 1-2 (2), 0-2 (3), 2-3 (4) and 1-3 (5):

EdgeWeightEnds already connected?Take it?Total
0-11noyes1
1-22noyes3
0-23yes, through 1no (a cycle)3
2-34noyes; 3 edges, stop7

Prim's grows a tree from node 0 instead, always adding the cheapest edge that leaves it, and takes the same three edges here.

Java

// Kruskal's: take edges cheapest-first, skipping any that would close a cycle
int kruskalMST(int n, int[][] edges) {                    // edges: {u, v, w}
    Arrays.sort(edges, (a, b) -> Integer.compare(a[2], b[2]));
    int[] parent = new int[n];
    for (int i = 0; i < n; i++) parent[i] = i;
    int total = 0, used = 0;
    for (int[] e : edges) {
        int ru = find(parent, e[0]), rv = find(parent, e[1]);
        if (ru != rv) {                                    // different components: this edge is safe
            parent[ru] = rv;
            total += e[2];
            if (++used == n - 1) break;
        }
    }
    return total;
}

int find(int[] parent, int x) {
    while (parent[x] != x) x = parent[x] = parent[parent[x]];   // path halving keeps trees shallow
    return x;
}

// Prim's: grow one tree, always adding the cheapest edge leaving it
int primMST(int n, List<List<int[]>> adj) {              // adj.get(u) holds {v, w}
    boolean[] inTree = new boolean[n];
    PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[1], b[1]));   // {node, edge weight}
    pq.add(new int[]{0, 0});
    int total = 0, added = 0;
    while (!pq.isEmpty() && added < n) {
        int[] cur = pq.poll();
        if (inTree[cur[0]]) continue;                      // an older, pricier way in - already connected
        inTree[cur[0]] = true;
        total += cur[1];
        added++;
        for (int[] edge : adj.get(cur[0])) if (!inTree[edge[0]]) pq.add(edge);
    }
    return total;
}

Python

import heapq


# Kruskal's: take edges cheapest-first, skipping any that would close a cycle
def kruskal_mst(n: int, edges: list[list[int]]) -> int:        # edges: [u, v, w]
    edges.sort(key=lambda e: e[2])
    parent = list(range(n))
    total = used = 0
    for u, v, w in edges:
        ru, rv = find(parent, u), find(parent, v)
        if ru != rv:                                     # different components: this edge is safe
            parent[ru] = rv
            total += w
            used += 1
            if used == n - 1:
                break
    return total


def find(parent: list[int], x: int) -> int:
    while parent[x] != x:
        parent[x] = parent[parent[x]]                    # path halving keeps trees shallow
        x = parent[x]
    return x


# Prim's: grow one tree, always adding the cheapest edge leaving it
def prim_mst(n: int, adj: list[list[tuple[int, int]]]) -> int:   # adj[u] holds (v, w)
    in_tree = [False] * n
    heap = [(0, 0)]                                      # (edge weight, node)
    total = added = 0
    while heap and added < n:
        w, node = heapq.heappop(heap)
        if in_tree[node]:
            continue                                     # an older, pricier way in - already connected
        in_tree[node] = True
        total += w
        added += 1
        for nxt, weight in adj[node]:
            if not in_tree[nxt]:
                heapq.heappush(heap, (weight, nxt))
    return total

Both rest on the cut property: the cheapest edge between any two groups of nodes belongs to some MST. Prim's groups are its tree and the rest; Kruskal's are the two components an edge joins.

A second flavor: dense graphs without a heap

On a complete graph, like points on a map, E is about V^2 / 2. Prim's with a plain array is O(V^2), which is optimal there:

Java

int[] key = new int[n];                    // cheapest edge from the tree to each outside node
Arrays.fill(key, Integer.MAX_VALUE);
key[0] = 0;
for (int added = 0; added < n; added++) {
    int u = -1;
    for (int v = 0; v < n; v++) if (!inTree[v] && (u == -1 || key[v] < key[u])) u = v;   // O(V) scan
    inTree[u] = true;
    total += key[u];
    for (int v = 0; v < n; v++)
        if (!inTree[v]) key[v] = Math.min(key[v], dist(u, v));   // min over the WHOLE tree
}

Python

key = [float("inf")] * n                 # cheapest edge from the tree to each outside node
key[0] = 0
for _ in range(n):
    u = min((v for v in range(n) if not in_tree[v]), key=key.__getitem__)   # O(V) scan
    in_tree[u] = True
    total += key[u]
    for v in range(n):
        if not in_tree[v]:
            key[v] = min(key[v], dist(u, v))                 # min over the WHOLE tree

key[v] must stay the minimum over the whole tree; overwriting it with dist(u, v) builds a greedy chain instead.

Where you'll see it

Network design (the least cable that reaches every site), clustering (cut the k - 1 most expensive MST edges for k clusters) and traveling-salesman approximations all start from an MST.

Which tool for which job?

You needUse
A list of edgesKruskal's: sort, then union-find
A sparse graph in adjacency listsPrim's with a heap
A dense or complete graphPrim's with an array, O(V^2)
Short routes from a sourcea shortest-path tree (Dijkstra), not an MST

Common mistakes

MistakeWhat happensDo this instead
Prim's without the in-tree skipa node is counted twice (4 instead of 12)skip popped nodes already in the tree
Union-find without either optimization4,999,950,000 parent hops instead of 199,997union by rank and path compression
Overwriting key[v] in array Prim'sa greedy chain instead of a treekeep the minimum over the whole tree
Assuming the graph is connecteda forest is returned silentlycheck that V - 1 edges were used

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why do Kruskal's and Prim's both produce a minimum spanning tree, despite choosing edges so differently?

Show answer

Both take only the cheapest edge across some cut, and the cut property says that edge is always safe.

2. Why does Prim's skip a popped node that's already in the tree?

Show answer

A node is pushed once per edge that reaches it. Without the skip, a stale entry counts it again: on 0-1 (1), 0-2 (2), 1-2 (1), 2-3 (10), the total is 4 with node 3 left out, instead of 12.

3. Is the MST path between two nodes their shortest path?

Show answer

Not necessarily. In the triangle 0-1 (2), 1-2 (2), 0-2 (3), the MST's path from 0 to 2 costs 4, while the direct edge costs 3. The MST minimizes total weight, not routes.

Key takeaways

  • The cut property - the cheapest edge across any cut is safe - justifies both algorithms.
  • Kruskal's: sort edges, take each one that joins two union-find components.
  • Prim's: grow one tree, skipping stale heap entries; on dense graphs use the O(V^2) array version.
  • An MST is not a shortest-path tree.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Edge listKruskal's plus union-find
Sparse adjacency listsheap Prim's
Dense / completearray Prim's, O(V^2)
k clustersKruskal's stopped at k
Short routes from a sourceDijkstra, not an MST

Snippets worth knowing by heart

Java

int find(int x) {                                 // path compression (halving)
    while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
    return x;
}
boolean union(int a, int b) {
    a = find(a); b = find(b);
    if (a == b) return false;                     // would form a cycle
    if (rank[a] < rank[b]) { int t = a; a = b; b = t; }
    parent[b] = a;
    if (rank[a] == rank[b]) rank[a]++;
    return true;
}

Python

total, used = 0, 0
for w, u, v in sorted(edges):            # Kruskal's
    if union(u, v):
        total += w
        used += 1
connected = used == n - 1

The traps

  • No in-tree skip in heap Prim's: a node is added twice and the weight is too high. Skip popped nodes already in the tree.
  • Union-find without optimizations: finds walk long chains, billions of hops. Use union by rank and path compression.
  • Overwriting key[v] in array Prim's: you build a greedy chain, not a tree. Keep the minimum edge to the whole tree.
  • Assuming the graph is connected: a forest comes back silently. Check that V - 1 edges were used.
  • MST vs shortest-path tree: the cheapest total isn't the shortest route from a source. Use Dijkstra for routes.

Staff-level points, one line each

  • Union-find: 4,999,950,000 hops naive against 199,997 with rank and compression.
  • An MST holds the best bottleneck path for every pair (435 of 435 checked).
  • Kruskal's stopped at 3 components recovered 3 blobs of points exactly.
  • On 9 points: MST 186.2 ≤ best tour 277.8 ≤ MST preorder tour 325.5 ≤ 2 × MST 372.4.
  • With tied weights, many trees are minimum. Compare totals, or sort by (weight, u, v).

Say it in an interview

  • "The cheapest edge across any cut is safe to take. Both Kruskal's and Prim's rely on that."
  • "Kruskal's is O(E log E) with union-find, and array Prim's is O(V^2) for dense graphs."
  • "An MST minimizes total weight, not distances from a source."
  • "Stopping Kruskal's early gives single-linkage clusters."

Practice

This skill comes with 10 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Minimum Spanning Tree free

Minimum Spanning Tree

MediumProCoding problemJava · Python

Given a connected, undirected, weighted graph with n nodes (labeled 0 to n-1) and a list of edges, return the total weight of its minimum spanning tree.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Min Cost to Connect All Points

MediumProCoding problemJava · Python

You have n points on a flat grid. Laying a cable between two points costs their Manhattan distance, |x1 - x2| + |y1 - y2|. Connect all the points, so that every point can reach every other through the cables, at the lowest total cost.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Minimum Spanning Tree free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime