Skip to content
dsa450for five year olds

Graph · 19 of 43

Implement Prims Algorithm

Grow a road network from one town, always adding the cheapest road that reaches a town not yet connected.

Simple way
time O(V^2)
room O(V)
Clever way
time O(E log V)
room O(V + E)

The problem, simply

You get V towns and possible two-way roads, each with a building price. Pick roads so every town can reach every other town, for the smallest total price. Give back that total.

This is the same goal as Kruskal's algorithm, the cheapest connected network. Prim reaches it differently: instead of looking at all roads at once, it grows one connected blob from a starting town.

A story to remember it

Picture it

graph
43124265012345

The same six towns as on the Kruskal page. Starting from 0, the blot spreads to 2, 1, 3, 4 and 5. The bright roads cost 3 + 1 + 2 + 2 + 5 = 13.

  • looking now
  • answer

Examples

The picture above

In
V = 6, edges = [[0,1,4],[0,2,3],[1,2,1],[1,3,2],[2,3,4],[3,4,2],[4,5,6],[3,5,5]]
Out
13

From 0 the cheapest road out is 0-2 for 3. Then 2-1 for 1, 1-3 for 2, 3-4 for 2 and 3-5 for 5.

Five towns

In
V = 5, edges = [[0,1,2],[0,3,6],[1,2,3],[1,3,8],[1,4,5],[2,4,7],[3,4,9]]
Out
16

The blot takes 0-1 (2), 1-2 (3), 1-4 (5) and 0-3 (6). Total 16.

Two towns, two roads

In
V = 2, edges = [[0,1,5],[0,1,3]]
Out
3

Two roads join the same pair. The heap offers the 3 first, and the 5 is thrown away later.

One town

In
V = 1, edges = []
Out
0

The blot starts on the only town. Nothing to pay.

Way 1: the simple way

Keep a price tag on every town outside the blot: the cheapest road from the blot to that town found so far (its key). Each turn, scan all towns to find the dry one with the smallest tag.

  1. Every tag starts at infinity, except town 0 at 0.
  2. Repeat V times: scan all towns for the dry one with the smallest tag. Ink it and add its tag to the total.
  3. For each road from the new town to a dry town, lower that town's tag if the road is cheaper.
java
import java.util.*;

class Solution {
    public int spanningTree(int V, int[][] edges) {
        List<List<int[]>> adj = new ArrayList<>();
        for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
        for (int[] e : edges) {
            adj.get(e[0]).add(new int[]{e[1], e[2]});
            adj.get(e[1]).add(new int[]{e[0], e[2]});
        }
        int[] key = new int[V];                    // cheapest road from the blot
        Arrays.fill(key, Integer.MAX_VALUE);
        key[0] = 0;
        boolean[] inked = new boolean[V];
        int cost = 0;
        for (int turn = 0; turn < V; turn++) {
            int u = -1;
            for (int v = 0; v < V; v++) {          // scan every town
                if (!inked[v] && (u == -1 || key[v] < key[u])) u = v;
            }
            inked[u] = true;
            cost += key[u];
            for (int[] nb : adj.get(u)) {
                if (!inked[nb[0]] && nb[1] < key[nb[0]]) key[nb[0]] = nb[1];
            }
        }
        return cost;
    }
}

How long it takes: V turns, each scanning V towns, O(V^2) (the roads add O(E), which is at most V^2). Extra room: the tags and flags, O(V). This is actually the best choice when almost every pair of towns has a road.

Way 2: the clever way

Put the offers in a min-heap, a bag that always gives back the cheapest offer. An offer is "town v can join for price w".

  1. Push the offer (town 0, price 0).
  2. Pop the cheapest offer. If that town is already inked, the offer is old: throw it away.
  3. Otherwise ink the town and add the price. Push an offer for each road from it to a dry town.
  4. Stop when all V towns are inked.

We do not delete old offers when a cheaper one shows up. We let them sit in the heap and skip them when they come out. This is called lazy deletion, and it keeps the code short.

The heap always hands over the cheapest road out of the blot

01 / 10

graph
43124265012345
heapoffers
0:000:0

Start the tree at town 0. Put 0:0 on the heap: town 0, joining for price 0. Each heap entry reads town:price, and the cheapest one is always on top.

cost
0
  • looking now
  • checking
  • done
  • answer
  • rejected
  • just added
  • ignored
java
import java.util.*;

class Solution {
    public int spanningTree(int V, int[][] edges) {
        List<List<int[]>> adj = new ArrayList<>();
        for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
        for (int[] e : edges) {
            adj.get(e[0]).add(new int[]{e[1], e[2]});
            adj.get(e[1]).add(new int[]{e[0], e[2]});
        }
        boolean[] inked = new boolean[V];
        PriorityQueue<int[]> heap = new PriorityQueue<>((x, y) -> Integer.compare(x[0], y[0]));  // {price, town}
        heap.add(new int[]{0, 0});
        int cost = 0, joined = 0;
        while (!heap.isEmpty() && joined < V) {
            int[] top = heap.poll();
            int w = top[0], u = top[1];
            if (inked[u]) continue;               // an old offer, skip it
            inked[u] = true;
            cost += w;
            joined++;
            for (int[] nb : adj.get(u)) {
                if (!inked[nb[0]]) heap.add(new int[]{nb[1], nb[0]});
            }
        }
        return cost;
    }
}

How long it takes: every road can add one offer from each end, so the heap sees O(E) pushes and pops, each O(log E). Since log E is at most 2 log V, that is O(E log V). Extra room: the neighbour lists and the heap, O(V + E).

Watch out for

  • Treating it like Dijkstra. Dijkstra pushes the total distance from the start (dist[u] + w). Prim pushes only the price of the one road, w.
  • A map in pieces. The blot never reaches the other piece, so joined stays below V. Check it if you must report that.
  • Building the neighbour list one way only. Roads are two-way, so add both directions.
  • Stopping when the heap is empty is fine too, but stopping at joined == V saves popping old offers.

What interviewers ask next

  • Prim or Kruskal? On a sparse map both are about O(E log V). On a dense map, the simple O(V^2) Prim from Way 1 is the fastest, with no heap at all.
  • Can you print the roads, not only the total? Push the town the offer came from too, {price, town, from}, and record from-town when a town joins.
  • Why is it correct? The cheapest road leaving the blot crosses the split "blot versus the rest", and the cheapest road across any split is safe to take (the cut property).
  • Minimum cost to connect points on a plane (LeetCode 1584)? Every pair of points is a road, so the map is dense: use the O(V^2) version.
  • Can you do better than O(E log V)? A Fibonacci heap with decrease-key gives O(E + V log V), but it is rarely used in practice.

Remember this

Grown-up words: towns are vertices, roads are weighted edges, the blot is the growing tree, the magic bag is a min-heap (priority queue), the price tags are keys, skipping stale offers is lazy deletion, the result is a minimum spanning tree (MST), and the method is Prim's algorithm.