Skip to content

Commit 03dc8b8

Browse files
authored
feat: add Prim's algorithm for Minimum Spanning Tree (TheAlgorithms#142)
* feat: add Prim's algorithm for Minimum Spanning Tree fix: do not decrease priority in PriorityQueue increasePriority() * clarify comment
1 parent 8deeca2 commit 03dc8b8

File tree

4 files changed

+154
-1
lines changed

4 files changed

+154
-1
lines changed

data_structures/heap/heap.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
export abstract class Heap<T> {
1515
protected heap: T[];
1616
// A comparison function. Returns true if a should be the parent of b.
17-
private compare: (a: T, b: T) => boolean;
17+
protected compare: (a: T, b: T) => boolean;
1818

1919
constructor(compare: (a: T, b: T) => boolean) {
2020
this.heap = [];
@@ -189,6 +189,10 @@ export class PriorityQueue<T> extends MinHeap<T> {
189189
return;
190190
}
191191
let key = this.keys[idx];
192+
if (this.compare(this.heap[key], value)) {
193+
// Do not do anything if the value in the heap already has a higher priority.
194+
return;
195+
}
192196
// Increase the priority and bubble it up the heap.
193197
this.heap[key] = value;
194198
this.bubbleUp(key);

data_structures/heap/test/min_heap.test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ describe("MinHeap", () => {
6565
heap.increasePriority(81, 72);
6666
heap.increasePriority(9, 0);
6767
heap.increasePriority(43, 33);
68+
// decreasing priority should do nothing
69+
heap.increasePriority(72, 100);
70+
heap.increasePriority(12, 24);
71+
heap.increasePriority(39, 40);
72+
6873
heap.check();
6974
// Elements after increasing priority
7075
const newElements: number[] = [

graph/prim.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { PriorityQueue } from '../data_structures/heap/heap'
2+
/**
3+
* @function prim
4+
* @description Compute a minimum spanning tree(MST) of a fully connected weighted undirected graph. The input graph is in adjacency list form. It is a multidimensional array of edges. graph[i] holds the edges for the i'th node. Each edge is a 2-tuple where the 0'th item is the destination node, and the 1'th item is the edge weight.
5+
* @Complexity_Analysis
6+
* Time complexity: O(Elog(V))
7+
* Space Complexity: O(V)
8+
* @param {[number, number][][]} graph - The graph in adjacency list form
9+
* @return {Edge[], number} - [The edges of the minimum spanning tree, the sum of the weights of the edges in the tree]
10+
* @see https://en.wikipedia.org/wiki/Prim%27s_algorithm
11+
*/
12+
export const prim = (graph: [number, number][][]): [Edge[], number] => {
13+
if (graph.length == 0) {
14+
return [[], 0];
15+
}
16+
let minimum_spanning_tree: Edge[] = [];
17+
let total_weight = 0;
18+
19+
let priorityQueue = new PriorityQueue((e: Edge) => { return e.b }, graph.length, (a: Edge, b: Edge) => { return a.weight < b.weight });
20+
let visited = new Set<number>();
21+
22+
// Start from the 0'th node. For fully connected graphs, we can start from any node and still produce the MST.
23+
visited.add(0);
24+
add_children(graph, priorityQueue, 0);
25+
26+
while (!priorityQueue.isEmpty()) {
27+
// We have already visited vertex `edge.a`. If we have not visited `edge.b` yet, we add its outgoing edges to the PriorityQueue.
28+
let edge = priorityQueue.extract();
29+
if (visited.has(edge.b)) {
30+
continue;
31+
}
32+
minimum_spanning_tree.push(edge);
33+
total_weight += edge.weight;
34+
visited.add(edge.b);
35+
add_children(graph, priorityQueue, edge.b);
36+
}
37+
38+
return [minimum_spanning_tree, total_weight];
39+
}
40+
41+
const add_children = (graph: [number, number][][], priorityQueue: PriorityQueue<Edge>, node: number) => {
42+
for (let i = 0; i < graph[node].length; ++i) {
43+
let out_edge = graph[node][i];
44+
// By increasing the priority, we ensure we only add each vertex to the queue one time, and the queue will be at most size V.
45+
priorityQueue.increasePriority(out_edge[0], new Edge(node, out_edge[0], out_edge[1]));
46+
}
47+
}
48+
49+
export class Edge {
50+
a: number = 0;
51+
b: number = 0;
52+
weight: number = 0;
53+
constructor(a: number, b: number, weight: number) {
54+
this.a = a;
55+
this.b = b;
56+
this.weight = weight;
57+
}
58+
}
59+

graph/test/prim.test.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Edge, prim } from "../prim";
2+
3+
let edge_equal = (x: Edge, y: Edge): boolean => {
4+
return (x.a == y.a && x.b == y.b) || (x.a == y.b && x.b == y.a) && x.weight == y.weight;
5+
}
6+
7+
let test_graph = (expected_tree_edges: Edge[], other_edges: Edge[], num_vertices: number, expected_cost: number) => {
8+
// First make sure the graph is undirected
9+
let graph: [number, number][][] = [];
10+
for (let _ = 0; _ < num_vertices; ++_) {
11+
graph.push([]);
12+
}
13+
for (let edge of expected_tree_edges) {
14+
graph[edge.a].push([edge.b, edge.weight]);
15+
graph[edge.b].push([edge.a, edge.weight]);
16+
}
17+
for (let edge of other_edges) {
18+
graph[edge.a].push([edge.b, edge.weight]);
19+
graph[edge.b].push([edge.a, edge.weight]);
20+
}
21+
22+
let [tree_edges, cost] = prim(graph);
23+
expect(cost).toStrictEqual(expected_cost);
24+
for (let expected_edge of expected_tree_edges) {
25+
expect(tree_edges.find(edge => edge_equal(edge, expected_edge))).toBeTruthy();
26+
}
27+
for (let unexpected_edge of other_edges) {
28+
expect(tree_edges.find(edge => edge_equal(edge, unexpected_edge))).toBeFalsy();
29+
}
30+
};
31+
32+
33+
describe("prim", () => {
34+
35+
it("should return empty tree for empty graph", () => {
36+
expect(prim([])).toStrictEqual([[], 0]);
37+
});
38+
39+
it("should return empty tree for single element graph", () => {
40+
expect(prim([])).toStrictEqual([[], 0]);
41+
});
42+
43+
it("should return correct value for two element graph", () => {
44+
expect(prim([[[1, 5]], []])).toStrictEqual([[new Edge(0, 1, 5)], 5]);
45+
});
46+
47+
it("should return the correct value", () => {
48+
let expected_tree_edges = [
49+
new Edge(0, 1, 1),
50+
new Edge(1, 3, 2),
51+
new Edge(3, 2, 3),
52+
];
53+
54+
let other_edges = [
55+
new Edge(0, 2, 4),
56+
new Edge(0, 3, 5),
57+
new Edge(1, 2, 6),
58+
];
59+
60+
test_graph(expected_tree_edges, other_edges, 4, 6);
61+
});
62+
63+
it("should return the correct value", () => {
64+
let expected_tree_edges = [
65+
new Edge(0, 2, 2),
66+
new Edge(1, 3, 9),
67+
new Edge(2, 6, 74),
68+
new Edge(2, 7, 8),
69+
new Edge(3, 4, 3),
70+
new Edge(4, 9, 9),
71+
new Edge(5, 7, 5),
72+
new Edge(7, 9, 4),
73+
new Edge(8, 9, 2),
74+
]
75+
76+
let other_edges = [
77+
new Edge(0, 1, 10),
78+
new Edge(2, 4, 47),
79+
new Edge(4, 5, 42),
80+
];
81+
82+
test_graph(expected_tree_edges, other_edges, 10, 116);
83+
});
84+
85+
})

0 commit comments

Comments
 (0)