Expert: advanced patterns and interviews · Tries, union-find and bit manipulation · topic 49 of 61 · 35 min
Minimum spanning trees
Explanation
The problem: connect everything cheaply
A district wants to link all its villages by road. Many roads are possible, each with a building cost. Which roads should it build so that every village can reach every other, at the lowest total cost? The same question appears when laying fibre-optic cable between offices, electricity lines between towns, or water pipes in a housing society.
In graph words: the graph is undirected and weighted. A spanning tree is a set of edges that connects all V vertices with no cycle; it always has exactly V − 1 edges. A minimum spanning tree (MST) is a spanning tree with the smallest total weight. If the graph is disconnected, no spanning tree exists. There may be several MSTs with the same total when weights repeat.
Kruskal's algorithm
- Sort all edges by weight, cheapest first.
- Go through them in order. If an edge joins two vertices that are in DIFFERENT union-find groups, take it and union the groups.
- If both ends are already in the same group, skip the edge: it would close a cycle.
- Stop after taking V − 1 edges. If you run out of edges first, the graph is disconnected.
Why is the greedy choice safe? Split the vertices into any two sides. The cheapest edge that crosses between the sides always belongs to some MST (this is called the cut property). Each edge Kruskal takes is the cheapest edge joining its group to the rest, so it is always safe.
def kruskal(n, edges):
parent = list(range(n))
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]] # path halving
x = parent[x]
return x
chosen = []
for w, u, v in sorted((w, u, v) for u, v, w in edges):
ru, rv = find(u), find(v)
if ru != rv:
parent[ru] = rv
chosen.append((u, v, w))
return chosen
edges = [(0, 1, 4), (0, 2, 1), (1, 2, 2), (1, 3, 5), (2, 3, 8)]
print(kruskal(4, edges)) # Output: [(0, 2, 1), (1, 2, 2), (1, 3, 5)]Sorting costs O(E log E), and the union-find work is almost O(1) per edge, so Kruskal is O(E log E) time and O(V + E) space. Note that E log E and E log V are the same order, since E is at most V².
Prim's algorithm
Prim grows ONE tree. Start with any vertex. Keep a min-heap of edges that leave the tree. Pop the cheapest; if it leads to a vertex already in the tree, skip it; otherwise add that vertex, add the edge's weight to the total, and push all its edges. It looks like Dijkstra, but the heap key is the weight of the single edge, not the total distance from the start.
import heapq
def prim(n, adj, start=0):
in_tree = [False] * n
heap = [(0, start)]
total = 0
added = 0
while heap and added < n:
w, u = heapq.heappop(heap)
if in_tree[u]:
continue
in_tree[u] = True
total += w
added += 1
for v, wv in adj[u]:
if not in_tree[v]:
heapq.heappush(heap, (wv, v))
return total if added == n else -1
adj = [[(1, 4), (2, 1)], [(0, 4), (2, 2), (3, 5)], [(0, 1), (1, 2), (3, 8)], [(1, 5), (2, 8)]]
print(prim(4, adj)) # Output: 8Which one to use
| Situation | Good choice | Why |
|---|---|---|
| Input is an edge list, graph is sparse | Kruskal | Sort once, then cheap union-find checks |
| Input is an adjacency list | Prim with a heap | Only looks at edges next to the tree |
| Every pair of points can be joined (complete graph) | Prim with a plain array, O(V²) | There are about V²/2 edges, so sorting them all is wasteful |
The "connect all points" problem is the last case: every pair of points can be joined, at a cost equal to their Manhattan distance |x1 − x2| + |y1 − y2|. Keep, for every vertex not yet in the tree, the cheapest edge to the tree, and each round pick the smallest. That is O(V²) time and O(V) space, with no heap and no edge list.
Common mistakes
- Not checking that the result has V − 1 edges, so a disconnected graph gets a wrong "answer". Fix: count the edges taken (or vertices added).
- Using an MST to answer "shortest route from A to B". Fix: that is Dijkstra.
- In Prim, marking a vertex as in the tree when it is pushed instead of when it is popped. Fix: mark it when popped, like Dijkstra.
- Sorting edges by the wrong field, for example (u, v, w) tuples sorted by u. Fix: sort by weight, with key=lambda e: e[2] or by putting w first.
- Applying it to a directed graph. Fix: the MST is defined for undirected graphs; directed versions need different algorithms.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Minimum spanning trees · 1/8
This lesson is about minimum spanning trees: connecting every vertex for the lowest total cost.
Prefer a video by a teacher? Find free videos on “minimum spanning tree kruskal prim algorithm explained” on YouTube ↗
In real life: Laying fibre between offices
A company wants to connect its five offices in a city with its own fibre-optic cable. The network team estimates the cost of each possible cable route (in lakh rupees, made-up numbers). Any office must be able to reach any other, directly or through other offices, and the total cost should be as low as possible. Kruskal's algorithm picks the cables. Real planning also adds backup links for safety, which an MST alone does not give you.
routes = [("HQ", "Tech Park", 4), ("HQ", "Warehouse", 7), ("Tech Park", "Warehouse", 3),
("Tech Park", "Sales", 6), ("Warehouse", "Sales", 5), ("Sales", "Support", 2),
("HQ", "Support", 9)]
parent = {}
def find(x):
parent.setdefault(x, x)
while parent[x] != x:
x = parent[x]
return x
total = 0
for a, b, cost in sorted(routes, key=lambda r: r[2]):
ra, rb = find(a), find(b)
if ra != rb:
parent[ra] = rb
total += cost
print("lay", a, "-", b, cost)
print("total", total)Practice (0/3 done)
Write kruskal_mst_cost(n, edges) for an UNDIRECTED graph with vertices 0 to n − 1 and edges (u, v, w). Weights can be negative, and there may be parallel edges or self-loops. Return the total weight of a minimum spanning tree, or -1 if the graph is not connected. With 0 or 1 vertices the cost is 0. Use sorting plus union-find.
points is a list of [x, y] integer points. Joining two points costs their Manhattan distance |x1 − x2| + |y1 − y2|. Write min_cost_connect_points(points) that returns the minimum total cost to connect all points (any point can then reach any other, directly or through others). An empty list or a single point costs 0. Inputs are small (up to about 300 points), so Prim's O(n²) array version is ideal.
There are n villages labelled 0 to n − 1. existing is a list of pairs (u, v) for roads that are ALREADY built (cost 0). proposals is a list of (u, v, cost) for roads that could be built. Write connect_villages(n, existing, proposals) that returns the minimum extra cost so that every village can reach every other, or -1 if that is impossible. Hint: union the existing roads first, then run Kruskal on the proposals.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
How many edges does a spanning tree of a connected graph with 10 vertices have?
Answer: 9
A tree on V vertices always has V − 1 edges.
In Kruskal's algorithm, the next cheapest edge joins two vertices already in the same group. What do you do?
Answer: Skip it, because it would close a cycle
Both ends are already connected, so the edge adds nothing but a cycle.
Which statement about an MST is true?
Answer: It minimises the total weight of the edges that connect everything
It minimises the sum over the whole tree; individual routes along it can be long.
You must connect 1,000 points where every pair can be joined. Which approach is natural?
Answer: Prim with a plain array, O(V²)
In a complete graph, Prim with an array avoids building and sorting about V²/2 edges.
What is the time of Kruskal's algorithm with union-find?
Answer: O(E log E)
Sorting the edges dominates; union-find adds almost O(1) per edge.
An undirected graph has 5 edges. What do all the degrees add up to?Related
Answer: 10
Every edge touches two vertices, so it adds 2 to the total: 2 × 5 = 10.
A graph has a few negative edges but no negative cycle. Which algorithm should you use for one source?Related
Answer: Bellman-Ford
Bellman-Ford allows negative edges; Dijkstra may settle a vertex too early.
Kahn's algorithm outputs 4 vertices of a 6-vertex graph. What does that tell you?Related
Answer: The graph has a cycle
Vertices on a cycle never reach in-degree 0, so they are left out.
From Topological sort
Directed edges 0→1, 0→2, 1→3, 2→3. A DFS from 0 meets vertex 3 a second time. Is there a cycle?Related
Answer: No: 3 is finished (black), not on the current path
Only an edge to a grey vertex (on the current path) closes a cycle. This is a diamond.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Tries, union-find and bit manipulation” before its exam: Bits: AND, OR, XOR and shifts, Bit tricks in interviews.
- Module 10 exam: Tries, union-find and bits · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Dynamic programming. 61 topics left in the course before the final exam.