Skip to content

Commit

Permalink
fix: refactor PrimMST and fix bug in PriorityQueue (TheAlgorithms#1300)
Browse files Browse the repository at this point in the history
* ref: KeyPriorityQueue in separate  file TheAlgorithms#1298

* feat: add tests for KeyPriorityQueue TheAlgorithms#1298

* fix: _shiftDown refactored and corrected TheAlgorithms#1298

* fix: use KeyPriorityQueue in PrimMST TheAlgorithms#1298

* feat: add test for PrimMST TheAlgorithms#1298

* fix: format files TheAlgorithms#1298

* fix: minor coding style changes

* fix: use map for keys and priorities TheAlgorithms#1298
  • Loading branch information
paulinegarelli authored Feb 23, 2023
1 parent 0c42758 commit 566d910
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 154 deletions.
153 changes: 153 additions & 0 deletions Data-Structures/Heap/KeyPriorityQueue.js
Original file line number Diff line number Diff line change
@@ -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 }
126 changes: 126 additions & 0 deletions Data-Structures/Heap/test/KeyPriorityQueue.test.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
})
Loading

0 comments on commit 566d910

Please sign in to comment.