Card 05/ 09
Prim's Algorithm in Python, Java and TypeScript
A heap of the edges leaving the tree, with edges to vertices already inside skipped.
Prim's algorithm in code is a set of vertices in the tree and a way to find the lightest edge leaving it. The Python and Java listings keep a binary heap of edges for that; the TypeScript listing keeps each outside vertex's best edge and scans them, because TypeScript's standard library has no priority queue. All three start at A on the topic's graph and print the edges in the order they were added, then the total.
Prim's algorithm with a heap or a scan of best edges, in each language
import heapq
def prim(graph, start):
in_tree = {start}
tree = []
heap = [(weight, start, neighbour) for neighbour, weight in graph[start]]
heapq.heapify(heap)
while heap and len(in_tree) < len(graph):
weight, u, v = heapq.heappop(heap)
if v in in_tree:
continue
in_tree.add(v)
tree.append((u, v, weight))
for neighbour, next_weight in graph[v]:
if neighbour not in in_tree:
heapq.heappush(heap, (next_weight, v, neighbour))
return tree
def undirected(edges):
graph = {}
for u, v, weight in edges:
graph.setdefault(u, []).append((v, weight))
graph.setdefault(v, []).append((u, weight))
return graph
EDGES = [
("A", "B", 4), ("A", "C", 1), ("B", "C", 2), ("B", "D", 5), ("C", "D", 8),
("C", "E", 9), ("D", "E", 3), ("D", "F", 7), ("E", "F", 6),
]
tree = prim(undirected(EDGES), "A")
print("added:", ", ".join(f"{u}-{v} {weight}" for u, v, weight in tree))
print("total:", sum(weight for _, _, weight in tree))added: A-C 1, C-B 2, B-D 5, D-E 3, E-F 6
total: 17import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.stream.Collectors;
public class PrimCode {
record Edge(String u, String v, int weight) {
@Override
public String toString() {
return u + "-" + v + " " + weight;
}
}
static List<Edge> prim(Map<String, List<Edge>> graph, String start) {
Set<String> inTree = new HashSet<>(Set.of(start));
List<Edge> tree = new ArrayList<>();
PriorityQueue<Edge> heap = new PriorityQueue<>(Comparator.comparingInt(Edge::weight));
heap.addAll(graph.get(start));
while (!heap.isEmpty() && inTree.size() < graph.size()) {
Edge edge = heap.poll();
if (!inTree.add(edge.v())) continue;
tree.add(edge);
for (Edge next : graph.get(edge.v())) {
if (!inTree.contains(next.v())) heap.add(next);
}
}
return tree;
}
static Map<String, List<Edge>> undirected(List<Edge> edges) {
Map<String, List<Edge>> graph = new LinkedHashMap<>();
for (Edge edge : edges) {
graph.computeIfAbsent(edge.u(), key -> new ArrayList<>()).add(edge);
graph.computeIfAbsent(edge.v(), key -> new ArrayList<>()).add(new Edge(edge.v(), edge.u(), edge.weight()));
}
return graph;
}
public static void main(String[] args) {
List<Edge> edges = List.of(
new Edge("A", "B", 4), new Edge("A", "C", 1), new Edge("B", "C", 2), new Edge("B", "D", 5), new Edge("C", "D", 8),
new Edge("C", "E", 9), new Edge("D", "E", 3), new Edge("D", "F", 7), new Edge("E", "F", 6));
List<Edge> tree = prim(undirected(edges), "A");
System.out.println("added: " + tree.stream().map(Edge::toString).collect(Collectors.joining(", ")));
System.out.println("total: " + tree.stream().mapToInt(Edge::weight).sum());
}
}added: A-C 1, C-B 2, B-D 5, D-E 3, E-F 6
total: 17type Edge = [string, string, number];
type Neighbour = [string, number];
function prim(graph: Map<string, Neighbour[]>, start: string): Edge[] {
const inTree = new Set<string>([start]);
const best = new Map<string, [number, string]>();
const tree: Edge[] = [];
let latest = start;
while (inTree.size < graph.size) {
for (const [neighbour, weight] of graph.get(latest)!) {
if (!inTree.has(neighbour) && weight < (best.get(neighbour)?.[0] ?? Infinity)) best.set(neighbour, [weight, latest]);
}
let next: string | null = null;
for (const [vertex, [weight]] of best) {
if (next === null || weight < best.get(next)![0]) next = vertex;
}
if (next === null) break;
const [weight, from] = best.get(next)!;
best.delete(next);
inTree.add(next);
tree.push([from, next, weight]);
latest = next;
}
return tree;
}
function undirected(edges: Edge[]): Map<string, Neighbour[]> {
const graph = new Map<string, Neighbour[]>();
for (const [u, v, weight] of edges) {
if (!graph.has(u)) graph.set(u, []);
if (!graph.has(v)) graph.set(v, []);
graph.get(u)!.push([v, weight]);
graph.get(v)!.push([u, weight]);
}
return graph;
}
const edges: Edge[] = [
["A", "B", 4], ["A", "C", 1], ["B", "C", 2], ["B", "D", 5], ["C", "D", 8],
["C", "E", 9], ["D", "E", 3], ["D", "F", 7], ["E", "F", 6],
];
const tree = prim(undirected(edges), "A");
console.log(`added: ${tree.map(([u, v, weight]) => `${u}-${v} ${weight}`).join(", ")}`);
console.log(`total: ${tree.reduce((sum, [, , weight]) => sum + weight, 0)}`);added: A-C 1, C-B 2, B-D 5, D-E 3, E-F 6
total: 17All three printed the same two lines, the order on Trace Prim's Algorithm Growing One Tree. Each edge is written from the vertex already in the tree to the one it brings in, so C-B 2 is the edge B-C, added from C.
In the Python and Java listings the highlighted lines take the lightest edge from the heap, skip it when its far end is already in the tree, and push only edges that lead out. An edge pushed while its far end was outside can be overtaken: A-B 4 went in at the start, and B joined by B-C 2 first. Python's heapq and Java's PriorityQueue cannot remove it, so it stays until it surfaces and is skipped, which A-B 4 does straight after B joins: the same lazy deletion Dijkstra's Algorithm in Python, Java and TypeScript uses. With up to 2E entries pushed at O(log V) each, the run is O(E log V).
The TypeScript listing's highlighted lines lower a best edge, pick the lightest best edge by looking at every one, and remove the chosen vertex from the map. Each of the V additions scans up to V entries, so the run is O(V²). That loses to the heap on a sparse graph and wins on a dense one, where E is close to V².
Java's undirected stores each edge twice, once reversed, so that edge.v() is always the far end. Every listing stops once the tree holds every vertex; on a disconnected graph the heap empties, or no best edge is left, and the tree covers only the start's part.
← → move through sections and cards