Hello! Let's dive into the second major algorithm for finding a Minimum Spanning Tree.
In our last lesson, you implemented Kruskal's algorithm, which works by sorting all edges in the graph and adding them to the MST if they don't form a cycle. It's a "forest-based" approach that connects different components until only one remains.
Today, we'll explore Prim's algorithm, which addresses the same problem but with a different greedy strategy. Instead of looking at all edges globally, Prim's algorithm grows a single tree from a starting vertex, one edge at a time. This lesson focuses on implementing the efficient version of this algorithm, a must-know for technical interviews and competitive programming.
Our goal is to find the Minimum Spanning Tree (MST) of a graph using Prim's algorithm with a priority queue.
1. The Core Idea of Prim's Algorithm
Prim's algorithm is also a greedy algorithm, but its greedy choice is different from Kruskal's. It works by partitioning the graph's vertices into two sets:
- Vertices already included in the MST.
- Vertices not yet included.
The algorithm starts with an arbitrary vertex in the first set. Then, at each step, it makes the following greedy choice: find the minimum-weight edge that connects a vertex in the MST set to a vertex outside of it, and add that edge (and the new vertex) to the MST.
This process is repeated exactly V-1 times, where V is the number of vertices, until all vertices are connected.

To build your intuition for this process, let's look at a step-by-step example.
Java Program - Minimum Spanning Tree - Prim's Algorithm
The article 'Minimum Spanning Tree - Prim's Algorithm' from NetJS Tech provides a clear, visual walkthrough of how the MST is built one edge at a time.
Read the sections titled 'Minimum spanning tree', 'Prim's algorithm', and the step-by-step example that follows the first graph image. Focus on how at each step, the algorithm considers all outgoing edges from the current tree and picks the cheapest one.
2. The Role of the Priority Queue
A naive implementation of Prim's would involve searching through all possible edges at each of the V-1 steps to find the minimum-weight one that crosses the boundary of our growing tree. This would be slow, with a time complexity of around .
We can do much better. The problem of "repeatedly finding the minimum item from a collection" is a perfect use case for a min-priority queue.
The efficient version of Prim's algorithm is remarkably similar in structure to Dijkstra's algorithm for shortest paths. It uses a priority queue to keep track of the "fringe"āthe set of all edges that connect vertices inside our MST to vertices outside it.
Here's the logic:
- Initialize a min-priority queue. It will store pairs of
{weight, vertex}. - Create a
visitedarray to track vertices already in the MST. - Choose a starting vertex (e.g., vertex 0), and add
{0, start_vertex}to the priority queue. The initial weight is 0 because there's no cost to start there. - Loop while the priority queue is not empty and the MST has fewer than
V-1edges:
a. Extract the pair with the smallest weight from the priority queue. Let this be{weight, vertex u}.
b. Ifuhas already been visited, continue to the next iteration.
c. Markuas visited and add the edge to the MST (the total weight of the MST increases byweight).
d. For each neighborvofu: ifvhas not been visited, add the edge connectinguandv(i.e.,{edge_weight, v}) to the priority queue.
This video from Prof. Josh Hug provides a fantastic conceptual explanation of this efficient, priority-queue-based approach. It helps build a strong mental model before we dive into the Java code.
Watch from 01:08 to 03:37. Pay close attention to these key ideas: The priority queue stores vertices on the 'fringe' ordered by their distance (edge weight) from the tree. 'Relaxing' an edge means updating the 'best known' way to connect a vertex to the tree. Removing a vertex from the priority queue is the moment we commit an edge to the MST.
This approach is efficient because the priority queue handles finding the minimum-weight edge in logarithmic time, rather than us needing to scan all edges manually.
3. Java Implementation
Now let's translate this logic into Java code. We'll use an adjacency list to represent the graph and Java's PriorityQueue to implement the min-heap.
For the priority queue to work correctly with our custom data, we can either store an object that implements Comparable or provide a Comparator during the PriorityQueue's initialization. Storing a simple int[] of {vertex, weight} and using a lambda for the comparator is a common and clean approach in competitive programming.
Prim's Algorithm | Java Code Implementation
The 'take U forward' channel has a great video that walks through the optimized Java code for Prim's algorithm.
Watch from 04:40 to 08:22. This segment covers: The motivation for using a PriorityQueue to optimize the algorithm. The setup of the PriorityQueue with a custom Node class and comparator. The main while loop logic, showing how to poll from the queue, check the visited status, and add new edges.
Below is a complete, runnable Java program that implements Prim's algorithm. It finds the total weight of the MST and prints the edges that form it.
import java.util.*;
public class PrimsAlgorithm {
// A class to represent a pair of (vertex, weight) for the adjacency list
static class Pair {
int vertex;
int weight;
Pair(int v, int w) {
this.vertex = v;
this.weight = w;
}
}
// A class to represent an edge for the priority queue and MST result
// Stores {source, destination, weight}
static class Edge {
int source;
int destination;
int weight;
Edge(int u, int v, int w) {
this.source = u;
this.destination = v;
this.weight = w;
}
}
public static void primMST(int V, ArrayList<ArrayList<Pair>> adj) {
// Priority Queue to store edges, ordered by weight
// We store an array {weight, vertex, parent}
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
boolean[] visited = new boolean[V];
List<Edge> mstEdges = new ArrayList<>();
int mstWeight = 0;
// Start with vertex 0
// {weight, vertex, parent}
pq.add(new int[]{0, 0, -1}); // -1 indicates no parent
while (!pq.isEmpty()) {
int[] current = pq.poll();
int weight = current[0];
int u = current[1];
int parent = current[2];
// If we have already included this vertex in the MST, skip it.
if (visited[u]) {
continue;
}
// Mark vertex as visited and add weight to the total
visited[u] = true;
mstWeight += weight;
if (parent != -1) { // Add edge to our result list, skipping the initial pseudo-edge
mstEdges.add(new Edge(parent, u, weight));
}
// Explore neighbors
for (Pair neighborPair : adj.get(u)) {
int v = neighborPair.vertex;
int edgeWeight = neighborPair.weight;
if (!visited[v]) {
pq.add(new int[]{edgeWeight, v, u});
}
}
}
System.out.println("Minimum Spanning Tree Weight: " + mstWeight);
System.out.println("Edges in the MST:");
for (Edge edge : mstEdges) {
System.out.println(edge.source + " - " + edge.destination + " \tWeight: " + edge.weight);
}
}
public static void main(String[] args) {
int V = 5;
ArrayList<ArrayList<Pair>> adj = new ArrayList<>();
for (int i = 0; i < V; i++) {
adj.add(new ArrayList<>());
}
// Example from netjstech.com resource
adj.get(0).add(new Pair(1, 2));
adj.get(1).add(new Pair(0, 2));
adj.get(0).add(new Pair(2, 2));
adj.get(2).add(new Pair(0, 2));
adj.get(1).add(new Pair(2, 4));
adj.get(2).add(new Pair(1, 4));
adj.get(1).add(new Pair(4, 5));
adj.get(4).add(new Pair(1, 5));
adj.get(2).add(new Pair(3, 7));
adj.get(3).add(new Pair(2, 7));
adj.get(2).add(new Pair(4, 1));
adj.get(4).add(new Pair(2, 1));
adj.get(3).add(new Pair(4, 6));
adj.get(4).add(new Pair(3, 6));
primMST(V, adj);
}
}
Test your understanding!
Why is the if (visited[u]) { continue; } check necessary inside the while loop? What problem does it solve?
Show answer
This check is crucial because Java's PriorityQueue does not support an efficient decreaseKey operation. When we find a shorter path to a vertex that's already in the queue, we simply add a new entry for that vertex with the improved, lower weight. We don't remove the old, more expensive entry.
This means the priority queue can contain multiple entries for the same vertex. The visited check ensures that we only process each vertex onceāthe first time it is extracted from the queue, which is guaranteed to be via its minimum-weight connecting edge. All subsequent encounters of that same vertex are ignored.
4. Complexity Analysis
A solid understanding of complexity is essential for interviews.
-
Time Complexity:
- The main
whileloop runs until all vertices are visited. In each iteration where we add a vertex to the MST, we iterate through its neighbors. Over the entire execution, each edge(u, v)will be processed once fromu's side and once fromv's side. - This means we perform at most
addoperations andpolloperations on the priority queue. - Both
addandpolltake time, where is the number of elements in the queue. In the worst case, the queue can hold edges. - Therefore, the time complexity is . Since can be at most , is asymptotically equivalent to , so the complexity is often stated as .
- The main
-
Space Complexity:
- Adjacency list: .
visitedarray: .PriorityQueue: In the worst case, can store all edges, so .- The total space complexity is dominated by the adjacency list and priority queue, making it .
For a final confirmation of this analysis, you can review the complexity section of the netjstech.com resource. Look for the final analysis that uses an adjacency list and priority queue.
Conclusion
You have now learned and implemented Prim's algorithm, the second fundamental approach to finding a Minimum Spanning Tree.
Key Takeaways:
- Prim's algorithm builds an MST by starting from a single vertex and greedily growing the tree, always adding the cheapest edge that connects a vertex in the tree to one outside of it.
- An efficient implementation relies on a min-priority queue to quickly find this minimum-weight edge.
- The time complexity using an adjacency list and a binary heap (like Java's
PriorityQueue) is . - The space complexity is .
Prim's vs. Kruskal's:
- Prim's is generally faster for dense graphs (where is close to ).
- Kruskal's is often faster for sparse graphs (where is close to ) because its complexity depends more on sorting edges () than on vertex operations.
Preview of the Next Lesson:
The structure of Prim's algorithmāusing a priority queue to explore a graph from a starting pointāis a powerful pattern. In our next lesson, we will see it again when we implement Dijkstra's algorithm. While Prim's finds an MST by minimizing edge weights to connect all vertices, Dijkstra's finds the shortest path from a single source to all other vertices by minimizing the total path distance from the source. The code will look surprisingly similar, but the greedy choice will be different.