Abstract

What it is: Prim’s Algorithm is a Greedy Algorithm that builds a Minimum Spanning Tree (MST) for a connected, undirected, weighted graph by repeatedly growing a single tree one cheapest edge at a time.

  • Category: Minimum Spanning Tree / Greedy Algorithm
  • Input: Undirected, connected graph with (not necessarily positive) edge weights
  • Output: A list of edges forming a minimum spanning tree of (total weight is minimized among all spanning trees)
  • Paradigm: Greedy, priority-queue-driven frontier expansion
  • Typical use cases: network design (minimizing total cable/pipe/wire length), clustering, approximation algorithms that use an MST as a subroutine

Core Logic (High-Level)

  1. Put all vertices in (undiscovered).
  2. Pick any vertex to start from.
  3. Put in (the tree built so far).
  4. Repeat until all vertices are in :
    1. Find the minimum edge that has one vertex in and one vertex outside it
    2. Move that outside endpoint from into .
    3. Add that edge to the output.

Naively, step 4.1 means scanning every edge crossing the boundary of each iteration. The cost array below avoids that: it caches the cheapest crossing edge per vertex, so step 4.1 becomes “pick the frontier vertex with the smallest cost” instead.

Key Idea

Instead of keeping track by looking at edges, put the cost information in the vertices themselves (cost array) — cost(u) is the cheapest edge weight seen so far connecting to the tree.

Update a vertex by putting the lowest cost vertex from into , then re-check its neighbors: if a neighbor now has a cheaper bridge through this new tree vertex, update its cost (same “relax” idea as Dijkstra’s Algorithm, just comparing one edge weight instead of a cumulative path).


Pseudocode (Mid-Level Implementation)

Similar to Dijkstra’s Algorithm, Prim’s uses the value instead of — same loop, same priority queue, but the relaxation compares just the single edge weight against cost(u), not a cumulative path length.

Algorithm 39 Prim's Algorithm

Input: GG undirected connecred graph with positive edge weights

Output: outputoutput: a list of edges that describe a minimum spanning tree

procedure Prim's(GG)

Pick a random vertex ss

Initialize XX = empty, F={s}F = \{s\}

Initialize cost(v)=cost(v) = \infty for all vv

Initialize cost(s)=0cost(s) = 0

Initialize prev(s)=nullprev(s) = null

Initialize output=output = empty

while FF is not empty do

Pick the vFv \in F that has the lowestlowest cost(v)cost(v) value

for each neighbor uu of vv do

if cost(u)>(v,u)cost(u) > \ell(v, u) then

Move uu to FF

Set cost(u)=(v,u)cost(u) = \ell(v, u)

Set prev(u)=vprev(u) = v

Move vv from FF to XX

Move (v,prev(v))(v, prev(v)) into outputoutput

return outputoutput

Variables & Data Structures

NameTypePurpose
XSetVertices already included in the growing spanning tree
FSet / Priority QueueFrontier — vertices discovered (adjacent to X) but not yet added, keyed by cost
USet (implicit)Vertices not yet discovered at all ()
costArray (vertex → number)Cheapest known edge weight connecting the vertex to the current tree ; starts at except
prevArray (vertex → vertex)The tree-neighbor that offered the current best cost; used to reconstruct the actual MST edges
outputList of edgesAccumulates the edges that make up the final MST
v, uVertexCurrent minimum-cost vertex being finalized / candidate neighbor

Helper Functions / Operations Used

  • ℓ(v, u) — the weight of the edge between v and u; O(1) lookup with an adjacency list/matrix
  • Pick with lowest cost(v) — a deletemin operation on the priority queue backing F
  • Relax a neighbor — if , update cost(u) and prev(u), and move/re-prioritize u in F (a decreasekey, or an insert if u was previously in U)

Low-Level Implementation

The low-level implementation is essentially Dijkstra’s low-level implementation with dist renamed to cost and the relaxation condition changed from to — i.e. compare against the single edge weight, not the cumulative path weight. Same makequeue / deletemin / decreasekey primitives apply, and the same array-vs-binary-heap trade-off discussion carries over directly.


Proof of Correctness

Claim: Upon termination, the edges in output form a minimum spanning tree of .

Loop Invariant (Cut Property): At the start of each iteration of the while loop, the edges already added to output form a subset of some minimum spanning tree of — equivalently, can always be extended to a full MST using only edges not yet ruled out.

  • Initialization: Before the first iteration, output is empty and . The empty edge set is trivially a subset of any MST, so the invariant holds.
  • Maintenance: Suppose the invariant holds — ‘s edges so far are consistent with some MST . Consider the cut . By the Cut Property, the minimum-weight edge crossing this cut is guaranteed to be in some MST. Prim’s always selects exactly this edge next — the vertex with lowest cost(v) is, by construction, the endpoint of the cheapest edge crossing the cut (since cost(v) was set to the weight of the cheapest edge from to during relaxation). So adding to output keeps the invariant true — either already contains this edge, or swapping it into (removing whatever edge used to connect ) produces another MST of equal or lower weight, since the swapped-in edge is minimum-weight across the cut. See Cut Property for the full proof of why this swap argument works.
  • Termination: Each iteration moves exactly one vertex from (or , via relaxation) into , so after iterations all vertices are in and output contains exactly edges — the size of a spanning tree on a connected graph. The while loop then ends because becomes empty.

Why it doesn’t miss/duplicate vertices: Each vertex is moved into exactly once (guarded implicitly by only picking , and only ever contains vertices not already in ); since is connected, every vertex is eventually discovered as a neighbor of some vertex already in , so no vertex is permanently stuck in .

Conclusion: By induction, when the loop terminates (), output is a spanning tree consistent with an MST at every step, and since it spans all of with exactly minimum-cut-respecting edges, output is a minimum spanning tree.


Time & Space Complexity Analysis

Basically has the same runtime as Dijkstra’s, using the same formula — the one difference is that Prim’s requires a connected input graph, so . That’s why the term drops out of the totals below: it’s always dominated by , so the runtime is stated purely in terms of and together rather than as a sum of two competing terms.

  • Binary Heap
  • Array

General Case

ComplexityNotes
TimeEvery vertex is finalized once (deletemin), every edge is examined at most twice (once from each endpoint) as a relaxation candidate
Spacecost, prev arrays + priority queue holding the frontier, plus output of size $

Implementation-Dependent Variations

Data Structure ChoiceImpact on TimeImpact on SpaceNotes
Array as Priority Queue total — deletemin is , decreasekey is Better for dense graphs where
Binary Heap as Priority Queue total — deletemin and decreasekey are both + supplemental “address book” array to locate vertices in the heapBetter for sparse graphs where ; note $
Adjacency list vs matrix vs for scanning neighbors vs Matrix only worth it on already-dense graphs

Best / Worst / Average Case

  • Best case: Still — Prim’s has no early-exit condition; it must process every vertex and consider every edge to guarantee the minimum spanning tree, regardless of graph shape.
  • Worst case: Same order — dense graph maximizes both the number of decreasekey calls and, if using an array-backed PQ, the cost of each deletemin.
  • Average case: Same asymptotic order; Prim’s has no probabilistic behavior to average over.

Drawbacks / Constraints

  • Preconditions: must be connected and undirected — Prim’s does not handle directed graphs (there is no directed-graph analogue of a spanning tree in the same sense) and will not produce a spanning structure if is disconnected (some vertices would remain in forever, stuck at ).
  • Unlike Dijkstra’s, negative edge weights are fine. Prim’s only ever compares single-edge weights (), never cumulative path weights, so the greedy cut-property argument still holds even with negative weights — there’s no analogue of Dijkstra’s “a later negative edge could undercut an already-finalized path” failure mode.
  • Not suitable for: Finding shortest paths between vertices — an MST minimizes total tree weight, not pairwise path weight; use Dijkstra’s Algorithm (non-negative weights) or Bellman-Ford (negative weights allowed) for shortest paths instead.
  • MST is not unique in general. If multiple edges tie for minimum weight across a cut, different tie-breaking choices can produce different (but equally minimal-weight) spanning trees.
  • Alternatives to consider: Kruskal’s Algorithm — a different greedy MST algorithm that sorts all edges globally and uses union-find, often preferable for very sparse graphs or when edges are already sorted/streamed.

References / Links