Data Structure 📂 Simulators · 3 of 4 76 min read

Minimum Spanning Tree (MST) — Prim's, Kruskal's and Borůvka's Algorithms (Interactive Simulator)

A minimum spanning tree connects every node with the least total edge weight and no cycles. Prim's grows one tree from a start node. Kruskal's sorts edges and uses Union-Find to skip cycles. Borůvka's merges components in rounds. Draw a graph, run each step, and build your own tree to check.

Section 01

The Story — Connecting Villages With Fibre Cable

Seven Hill Villages Need Internet
A telecom company must connect seven hill villages with fibre cable. Some pairs of villages can be joined. Each possible cable has a cost (in lakh rupees). Hills make some routes very costly.

The rule is simple: every village must be connected to every other one, directly or through other villages. But the company wants to spend the least total money.

Will they buy a cable that makes a loop? Never. A loop means one cable is extra. They can remove it and everyone still stays connected.

The cheapest set of cables with no loops is called a Minimum Spanning Tree (MST).

A spanning tree of a connected graph uses all the nodes and just enough edges to connect them, with no cycles. If there are V nodes, a spanning tree always has exactly V − 1 edges. The minimum spanning tree is the one with the smallest total edge weight.

🌳
Spanning Tree
V nodes · V − 1 edges
Connects every node. Has no cycle. Remove any one edge and the tree breaks into two parts.
💰
Minimum Spanning Tree
smallest total weight
Among all spanning trees, the one whose edge weights add up to the least. If weights repeat, there can be more than one MST, but they all have the same total.
🚫
Not a Shortest Path
common confusion
An MST keeps total cable cost low. It does not promise the shortest route between two nodes. For that you need Dijkstra.
Cut Property (why greedy works)
cheapest edge across a cut ∈ MST
Split the nodes into two groups. The cheapest edge joining the two groups is always safe to take. All three algorithms rely on this one rule.
Cycle Property
heaviest edge on a cycle ∉ MST
In any cycle, the single heaviest edge is never needed. That is why Kruskal can safely reject an edge that makes a cycle.

Section 02

The Three Algorithms at a Glance

🌱
Prim's
Grow one tree · Priority queue
Start at one node. Again and again, add the cheapest edge that leaves the tree. The tree grows like a plant from its seed.
📋
Kruskal's
Sort edges · Union-Find
Sort all edges from cheap to costly. Take each edge unless it makes a cycle. Union-Find answers "same group?" very fast.
🧩
Borůvka's
Merge components in rounds
Every group picks its own cheapest outgoing edge at the same time. Add them all. The number of groups at least halves each round.
💡
Same Answer, Different Order

On the same graph, all three give an MST with the same total weight. They just add the edges in a different order. Prim grows one tree. Kruskal grows a forest of small trees that slowly join. Borůvka joins many trees at once in each round.


Section 03

Watch Them Build — Prim vs Kruskal

Same graph, same answer (total = 39). Watch how they build it. Prim's green tree grows from A. Kruskal's colours show separate groups that merge. A red dotted edge was rejected because it would make a cycle.

🎬 Prim vs Kruskal — Live Animation

Prim's — one tree grows

Kruskal's — cheapest edge first

Prim order: A–D, D–F, A–B, B–E, E–C, E–G. Kruskal order: A–D, C–E, D–F, A–B, B–E, then rejects B–C, E–F, B–D, then takes E–G.


Section 04

The MST Simulator — Build Your Own Graph

Draw nodes and weighted edges. Pick Prim's, Kruskal's or Borůvka's. Each click on Step runs one line. Watch the priority queue, the sorted edge list, or the component list change. The Union-Find table shows each node's parent. You can also build your own tree and check it against the real MST.

👉 How to Use the Simulator
Draw
Add Node: click an empty spot. Drag to move. Double-click a node to rename it.
Join
Add Edge: type a weight, then click two nodes. Double-click a weight label to change it.
Run
Pick an algorithm (and a start node for Prim). Press Step, Play or End.
Challenge
Pick Edges mode: click edges to build your own spanning tree (pink). Press Check My Tree.
🌲 Minimum Spanning Tree Simulator Not started
Draw Weight
Load
Algorithm Start
Speed
Priority Queue
Table
Algorithm — Current Line
What Is Happening
    🎯 Predict Total MST weight =
    ⎯ Thick green = MST edge ⎯ Dashed amber = edge being checked ⎯ Red dots = rejected (cycle) ⎯ Purple = Borůvka's chosen edge ⎯ Pink = your picked edge ● Node colours in Kruskal / Borůvka = which group (set) it is in
    🧪
    Try These Experiments

    1. Classic graph: type your guess for the total weight, then check it. Run all three algorithms. The total is always 39.
    2. Run Prim from different start nodes. The edge order changes, but the total does not.
    3. In Kruskal, turn Path compression off and on. Watch the parent column. With it on, nodes point straight at the root.
    4. Load Equal Weights, pick your own tree in Pick Edges mode, then check it. Many different trees are all correct.
    5. Load Disconnected. There is no spanning tree, so Kruskal and Borůvka build a spanning forest. Prim only covers the start's part.


    Section 05

    Union-Find Playground — The Engine Inside Kruskal

    Kruskal asks one question again and again: "Are u and v already in the same group?" A Union-Find (also called Disjoint Set) answers it almost instantly. Each node points to a parent. Follow the parents up to the root. Two nodes with the same root are in the same set.

    🔍
    find(x)
    walk up to the root
    Follow parent[x] until a node points to itself. That node is the set's leader (root).
    🔗
    union(a, b)
    join two sets
    Find both roots. If they differ, make one root point to the other. Union by rank hangs the shorter tree under the taller one.
    ⚡
    Path Compression
    flatten while you walk
    After find(x), point every node on the path straight to the root. Later finds become almost O(1).
    🔗 Union-Find Playground8 sets
    union
    find
    Arrays
    🧪
    Try This

    Turn both options off. Press Make a Long Chain. You get a tall chain, so find(0) must walk every node. Now turn Path compression on and run find(0). Watch the whole chain flatten in one step.


    Section 06

    Step-by-Step Traces on the Classic Graph

    Edges: A–B 7, A–D 5, B–C 8, B–D 9, B–E 7, C–E 5, D–E 15, D–F 6, E–F 8, E–G 9, F–G 11.

    Prim's (start at A)

    StepTree So FarCheapest Edge Leaving the TreeActionTotal
    1{A}A–D (5)Add D5
    2{A, D}D–F (6)Add F11
    3{A, D, F}A–B (7)Add B18
    4{A, B, D, F}B–E (7)Add E25
    5{A, B, D, E, F}E–C (5)Add C30
    6{A, B, C, D, E, F}B–C (8), E–F (8), B–D (9) are staleSkip stale edges30
    7{A, B, C, D, E, F}E–G (9)Add G — done39

    Kruskal's (edges sorted by weight)

    EdgeWeightSame Set?ActionSets After
    A–D5NoTake{A,D} {B} {C} {E} {F} {G}
    C–E5NoTake{A,D} {B} {C,E} {F} {G}
    D–F6NoTake{A,D,F} {B} {C,E} {G}
    A–B7NoTake{A,B,D,F} {C,E} {G}
    B–E7NoTake{A,B,C,D,E,F} {G}
    B–C8YesReject — cycleno change
    E–F8YesReject — cycleno change
    B–D9YesReject — cycleno change
    E–G9NoTake — 6 edges, done{A,B,C,D,E,F,G}

    Borůvka's

    RoundCheapest Edge for Each ComponentEdges AddedComponents After
    1A→A–D 5 · B→A–B 7 · C→C–E 5 · D→A–D 5 · E→C–E 5 · F→D–F 6 · G→E–G 9A–D, A–B, C–E, D–F, E–G{A,B,D,F} {C,E,G}
    2{A,B,D,F}→B–E 7 · {C,E,G}→B–E 7B–E{A,B,C,D,E,F,G}
    ⚠️
    Borůvka Needs a Tie-Break Rule

    In round 1, node B has two edges of weight 7 (A–B and B–E). If two groups break ties in different ways, they can pick edges that form a cycle. The fix is simple. Compare by (weight, edge number), so every group follows the same order.


    Section 07

    Python Implementation

    # Shared graph for all three examples
    edges = [('A','B',7), ('A','D',5), ('B','C',8), ('B','D',9), ('B','E',7), ('C','E',5),
             ('D','E',15), ('D','F',6), ('E','F',8), ('E','G',9), ('F','G',11)]
    nodes = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
    
    graph = {n: [] for n in nodes}          # adjacency list
    for u, v, w in edges:
        graph[u].append((v, w))
        graph[v].append((u, w))

    1. Prim's Algorithm (heapq)

    import heapq
    
    def prim(graph, start):
        in_tree = {start}
        pq = [(w, start, v) for v, w in graph[start]]
        heapq.heapify(pq)
        mst, total = [], 0
        while pq and len(in_tree) < len(graph):
            w, u, v = heapq.heappop(pq)         # cheapest edge
            if v in in_tree:
                continue                           # stale: would make a cycle
            in_tree.add(v)
            mst.append((u, v, w))
            total += w
            for x, wx in graph[v]:              # new edges leaving the tree
                if x not in in_tree:
                    heapq.heappush(pq, (wx, v, x))
        return mst, total
    
    mst, total = prim(graph, 'A')
    print("MST edges:", mst)
    print("Total weight:", total)
    OUTPUT
    MST edges: [('A', 'D', 5), ('D', 'F', 6), ('A', 'B', 7), ('B', 'E', 7), ('E', 'C', 5), ('E', 'G', 9)] Total weight: 39

    2. Union-Find + Kruskal's Algorithm

    class DSU:
        def __init__(self, items):
            self.parent = {x: x for x in items}   # each node is its own set
            self.rank = {x: 0 for x in items}
    
        def find(self, x):
            if self.parent[x] != x:
                self.parent[x] = self.find(self.parent[x])   # path compression
            return self.parent[x]
    
        def union(self, a, b):
            ra, rb = self.find(a), self.find(b)
            if ra == rb:
                return False                     # same set: cycle!
            if self.rank[ra] < self.rank[rb]:    # union by rank
                ra, rb = rb, ra
            self.parent[rb] = ra
            if self.rank[ra] == self.rank[rb]:
                self.rank[ra] += 1
            return True
    
    def kruskal(nodes, edges):
        dsu = DSU(nodes)
        mst, total = [], 0
        for u, v, w in sorted(edges, key=lambda e: e[2]):
            if dsu.union(u, v):
                mst.append((u, v, w))
                total += w
                print(f"  take   {u}-{v} ({w})")
            else:
                print(f"  reject {u}-{v} ({w})  -> would make a cycle")
            if len(mst) == len(nodes) - 1:
                break                            # V - 1 edges: done
        return mst, total
    
    mst, total = kruskal(nodes, edges)
    print("Total weight:", total)
    OUTPUT
    take A-D (5) take C-E (5) take D-F (6) take A-B (7) take B-E (7) reject B-C (8) -> would make a cycle reject E-F (8) -> would make a cycle reject B-D (9) -> would make a cycle take E-G (9) Total weight: 39

    3. Borůvka's Algorithm

    def boruvka(nodes, edges):
        dsu = DSU(nodes)
        mst, total, rnd = [], 0, 0
        comps = len(nodes)
        while comps > 1:
            rnd += 1
            cheapest = {}                          # root -> best edge
            for i, (u, v, w) in enumerate(edges):
                ru, rv = dsu.find(u), dsu.find(v)
                if ru == rv:
                    continue                       # inside one component
                for r in (ru, rv):
                    # tie-break by (weight, index) to avoid cycles
                    if r not in cheapest or (w, i) < (cheapest[r][2], cheapest[r][3]):
                        cheapest[r] = (u, v, w, i)
            if not cheapest:
                break                              # graph is not connected
            added = []
            for u, v, w, i in cheapest.values():
                if dsu.union(u, v):              # may be chosen twice
                    mst.append((u, v, w))
                    total += w
                    comps -= 1
                    added.append(f"{u}-{v}({w})")
            print(f"  Round {rnd}: added {added}, components left = {comps}")
        return mst, total
    
    mst, total = boruvka(nodes, edges)
    print("Total weight:", total)
    OUTPUT
    Round 1: added ['A-D(5)', 'A-B(7)', 'C-E(5)', 'D-F(6)', 'E-G(9)'], components left = 2 Round 2: added ['B-E(7)'], components left = 1 Total weight: 39

    Section 08

    Prim vs Kruskal vs Borůvka

    V = number of nodes. E = number of edges.

    PropertyPrim'sKruskal'sBorůvka's
    Main ideaGrow one treeCheapest edge overall, skip cyclesEvery component picks its cheapest edge
    Key data structurePriority queue (min-heap)Sorted list + Union-FindUnion-Find + "cheapest" array
    TimeO(E log V) with heapO(E log E) (sorting)O(E log V)
    Best forDense graphs, adjacency list/matrixSparse graphs, edge list inputParallel / distributed computing
    Needs a start node?YesNoNo
    Disconnected graphSpans only the start's partGives a spanning forestGives a spanning forest
    Easy to run in parallel?NoSorting onlyYes — each component works alone
    📐
    Why Borůvka Needs Only log V Rounds

    In each round, every component joins with at least one other component. So the number of components at least halves: V → V/2 → V/4 → … → 1. That takes at most log₂ V rounds. With 1,000,000 nodes, that is only about 20 rounds.


    Section 09

    Where MSTs Are Used

    🔌
    Network Design
    Laying cable, pipes, roads or power lines to connect every point at the lowest cost.
    telecom, water, electricity
    🔬
    Clustering
    Build the MST, then remove the k − 1 heaviest edges. You get k groups of close points.
    single-linkage clustering
    🗺️
    TSP Approximation
    A walk around the MST gives a travelling-salesman tour at most 2× the best (for metric graphs).
    approximation algorithms
    🖼️
    Image Segmentation
    Pixels are nodes, colour differences are weights. MST-based methods group similar pixels.
    computer vision
    🎮
    Maze Generation
    Give random weights to grid walls and run Kruskal. The result is a perfect maze with exactly one path between any two cells.
    games
    🌐
    Parallel Big Data
    Borůvka-style rounds split well across many machines for graphs with billions of edges.
    distributed systems

    Section 10

    Golden Rules

    🏆 Minimum Spanning Tree — Rules to Remember
    1
    A spanning tree of V nodes has exactly V − 1 edges and no cycle. Use this to check your answer.
    2
    Cut property: the cheapest edge crossing any split is safe. Every MST algorithm is built on it.
    3
    Prim = one growing tree + min-heap. Skip heap entries whose far end is already in the tree.
    4
    Kruskal = sort edges + Union-Find. If find(u) == find(v), the edge makes a cycle, so reject it.
    5
    Always use path compression and union by rank. Together they make Union-Find almost O(1) per operation.
    6
    Borůvka needs a consistent tie-break such as (weight, edge id). Otherwise equal weights can create a cycle.
    7
    MSTs work fine with negative weights. Only the order of weights matters, not their sign.
    8
    An MST is not a shortest-path tree. The path between two nodes inside an MST can be longer than the true shortest path.