Prim's Algorithm
Prim's Algorithm
Category
Famous Algorithms
Difficulty
Very Hard
Problem Statement
Given a connected, undirected, weighted graph, find the minimum spanning tree (MST) -- the subset of edges that connects all vertices with the minimum total edge weight, without forming any cycles. Prim's algorithm builds the MST by greedily adding the cheapest edge that connects a visited vertex to an unvisited vertex.
Intuition
Prim's algorithm is based on the cut property of MSTs: for any cut of the graph (a partition of vertices into two non-empty sets), the lightest edge crossing the cut is in some MST. The algorithm maintains a growing set S of visited vertices. At each step, the cheapest edge from S to V - S is added, and the newly connected vertex joins S. A min-heap (priority queue) efficiently retrieves the cheapest crossing edge.
Approach
- Initialize: pick an arbitrary starting vertex. Add it to the visited set
S. Push all its edges into a min-heap keyed by edge weight. - While the heap is non-empty and
|S| < |V|:- Extract the minimum-weight edge
(u, v, w)from the heap. - If
vis already inS, skip (it would form a cycle). - Otherwise, add
vtoS, add edge(u, v, w)to the MST. - Push all edges from
vto unvisited neighbors into the heap.
- Extract the minimum-weight edge
- When
|S| = |V|, the MST is complete. The collected edges form the MST.
Pseudocode
function primsAlgorithm(graph, startVertex):
n = number of vertices
visited = set containing startVertex
mstEdges = []
totalWeight = 0
minHeap = empty min-heap
// Push all edges from startVertex
for (neighbor, weight) in graph[startVertex]:
minHeap.push((weight, startVertex, neighbor))
while minHeap is not empty and |visited| < n:
(weight, u, v) = minHeap.pop()
if v in visited:
continue
visited.add(v)
mstEdges.append((u, v, weight))
totalWeight += weight
for (neighbor, w) in graph[v]:
if neighbor not in visited:
minHeap.push((w, v, neighbor))
return mstEdges, totalWeight
Time & Space Complexity
- Time: O(E log E) or equivalently O(E log V) since E <= V^2. Each edge is pushed onto the heap at most twice (once from each endpoint), and each heap operation is O(log E).
- Space: O(V + E). The heap can hold up to O(E) edges. The visited set and MST edges are O(V).
With an indexed priority queue (decrease-key), the complexity can be improved to O(E + V log V) using a Fibonacci heap, though this is rarely done in practice.
Key Insights
- Prim's algorithm is greedy: at each step, it takes the cheapest edge crossing the cut between visited and unvisited vertices. The cut property guarantees correctness.
- The algorithm is similar to Dijkstra's algorithm in structure (both use a priority queue and grow a set of "processed" nodes), but they optimize different things: Prim's minimizes edge weight; Dijkstra's minimizes distance from source.
- Prim's is well-suited for dense graphs (especially with an adjacency matrix and O(V^2) simple implementation), whereas Kruskal's (sort all edges, union-find) tends to be preferred for sparse graphs.
- The MST of a connected graph with
Vvertices always has exactlyV - 1edges. - If the graph is not connected, Prim's from a single start vertex finds the MST of the connected component containing that vertex.
Edge Cases
- Single vertex: The MST is empty (no edges needed).
- Graph with one edge: That edge is the MST.
- All edges have equal weight: Any spanning tree is an MST.
- Disconnected graph: Prim's from one vertex only spans its connected component. Run it from each unvisited vertex to get a minimum spanning forest.
- Negative edge weights: Prim's handles negative weights correctly, unlike Dijkstra's with negative edges.
- Parallel edges (multigraph): The algorithm naturally picks the cheapest among parallel edges.
- Self-loops: These are never part of an MST and are naturally skipped (the endpoint is already visited).