Structs & Data Structures · intermediate · ~14 min
Connect everything at least cost.
A minimum spanning tree connects every vertex of a weighted undirected graph using a subset of edges with the least possible total weight. Prim's algorithm grows the tree outward: repeatedly add the cheapest edge that connects a new vertex to the tree. If at some point no edge reaches a still-outside vertex, the graph is disconnected and no spanning tree exists.
MSTs minimise the cost to connect everything: laying cable or pipe, designing networks, clustering data, approximating hard tour problems. Prim's growth strategy is also a clean template for greedy algorithms in general.
Prim's growth. Maintain a key[] = cheapest known edge into the tree for each outside vertex; add the minimum, then update its neighbours' keys.
Disconnection. If the minimum key is ∞, some vertex is unreachable → return 'no MST'.
Connectivity. Whether an undirected graph is connected at all is just one DFS/BFS reaching every vertex.
Prim vs Kruskal. Prim grows one tree; Kruskal sorts edges and unions components — both give the same total weight.
#include <stdlib.h>
/* Prim: grow one tree, always adding the cheapest edge to a new vertex.
Symmetric w[]; 0 = no edge. Returns -1 if the graph is disconnected. */
long mst_weight(const int *w, int n) {
if (n <= 0) return 0;
const long INF = 1000000000L;
long *key = malloc((size_t)n * sizeof *key);
int *in = calloc((size_t)n, sizeof *in);
if (!key || !in) { free(key); free(in); return -1; }
for (int i = 0; i < n; i++) key[i] = INF;
key[0] = 0;
long total = 0;
for (int it = 0; it < n; it++) {
int u = -1; long best = INF;
for (int i = 0; i < n; i++)
if (!in[i] && key[i] < best) { best = key[i]; u = i; }
if (u < 0) { free(key); free(in); return -1; } /* DISCONNECTED */
in[u] = 1; total += key[u];
for (int v = 0; v < n; v++) {
int e = w[u*n + v];
if (e > 0 && !in[v] && e < key[v]) key[v] = e; /* edge weight, NOT a path cost */
}
}
free(key); free(in);
return total;
}
Key points:
key[v] is the weight of the single cheapest edge into the tree — not a cumulative distance. That one difference is what separates Prim from Dijkstra.u < 0 with vertices still outside the tree means the graph is disconnected.key[0] = 0 seeds the tree at vertex 0; any start vertex gives the same total weight.A minimum spanning tree connects every vertex of a weighted undirected graph using edges of least total weight. Prim's algorithm grows a tree, always adding the cheapest edge that reaches a new vertex; if no such edge remains while vertices are still outside, the graph is disconnected and has no spanning tree. Connectivity itself is just a single traversal.
#include <stdio.h> int main(void){ int n=4; /* undirected weighted (symmetric) */ int w[16]={ 0,1,3,0, 1,0,1,4, 3,1,0,1, 0,4,1,0 }; long INF=1000000000L, key[4]; int in[4]={0}; long total=0; for(int i=0;i<n;i++) key[i]=INF; key[0]=0; for(int it=0;it<n;it++){ int u=-1; long best=INF; for(int i=0;i<n;i++) if(!in[i]&&key[i]<best){best=key[i];u=i;} in[u]=1; total+=key[u]; for(int v=0;v<n;v++){ int e=w[u*n+v]; if(e>0&&!in[v]&&e<key[v]) key[v]=e; } } printf("MST total weight = %ld\n", total); return 0; }
| Step | Line | What happens |
|---|---|---|
| 1 | key[0] = 0, rest INF |
Vertex 0 is free to add; nothing else is reachable yet. |
| 2 | selection | Picks the outside vertex with the cheapest known edge into the tree. |
| 3 | total += key[u] |
Adds that edge's weight — the tree grows by exactly one vertex and one edge. |
| 4 | update loop | For each neighbour still outside, lower key[v] if this new tree vertex offers a cheaper edge. |
| 5 | e < key[v], not total + e |
Prim compares raw edge weights; using a running total would compute shortest paths instead. |
| 6 | u < 0 |
No outside vertex has any edge into the tree -> the graph is disconnected, so no spanning tree exists. |
Not detecting disconnection (an unreachable vertex). Re-adding an edge to a vertex already in the tree. Treating weight 0 as an edge. Assuming the graph is connected without checking.
Compiler errors and warnings:
-Wsign-compare mixing int weights with long accumulators.Runtime symptoms:
key[u] + e instead of e. Prim compares edge weights alone.u < 0 check is missing, so the loop silently stops early. It must return the "no MST" signal.in[u] = 1 missing, so the same vertex is selected forever.Technique: verify the total against Kruskal's result on the same graph — two independent algorithms agreeing is strong evidence. Also test a deliberately disconnected graph.
u < 0 branch is the correctness guard.return -1 inside the loop that must also free.in must be zeroed (calloc); garbage marks vertices as already in the tree and produces an under-weight result.w must be symmetric for an undirected MST.total sums up to n-1 edge weights — use a wide accumulator when weights are large.n before the size_t casts.Concrete uses: Laying cable, pipe or fibre at minimum total cost; designing low-cost network backbones; clustering (removing the heaviest MST edges splits data into groups); approximation algorithms for the travelling-salesman problem; and image segmentation via minimum spanning forests. Connectivity checking alone — a single traversal — answers "is this network in one piece?", which is a common monitoring question.
Professional best practices:
Beginner:
key[v] as an edge weight and say so in a comment; the Dijkstra confusion is extremely common.Intermediate:
1. (Beginner) MST weight. Implement mst_weight returning -1 for a disconnected graph. Example: the demo graph -> 3. Concepts: Prim's growth, disconnection detection.
2. (Beginner) Connectivity. Implement int is_connected(const int *adj, int n) with a single DFS or BFS. Concepts: one traversal is enough.
3. (Intermediate) Report the edges. Extend Prim to record which edge was chosen for each added vertex and print the tree. Concepts: tracking provenance.
4. (Intermediate) Cross-check with Kruskal. Implement Kruskal with union-find and confirm both give the same total on random graphs. Concepts: independent-oracle validation.
A minimum spanning tree connects every vertex at the least total edge weight, and Prim's algorithm builds it by repeatedly adding the cheapest edge that reaches a vertex still outside the tree. The crucial distinction from Dijkstra is that key[v] holds a single edge weight, not a cumulative path cost — comparing e rather than key[u] + e is the entire difference between the two algorithms. If no outside vertex has any edge into the tree while vertices remain, the graph is disconnected and no spanning tree exists; that case must be reported rather than silently returning a partial total. Prim's O(n^2) form suits dense matrices, while Kruskal with union-find suits sparse edge lists.