diff --git a/data_structures/heap/heap.ts b/data_structures/heap/heap.ts index 28915849..5f8e8974 100644 --- a/data_structures/heap/heap.ts +++ b/data_structures/heap/heap.ts @@ -14,7 +14,7 @@ export abstract class Heap { protected heap: T[]; // A comparison function. Returns true if a should be the parent of b. - private compare: (a: T, b: T) => boolean; + protected compare: (a: T, b: T) => boolean; constructor(compare: (a: T, b: T) => boolean) { this.heap = []; @@ -189,6 +189,10 @@ export class PriorityQueue extends MinHeap { return; } let key = this.keys[idx]; + if (this.compare(this.heap[key], value)) { + // Do not do anything if the value in the heap already has a higher priority. + return; + } // Increase the priority and bubble it up the heap. this.heap[key] = value; this.bubbleUp(key); diff --git a/data_structures/heap/test/min_heap.test.ts b/data_structures/heap/test/min_heap.test.ts index e7fa82ad..33ac03cd 100644 --- a/data_structures/heap/test/min_heap.test.ts +++ b/data_structures/heap/test/min_heap.test.ts @@ -65,6 +65,11 @@ describe("MinHeap", () => { heap.increasePriority(81, 72); heap.increasePriority(9, 0); heap.increasePriority(43, 33); + // decreasing priority should do nothing + heap.increasePriority(72, 100); + heap.increasePriority(12, 24); + heap.increasePriority(39, 40); + heap.check(); // Elements after increasing priority const newElements: number[] = [ diff --git a/graph/prim.ts b/graph/prim.ts new file mode 100644 index 00000000..5c36479a --- /dev/null +++ b/graph/prim.ts @@ -0,0 +1,59 @@ +import { PriorityQueue } from '../data_structures/heap/heap' +/** + * @function prim + * @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. + * @Complexity_Analysis + * Time complexity: O(Elog(V)) + * Space Complexity: O(V) + * @param {[number, number][][]} graph - The graph in adjacency list form + * @return {Edge[], number} - [The edges of the minimum spanning tree, the sum of the weights of the edges in the tree] + * @see https://en.wikipedia.org/wiki/Prim%27s_algorithm + */ +export const prim = (graph: [number, number][][]): [Edge[], number] => { + if (graph.length == 0) { + return [[], 0]; + } + let minimum_spanning_tree: Edge[] = []; + let total_weight = 0; + + let priorityQueue = new PriorityQueue((e: Edge) => { return e.b }, graph.length, (a: Edge, b: Edge) => { return a.weight < b.weight }); + let visited = new Set(); + + // Start from the 0'th node. For fully connected graphs, we can start from any node and still produce the MST. + visited.add(0); + add_children(graph, priorityQueue, 0); + + while (!priorityQueue.isEmpty()) { + // We have already visited vertex `edge.a`. If we have not visited `edge.b` yet, we add its outgoing edges to the PriorityQueue. + let edge = priorityQueue.extract(); + if (visited.has(edge.b)) { + continue; + } + minimum_spanning_tree.push(edge); + total_weight += edge.weight; + visited.add(edge.b); + add_children(graph, priorityQueue, edge.b); + } + + return [minimum_spanning_tree, total_weight]; +} + +const add_children = (graph: [number, number][][], priorityQueue: PriorityQueue, node: number) => { + for (let i = 0; i < graph[node].length; ++i) { + let out_edge = graph[node][i]; + // 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. + priorityQueue.increasePriority(out_edge[0], new Edge(node, out_edge[0], out_edge[1])); + } +} + +export class Edge { + a: number = 0; + b: number = 0; + weight: number = 0; + constructor(a: number, b: number, weight: number) { + this.a = a; + this.b = b; + this.weight = weight; + } +} + diff --git a/graph/test/prim.test.ts b/graph/test/prim.test.ts new file mode 100644 index 00000000..763f1716 --- /dev/null +++ b/graph/test/prim.test.ts @@ -0,0 +1,85 @@ +import { Edge, prim } from "../prim"; + +let edge_equal = (x: Edge, y: Edge): boolean => { + return (x.a == y.a && x.b == y.b) || (x.a == y.b && x.b == y.a) && x.weight == y.weight; +} + +let test_graph = (expected_tree_edges: Edge[], other_edges: Edge[], num_vertices: number, expected_cost: number) => { + // First make sure the graph is undirected + let graph: [number, number][][] = []; + for (let _ = 0; _ < num_vertices; ++_) { + graph.push([]); + } + for (let edge of expected_tree_edges) { + graph[edge.a].push([edge.b, edge.weight]); + graph[edge.b].push([edge.a, edge.weight]); + } + for (let edge of other_edges) { + graph[edge.a].push([edge.b, edge.weight]); + graph[edge.b].push([edge.a, edge.weight]); + } + + let [tree_edges, cost] = prim(graph); + expect(cost).toStrictEqual(expected_cost); + for (let expected_edge of expected_tree_edges) { + expect(tree_edges.find(edge => edge_equal(edge, expected_edge))).toBeTruthy(); + } + for (let unexpected_edge of other_edges) { + expect(tree_edges.find(edge => edge_equal(edge, unexpected_edge))).toBeFalsy(); + } +}; + + +describe("prim", () => { + + it("should return empty tree for empty graph", () => { + expect(prim([])).toStrictEqual([[], 0]); + }); + + it("should return empty tree for single element graph", () => { + expect(prim([])).toStrictEqual([[], 0]); + }); + + it("should return correct value for two element graph", () => { + expect(prim([[[1, 5]], []])).toStrictEqual([[new Edge(0, 1, 5)], 5]); + }); + + it("should return the correct value", () => { + let expected_tree_edges = [ + new Edge(0, 1, 1), + new Edge(1, 3, 2), + new Edge(3, 2, 3), + ]; + + let other_edges = [ + new Edge(0, 2, 4), + new Edge(0, 3, 5), + new Edge(1, 2, 6), + ]; + + test_graph(expected_tree_edges, other_edges, 4, 6); + }); + + it("should return the correct value", () => { + let expected_tree_edges = [ + new Edge(0, 2, 2), + new Edge(1, 3, 9), + new Edge(2, 6, 74), + new Edge(2, 7, 8), + new Edge(3, 4, 3), + new Edge(4, 9, 9), + new Edge(5, 7, 5), + new Edge(7, 9, 4), + new Edge(8, 9, 2), + ] + + let other_edges = [ + new Edge(0, 1, 10), + new Edge(2, 4, 47), + new Edge(4, 5, 42), + ]; + + test_graph(expected_tree_edges, other_edges, 10, 116); + }); + +})