diff --git a/Data-Structures/Heap/KeyPriorityQueue.js b/Data-Structures/Heap/KeyPriorityQueue.js new file mode 100644 index 0000000000..e7bbede45e --- /dev/null +++ b/Data-Structures/Heap/KeyPriorityQueue.js @@ -0,0 +1,153 @@ +/** + * KeyPriorityQueue is a priority queue based on a Minimum Binary Heap. + * + * Minimum Binary Heaps are binary trees which are filled level by level + * and then from left to right inside a depth level. + * Their main property is that any parent node has a smaller or equal priority to all of its children, + * hence the root of the tree always has the smallest priority of all nodes. + * + * This implementation of the Minimum Binary Heap allows for nodes to be associated to both a key, + * which can be any datatype, and a priority. + * + * The heap is represented by an array with nodes ordered + * from root-to-leaf, left-to-right. + * Therefore, the parent-child node relationship is such that + * * the children nodes positions relative to their parent are: (parentPos * 2 + 1) and (parentPos * 2 + 2) + * * the parent node position relative to either of its children is: Math.floor((childPos - 1) / 2) + * + * More information and visuals on Binary Heaps can be found here: https://www.geeksforgeeks.org/binary-heap/ + */ + +// Priority Queue Helper functions +const getParentPosition = position => Math.floor((position - 1) / 2) +const getChildrenPositions = position => [2 * position + 1, 2 * position + 2] + +class KeyPriorityQueue { + // Priority Queue class using Minimum Binary Heap + constructor () { + this._heap = [] + this.priorities = new Map() + } + + /** + * Checks if the heap is empty + * @returns boolean + */ + isEmpty () { + return this._heap.length === 0 + } + + /** + * Adds an element to the queue + * @param {*} key + * @param {number} priority + */ + push (key, priority) { + this._heap.push(key) + this.priorities.set(key, priority) + this._shiftUp(this._heap.length - 1) + } + + /** + * Removes the element with least priority + * @returns the key of the element with least priority + */ + pop () { + this._swap(0, this._heap.length - 1) + const key = this._heap.pop() + this.priorities.delete(key) + this._shiftDown(0) + return key + } + + /** + * Checks whether a given key is present in the queue + * @param {*} key + * @returns boolean + */ + contains (key) { + return this.priorities.has(key) + } + + /** + * Updates the priority of the given element. + * Adds the element if it is not in the queue. + * @param {*} key the element to change + * @param {number} priority new priority of the element + */ + update (key, priority) { + const currPos = this._heap.indexOf(key) + // if the key does not exist yet, add it + if (currPos === -1) return this.push(key, priority) + // else update priority + this.priorities.set(key, priority) + const parentPos = getParentPosition(currPos) + const currPriority = this._getPriorityOrInfinite(currPos) + const parentPriority = this._getPriorityOrInfinite(parentPos) + const [child1Pos, child2Pos] = getChildrenPositions(currPos) + const child1Priority = this._getPriorityOrInfinite(child1Pos) + const child2Priority = this._getPriorityOrInfinite(child2Pos) + + if (parentPos >= 0 && parentPriority > currPriority) { + this._shiftUp(currPos) + } else if (child1Priority < currPriority || child2Priority < currPriority) { + this._shiftDown(currPos) + } + } + + _getPriorityOrInfinite (position) { + // Helper function, returns priority of the node, or Infinite if no node corresponds to this position + if (position >= 0 && position < this._heap.length) return this.priorities.get(this._heap[position]) + else return Infinity + } + + _shiftUp (position) { + // Helper function to shift up a node to proper position (equivalent to bubbleUp) + let currPos = position + let parentPos = getParentPosition(currPos) + let currPriority = this._getPriorityOrInfinite(currPos) + let parentPriority = this._getPriorityOrInfinite(parentPos) + + while (parentPos >= 0 && parentPriority > currPriority) { + this._swap(currPos, parentPos) + currPos = parentPos + parentPos = getParentPosition(currPos) + currPriority = this._getPriorityOrInfinite(currPos) + parentPriority = this._getPriorityOrInfinite(parentPos) + } + } + + _shiftDown (position) { + // Helper function to shift down a node to proper position (equivalent to bubbleDown) + let currPos = position + let [child1Pos, child2Pos] = getChildrenPositions(currPos) + let child1Priority = this._getPriorityOrInfinite(child1Pos) + let child2Priority = this._getPriorityOrInfinite(child2Pos) + let currPriority = this._getPriorityOrInfinite(currPos) + + if (currPriority === Infinity) { + return + } + + while (child1Priority < currPriority || child2Priority < currPriority) { + if (child1Priority < currPriority && child1Priority < child2Priority) { + this._swap(child1Pos, currPos) + currPos = child1Pos + } else { + this._swap(child2Pos, currPos) + currPos = child2Pos + } + [child1Pos, child2Pos] = getChildrenPositions(currPos) + child1Priority = this._getPriorityOrInfinite(child1Pos) + child2Priority = this._getPriorityOrInfinite(child2Pos) + currPriority = this._getPriorityOrInfinite(currPos) + } + } + + _swap (position1, position2) { + // Helper function to swap 2 nodes + [this._heap[position1], this._heap[position2]] = [this._heap[position2], this._heap[position1]] + } +} + +export { KeyPriorityQueue } diff --git a/Data-Structures/Heap/test/KeyPriorityQueue.test.js b/Data-Structures/Heap/test/KeyPriorityQueue.test.js new file mode 100644 index 0000000000..b9e9ea6202 --- /dev/null +++ b/Data-Structures/Heap/test/KeyPriorityQueue.test.js @@ -0,0 +1,126 @@ +import { KeyPriorityQueue } from '../KeyPriorityQueue.js' + +describe('Key Priority Queue', () => { + describe('Method isEmpty', () => { + test('Check heap is empty', () => { + const queue = new KeyPriorityQueue() + const res = queue.isEmpty() + expect(res).toEqual(true) + }) + + test('Check heap is not empty', () => { + const queue = new KeyPriorityQueue() + queue.push(0, 2) + const res = queue.isEmpty() + expect(res).toEqual(false) + }) + }) + + describe('Methods push and pop', () => { + test('Test Case 1', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 7) + queue.push(2, 9) + queue.push(3, 13) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(1, 7) + expectedQueue.push(3, 13) + expectedQueue.push(2, 9) + // result from popping element from the queue + queue.pop() + expect(queue).toEqual(expectedQueue) + }) + + test('Test Case 2', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 9) + queue.push(2, 7) + queue.push(3, 13) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(2, 7) + expectedQueue.push(1, 9) + expectedQueue.push(3, 13) + // result from popping element from the queue + queue.pop() + expect(queue).toEqual(expectedQueue) + }) + + test('Test Case 3', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 7) + queue.push(2, 9) + queue.push(3, 12) + queue.push(4, 13) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(1, 7) + expectedQueue.push(3, 12) + expectedQueue.push(2, 9) + expectedQueue.push(4, 13) + // result from popping element from the queue + queue.pop() + expect(queue).toEqual(expectedQueue) + }) + }) + + describe('Method contains', () => { + test('Check heap does not contain element', () => { + const queue = new KeyPriorityQueue() + const res = queue.contains(0) + expect(res).toEqual(false) + }) + + test('Check heap contains element', () => { + const queue = new KeyPriorityQueue() + queue.push(0, 2) + const res = queue.contains(0) + expect(res).toEqual(true) + }) + }) + + describe('Method update', () => { + test('Update without change in position', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 5) + queue.push(2, 7) + queue.push(3, 11) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(0, 2) + expectedQueue.push(1, 5) + expectedQueue.push(2, 7) + expectedQueue.push(3, 11) + // result from updating to similar priority + queue.update(0, 2) + expect(queue).toEqual(expectedQueue) + }) + + test('Update with change in position', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 5) + queue.push(2, 7) + queue.push(3, 11) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(1, 5) + expectedQueue.push(3, 11) + expectedQueue.push(2, 7) + expectedQueue.push(0, 9) + // result from updating to similar priority + queue.update(0, 9) + expect(queue).toEqual(expectedQueue) + }) + }) +}) diff --git a/Graphs/PrimMST.js b/Graphs/PrimMST.js index 17d68efb90..c658845874 100644 --- a/Graphs/PrimMST.js +++ b/Graphs/PrimMST.js @@ -1,148 +1,4 @@ -// Priority Queue Helper functions -function getParentPosition (position) { - // Get the parent node of the current node - return Math.floor((position - 1) / 2) -} -function getChildrenPosition (position) { - // Get the children nodes of the current node - return [2 * position + 1, 2 * position + 2] -} - -class PriorityQueue { - // Priority Queue class using Minimum Binary Heap - constructor () { - this._heap = [] - this.keys = {} - } - - isEmpty () { - // Checking if the heap is empty - return this._heap.length === 0 - } - - push (key, priority) { - // Adding element to the queue (equivalent to add) - this._heap.push([key, priority]) - this.keys[key] = this._heap.length - 1 - this._shiftUp(this.keys[key]) - } - - pop () { - // Removing the element with least priority (equivalent to extractMin) - this._swap(0, this._heap.length - 1) - const [key] = this._heap.pop() - delete this.keys[key] - this._shiftDown(0) - return key - } - - contains (key) { - // Check if a given key is present in the queue - return (key in this.keys) - } - - update (key, priority) { - // Update the priority of the given element (equivalent to decreaseKey) - const currPos = this.keys[key] - this._heap[currPos][1] = priority - const parentPos = getParentPosition(currPos) - const currPriority = this._heap[currPos][1] - let parentPriority = Infinity - if (parentPos >= 0) { - parentPriority = this._heap[parentPos][1] - } - const [child1Pos, child2Pos] = getChildrenPosition(currPos) - let [child1Priority, child2Priority] = [Infinity, Infinity] - if (child1Pos < this._heap.length) { - child1Priority = this._heap[child1Pos][1] - } - if (child2Pos < this._heap.length) { - child2Priority = this._heap[child2Pos][1] - } - - if (parentPos >= 0 && parentPriority > currPriority) { - this._shiftUp(currPos) - } else if (child2Pos < this._heap.length && - (child1Priority < currPriority || child2Priority < currPriority)) { - this._shiftDown(currPos) - } - } - - _shiftUp (position) { - // Helper function to shift up a node to proper position (equivalent to bubbleUp) - let currPos = position - let parentPos = getParentPosition(currPos) - let currPriority = this._heap[currPos][1] - let parentPriority = Infinity - if (parentPos >= 0) { - parentPriority = this._heap[parentPos][1] - } - - while (parentPos >= 0 && parentPriority > currPriority) { - this._swap(currPos, parentPos) - currPos = parentPos - parentPos = getParentPosition(currPos) - currPriority = this._heap[currPos][1] - try { - parentPriority = this._heap[parentPos][1] - } catch (error) { - parentPriority = Infinity - } - } - this.keys[this._heap[currPos][0]] = currPos - } - - _shiftDown (position) { - // Helper function to shift down a node to proper position (equivalent to bubbleDown) - let currPos = position - let [child1Pos, child2Pos] = getChildrenPosition(currPos) - let [child1Priority, child2Priority] = [Infinity, Infinity] - if (child1Pos < this._heap.length) { - child1Priority = this._heap[child1Pos][1] - } - if (child2Pos < this._heap.length) { - child2Priority = this._heap[child2Pos][1] - } - let currPriority - try { - currPriority = this._heap[currPos][1] - } catch { - return - } - - while (child2Pos < this._heap.length && - (child1Priority < currPriority || child2Priority < currPriority)) { - if (child1Priority < currPriority && child1Priority < child2Priority) { - this._swap(child1Pos, currPos) - currPos = child1Pos - } else { - this._swap(child2Pos, currPos) - currPos = child2Pos - } - [child1Pos, child2Pos] = getChildrenPosition(currPos) - try { - [child1Priority, child2Priority] = [this._heap[child1Pos][1], this._heap[child2Pos][1]] - } catch (error) { - [child1Priority, child2Priority] = [Infinity, Infinity] - } - - currPriority = this._heap[currPos][1] - } - this.keys[this._heap[currPos][0]] = currPos - if (child1Pos < this._heap.length && child1Priority < currPriority) { - this._swap(child1Pos, currPos) - this.keys[this._heap[child1Pos][0]] = child1Pos - } - } - - _swap (position1, position2) { - // Helper function to swap 2 nodes - [this._heap[position1], this._heap[position2]] = [this._heap[position2], this._heap[position1]] - this.keys[this._heap[position1][0]] = position1 - this.keys[this._heap[position2][0]] = position2 - } -} - +import { KeyPriorityQueue } from '../Data-Structures/Heap/KeyPriorityQueue' class GraphWeightedUndirectedAdjacencyList { // Weighted Undirected Graph class constructor () { @@ -167,7 +23,7 @@ class GraphWeightedUndirectedAdjacencyList { // Details: https://en.wikipedia.org/wiki/Prim%27s_algorithm const distance = {} const parent = {} - const priorityQueue = new PriorityQueue() + const priorityQueue = new KeyPriorityQueue() // Initialization for (const node in this.connections) { distance[node] = (node === start.toString() ? 0 : Infinity) @@ -198,11 +54,3 @@ class GraphWeightedUndirectedAdjacencyList { } export { GraphWeightedUndirectedAdjacencyList } - -// const graph = new GraphWeightedUndirectedAdjacencyList() -// graph.addEdge(1, 2, 1) -// graph.addEdge(2, 3, 2) -// graph.addEdge(3, 4, 1) -// graph.addEdge(3, 5, 100) // Removed in MST -// graph.addEdge(4, 5, 5) -// graph.PrimMST(1) diff --git a/Graphs/test/PrimMST.test.js b/Graphs/test/PrimMST.test.js new file mode 100644 index 0000000000..82f3c033f1 --- /dev/null +++ b/Graphs/test/PrimMST.test.js @@ -0,0 +1,20 @@ +import { GraphWeightedUndirectedAdjacencyList } from '../PrimMST.js' + +test('Test Case PrimMST 1', () => { + // create graph to compute MST on + const graph = new GraphWeightedUndirectedAdjacencyList() + graph.addEdge(1, 2, 1) + graph.addEdge(2, 3, 2) + graph.addEdge(3, 4, 1) + graph.addEdge(3, 5, 100) // Removed in MST + graph.addEdge(4, 5, 5) + // create expected graph + const expectedGraph = new GraphWeightedUndirectedAdjacencyList() + expectedGraph.addEdge(1, 2, 1) + expectedGraph.addEdge(2, 3, 2) + expectedGraph.addEdge(3, 4, 1) + expectedGraph.addEdge(4, 5, 5) + // result from MST + const res = graph.PrimMST(1) + expect(res).toEqual(expectedGraph) +})