Prim's algorithm
Prim's algorithm grows one tree from a start vertex, always adding the cheapest edge that leaves the tree; with a heap it runs in O((n + m) log n), and a plain array gives O(n²) for dense graphs.
Learning goals
- Run Prim with a heap of frontier edges
- Compare the lazy version with the eager version that keeps one key per vertex
- Build the adjacency list from an edge list
- Use the array version on dense graphs
Prerequisites
Prim looks like Dijkstra, but a vertex's priority is the weight of one edge to the tree, not the length of a whole path. Which algorithm is better depends on the graph: sparse favours Kruskal or heap Prim, dense favours array Prim.
Frontier heap
Start with one vertex. The **frontier** is the set of edges with exactly one end in the tree. Take the lightest frontier edge (the cut property again, with S = the tree), add its far vertex, and put that vertex's edges on the frontier. A min-heap holds the frontier.
In the **lazy** version an edge stays in the heap after its far end joined the tree some other way; when it is popped it is discarded as outdated. The heap can hold up to m entries and the cost is O(m log m). Choose *Prim, lazy* in the animation: outdated entries are drawn struck out.
1static long primLazy(int n, List<int[]>[] adj, int s) { // adj[u] = {v, w}; weight of the tree of s's component2boolean[] in = new boolean[n];3PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[1], b[1])); // {vertex, weight of the edge that reaches it}4pq.add(new int[]{s, 0});5long total = 0;6while (!pq.isEmpty()) {7int[] top = pq.poll();8if (in[top[0]]) continue; // outdated entry: already in the tree9in[top[0]] = true;10total += top[1];11for (int[] e : adj[top[0]]) if (!in[e[0]]) pq.add(new int[]{e[0], e[1]});12}13return total;14}
Minimum spanning tree: Kruskal and Prim (lazy and eager)
Interactive · edit the inputundirected graph · 6 vertices · 8 edges
C0
C1
C2
C3
C4
C5
edges, lightest first
component leader of v
Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components.
1static long kruskal(int n, int[][] edges) { // edges {u, v, w}; total weight of the minimum spanning forest2int[][] sorted = edges.clone();3Arrays.sort(sorted, Comparator.comparingInt(e -> e[2])); // lightest first4int[] parent = new int[n];5for (int i = 0; i < n; i++) parent[i] = i;6long total = 0;7for (int[] e : sorted) {8int a = find(parent, e[0]), b = find(parent, e[1]);9if (a == b) continue; // already connected: this edge would close a cycle10parent[a] = b;11total += e[2];12}13return total;14}15static int find(int[] p, int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }16static long primLazy(int n, List<int[]>[] adj, int s) { // adj[u] = {v, w}; weight of the tree of s's component17boolean[] in = new boolean[n];18PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // {vertex, weight of the edge that reaches it}19pq.add(new int[]{s, 0});20long total = 0;21while (!pq.isEmpty()) {22int[] top = pq.poll();23if (in[top[0]]) continue; // outdated: already in the tree24in[top[0]] = true;25total += top[1];26for (int[] e : adj[top[0]]) if (!in[e[0]]) pq.add(new int[]{e[0], e[1]});27}28return total;29}30static long primEager(int n, List<int[]>[] adj, int s) { // key[v] = cheapest edge from the tree to v31long INF = Long.MAX_VALUE / 2;32long[] key = new long[n];33Arrays.fill(key, INF);34key[s] = 0;35boolean[] in = new boolean[n];36long total = 0;37for (int step = 0; step < n; step++) {38int u = -1;39for (int v = 0; v < n; v++) if (!in[v] && key[v] < INF && (u == -1 || key[v] < key[u])) u = v;40if (u == -1) break; // the rest is unreachable41in[u] = true;42total += key[u];43for (int[] e : adj[u]) if (!in[e[0]] && e[1] < key[e[0]]) key[e[0]] = e[1]; // decrease key44}45return total;46}
Live state
n
6
edges
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=6 edges=8 | Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components. |
| 2 | L11 | edge=1-2:1 total=1 | 1-2:1 joins two different components: add it to the tree. Total weight 1. |
| 3 | L11 | edge=1-3:2 total=3 | 1-3:2 joins two different components: add it to the tree. Total weight 3. |
| 4 | L11 | edge=0-2:3 total=6 | 0-2:3 joins two different components: add it to the tree. Total weight 6. |
| 5 | L9 | edge=0-1:4 | 0-1:4: 0 and 1 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 6 | L9 | edge=2-3:4 | 2-3:4: 2 and 3 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 7 | L11 | edge=2-4:5 total=11 | 2-4:5 joins two different components: add it to the tree. Total weight 11. |
| 8 | L11 | edge=4-5:6 total=17 | 4-5:6 joins two different components: add it to the tree. Total weight 17. |
| 9 | L9 | edge=3-4:7 | 3-4:7: 3 and 4 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 10 | L13 | return=17 components=1 | The tree has 5 edges and total weight 17 (the smallest possible). |
Eager/lazy versions
The **eager** version keeps for each outside vertex only the cheapest edge found so far, key[v]. When a new vertex joins the tree, its edges can lower the keys of neighbours; the heap entry is replaced (decrease-key). The heap then holds at most n entries and the cost is O(m log n).
Java's PriorityQueue cannot decrease a key cheaply, so the code uses a TreeSet ordered by {key, vertex} as an indexed heap: remove the old entry and insert the new one, both O(log n). The *Prim, eager* animation shows the key row.
1static long primEagerTree(int n, List<int[]>[] adj, int s) { // one entry per vertex2long INF = Long.MAX_VALUE / 2;3long[] key = new long[n];4Arrays.fill(key, INF);5key[s] = 0;6boolean[] in = new boolean[n];7TreeSet<long[]> open = new TreeSet<>((a, b) -> a[0] != b[0] ? Long.compare(a[0], b[0]) : Long.compare(a[1], b[1])); // {key, vertex}8open.add(new long[]{0, s});9long total = 0;10while (!open.isEmpty()) {11long[] top = open.pollFirst();12int u = (int) top[1];13in[u] = true;14total += top[0];15for (int[] e : adj[u]) {16if (in[e[0]] || e[1] >= key[e[0]]) continue;17if (key[e[0]] != INF) open.remove(new long[]{key[e[0]], e[0]}); // decrease key: replace the entry18key[e[0]] = e[1];19open.add(new long[]{e[1], e[0]});20}21}22return total;23}
Minimum spanning tree: Kruskal and Prim (lazy and eager)
Interactive · edit the inputundirected graph · 6 vertices · 8 edges
C0
C1
C2
C3
C4
C5
edges, lightest first
component leader of v
Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components.
1static long kruskal(int n, int[][] edges) { // edges {u, v, w}; total weight of the minimum spanning forest2int[][] sorted = edges.clone();3Arrays.sort(sorted, Comparator.comparingInt(e -> e[2])); // lightest first4int[] parent = new int[n];5for (int i = 0; i < n; i++) parent[i] = i;6long total = 0;7for (int[] e : sorted) {8int a = find(parent, e[0]), b = find(parent, e[1]);9if (a == b) continue; // already connected: this edge would close a cycle10parent[a] = b;11total += e[2];12}13return total;14}15static int find(int[] p, int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }16static long primLazy(int n, List<int[]>[] adj, int s) { // adj[u] = {v, w}; weight of the tree of s's component17boolean[] in = new boolean[n];18PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // {vertex, weight of the edge that reaches it}19pq.add(new int[]{s, 0});20long total = 0;21while (!pq.isEmpty()) {22int[] top = pq.poll();23if (in[top[0]]) continue; // outdated: already in the tree24in[top[0]] = true;25total += top[1];26for (int[] e : adj[top[0]]) if (!in[e[0]]) pq.add(new int[]{e[0], e[1]});27}28return total;29}30static long primEager(int n, List<int[]>[] adj, int s) { // key[v] = cheapest edge from the tree to v31long INF = Long.MAX_VALUE / 2;32long[] key = new long[n];33Arrays.fill(key, INF);34key[s] = 0;35boolean[] in = new boolean[n];36long total = 0;37for (int step = 0; step < n; step++) {38int u = -1;39for (int v = 0; v < n; v++) if (!in[v] && key[v] < INF && (u == -1 || key[v] < key[u])) u = v;40if (u == -1) break; // the rest is unreachable41in[u] = true;42total += key[u];43for (int[] e : adj[u]) if (!in[e[0]] && e[1] < key[e[0]]) key[e[0]] = e[1]; // decrease key44}45return total;46}
Live state
n
6
edges
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=6 edges=8 | Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components. |
| 2 | L11 | edge=1-2:1 total=1 | 1-2:1 joins two different components: add it to the tree. Total weight 1. |
| 3 | L11 | edge=1-3:2 total=3 | 1-3:2 joins two different components: add it to the tree. Total weight 3. |
| 4 | L11 | edge=0-2:3 total=6 | 0-2:3 joins two different components: add it to the tree. Total weight 6. |
| 5 | L9 | edge=0-1:4 | 0-1:4: 0 and 1 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 6 | L9 | edge=2-3:4 | 2-3:4: 2 and 3 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 7 | L11 | edge=2-4:5 total=11 | 2-4:5 joins two different components: add it to the tree. Total weight 11. |
| 8 | L11 | edge=4-5:6 total=17 | 4-5:6 joins two different components: add it to the tree. Total weight 17. |
| 9 | L9 | edge=3-4:7 | 3-4:7: 3 and 4 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 10 | L13 | return=17 components=1 | The tree has 5 edges and total weight 17 (the smallest possible). |
Adjacency list
Prim needs the neighbours of the vertex it just added, so an **adjacency list** is the natural input: an array of lists, each entry {neighbour, weight}. An edge list is converted by adding each undirected edge to both endpoints' lists, in O(n + m).
Looking for the outgoing edges in an edge list would scan all m edges at every step and cost O(n · m). The same conversion step is the first line of almost every graph program.
1static long primOnEdgeList(int n, int[][] edges) { // builds the lists first; weight of the tree containing vertex 02List<int[]>[] adj = new List[n];3for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();4for (int[] e : edges) {5adj[e[0]].add(new int[]{e[1], e[2]});6adj[e[1]].add(new int[]{e[0], e[2]});7}8boolean[] in = new boolean[n];9PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[1], b[1]));10pq.add(new int[]{0, 0});11long total = 0;12while (!pq.isEmpty()) {13int[] top = pq.poll();14if (in[top[0]]) continue;15in[top[0]] = true;16total += top[1];17for (int[] e : adj[top[0]]) if (!in[e[0]]) pq.add(e);18}19return total;20}
Minimum spanning tree: Kruskal and Prim (lazy and eager)
Interactive · edit the inputundirected graph · 6 vertices · 8 edges
C0
C1
C2
C3
C4
C5
edges, lightest first
component leader of v
Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components.
1static long kruskal(int n, int[][] edges) { // edges {u, v, w}; total weight of the minimum spanning forest2int[][] sorted = edges.clone();3Arrays.sort(sorted, Comparator.comparingInt(e -> e[2])); // lightest first4int[] parent = new int[n];5for (int i = 0; i < n; i++) parent[i] = i;6long total = 0;7for (int[] e : sorted) {8int a = find(parent, e[0]), b = find(parent, e[1]);9if (a == b) continue; // already connected: this edge would close a cycle10parent[a] = b;11total += e[2];12}13return total;14}15static int find(int[] p, int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }16static long primLazy(int n, List<int[]>[] adj, int s) { // adj[u] = {v, w}; weight of the tree of s's component17boolean[] in = new boolean[n];18PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // {vertex, weight of the edge that reaches it}19pq.add(new int[]{s, 0});20long total = 0;21while (!pq.isEmpty()) {22int[] top = pq.poll();23if (in[top[0]]) continue; // outdated: already in the tree24in[top[0]] = true;25total += top[1];26for (int[] e : adj[top[0]]) if (!in[e[0]]) pq.add(new int[]{e[0], e[1]});27}28return total;29}30static long primEager(int n, List<int[]>[] adj, int s) { // key[v] = cheapest edge from the tree to v31long INF = Long.MAX_VALUE / 2;32long[] key = new long[n];33Arrays.fill(key, INF);34key[s] = 0;35boolean[] in = new boolean[n];36long total = 0;37for (int step = 0; step < n; step++) {38int u = -1;39for (int v = 0; v < n; v++) if (!in[v] && key[v] < INF && (u == -1 || key[v] < key[u])) u = v;40if (u == -1) break; // the rest is unreachable41in[u] = true;42total += key[u];43for (int[] e : adj[u]) if (!in[e[0]] && e[1] < key[e[0]]) key[e[0]] = e[1]; // decrease key44}45return total;46}
Live state
n
6
edges
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=6 edges=8 | Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components. |
| 2 | L11 | edge=1-2:1 total=1 | 1-2:1 joins two different components: add it to the tree. Total weight 1. |
| 3 | L11 | edge=1-3:2 total=3 | 1-3:2 joins two different components: add it to the tree. Total weight 3. |
| 4 | L11 | edge=0-2:3 total=6 | 0-2:3 joins two different components: add it to the tree. Total weight 6. |
| 5 | L9 | edge=0-1:4 | 0-1:4: 0 and 1 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 6 | L9 | edge=2-3:4 | 2-3:4: 2 and 3 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 7 | L11 | edge=2-4:5 total=11 | 2-4:5 joins two different components: add it to the tree. Total weight 11. |
| 8 | L11 | edge=4-5:6 total=17 | 4-5:6 joins two different components: add it to the tree. Total weight 17. |
| 9 | L9 | edge=3-4:7 | 3-4:7: 3 and 4 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 10 | L13 | return=17 components=1 | The tree has 5 edges and total weight 17 (the smallest possible). |
Dense graph variant
When m is close to n², a heap is overkill. Keep key[v] in a plain array and find the smallest outside key by scanning all n entries each round: n rounds of O(n) is O(n²), with no sorting and no heap. The adjacency can be a matrix.
For a complete graph with n vertices, m is about n² / 2, so O(n²) beats Kruskal's O(m log m) and heap Prim's O(m log n). The array version is also the easiest to write correctly.
1static long primDense(int[][] w) { // w[i][j] = weight, Integer.MAX_VALUE when there is no edge2int n = w.length;3long[] key = new long[n];4Arrays.fill(key, Long.MAX_VALUE);5key[0] = 0;6boolean[] in = new boolean[n];7long total = 0;8for (int step = 0; step < n; step++) {9int u = -1;10for (int v = 0; v < n; v++) if (!in[v] && key[v] != Long.MAX_VALUE && (u == -1 || key[v] < key[u])) u = v;11if (u == -1) break; // the rest is unreachable12in[u] = true;13total += key[u];14for (int v = 0; v < n; v++)15if (!in[v] && w[u][v] != Integer.MAX_VALUE && w[u][v] < key[v]) key[v] = w[u][v];16}17return total;18}
Minimum spanning tree: Kruskal and Prim (lazy and eager)
Interactive · edit the inputundirected graph · 6 vertices · 8 edges
C0
C1
C2
C3
C4
C5
edges, lightest first
component leader of v
Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components.
1static long kruskal(int n, int[][] edges) { // edges {u, v, w}; total weight of the minimum spanning forest2int[][] sorted = edges.clone();3Arrays.sort(sorted, Comparator.comparingInt(e -> e[2])); // lightest first4int[] parent = new int[n];5for (int i = 0; i < n; i++) parent[i] = i;6long total = 0;7for (int[] e : sorted) {8int a = find(parent, e[0]), b = find(parent, e[1]);9if (a == b) continue; // already connected: this edge would close a cycle10parent[a] = b;11total += e[2];12}13return total;14}15static int find(int[] p, int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }16static long primLazy(int n, List<int[]>[] adj, int s) { // adj[u] = {v, w}; weight of the tree of s's component17boolean[] in = new boolean[n];18PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // {vertex, weight of the edge that reaches it}19pq.add(new int[]{s, 0});20long total = 0;21while (!pq.isEmpty()) {22int[] top = pq.poll();23if (in[top[0]]) continue; // outdated: already in the tree24in[top[0]] = true;25total += top[1];26for (int[] e : adj[top[0]]) if (!in[e[0]]) pq.add(new int[]{e[0], e[1]});27}28return total;29}30static long primEager(int n, List<int[]>[] adj, int s) { // key[v] = cheapest edge from the tree to v31long INF = Long.MAX_VALUE / 2;32long[] key = new long[n];33Arrays.fill(key, INF);34key[s] = 0;35boolean[] in = new boolean[n];36long total = 0;37for (int step = 0; step < n; step++) {38int u = -1;39for (int v = 0; v < n; v++) if (!in[v] && key[v] < INF && (u == -1 || key[v] < key[u])) u = v;40if (u == -1) break; // the rest is unreachable41in[u] = true;42total += key[u];43for (int[] e : adj[u]) if (!in[e[0]] && e[1] < key[e[0]]) key[e[0]] = e[1]; // decrease key44}45return total;46}
Live state
n
6
edges
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=6 edges=8 | Sort the edges by weight (ties keep input order). Every vertex starts as its own component; an edge is taken when it joins two different components. |
| 2 | L11 | edge=1-2:1 total=1 | 1-2:1 joins two different components: add it to the tree. Total weight 1. |
| 3 | L11 | edge=1-3:2 total=3 | 1-3:2 joins two different components: add it to the tree. Total weight 3. |
| 4 | L11 | edge=0-2:3 total=6 | 0-2:3 joins two different components: add it to the tree. Total weight 6. |
| 5 | L9 | edge=0-1:4 | 0-1:4: 0 and 1 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 6 | L9 | edge=2-3:4 | 2-3:4: 2 and 3 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 7 | L11 | edge=2-4:5 total=11 | 2-4:5 joins two different components: add it to the tree. Total weight 11. |
| 8 | L11 | edge=4-5:6 total=17 | 4-5:6 joins two different components: add it to the tree. Total weight 17. |
| 9 | L9 | edge=3-4:7 | 3-4:7: 3 and 4 are already connected through the tree, so this edge would close a cycle. Reject it. |
| 10 | L13 | return=17 components=1 | The tree has 5 edges and total weight 17 (the smallest possible). |
Practice ladder
Now try it yourself
Easy
- Find if Path Exists in Graph
Medium
- Min Cost to Connect All Points
- Connecting Cities With Minimum Cost
Advanced
- Optimize Water Distribution in a Village
Key takeaways
- Prim grows one tree, always adding the lightest edge that leaves it.
- Lazy Prim keeps outdated edges in the heap and skips them; eager Prim keeps one key per vertex with decrease-key.
- Start from an adjacency list; an edge list makes every step scan all edges.
- On dense graphs the array version runs in O(n²) without any heap.