Implementing Heap in TypeScript

Priority Queue is an extension of queue with following properties:

  • Every item has a priority associated with it.
  • An element with high priority is dequeued before an element with low priority.
  • If two elements have the same priority, they are served according to their order in the queue.

A typical priority queue supports following operations:

  • insert(item, priority): Inserts an item with given priority.
  • getHighestPriority(): Returns the highest priority item.
  • deleteHighestPriority(): Removes the highest priority item.

Applications of Priority Queue:

  • CPU Scheduling
  • Graph algorithms like Dijkstra’s shortest path algorithm, Prim’s Minimum Spanning Tree, etc
  • All queue applications where priority is involved.
  • Data Compression (Huffman codes)
  • Find the largest M items in a stream of N items.

Ways to implement Priority Queue:

  • Arrays - Insertion and Deletion is expensive in order to maintain the priority.
  • LinkedList -> Same as array, but deletion is fast.
  • Binary Heap -> Best

Binary Heap (Min Heap or Max Heap)

  • Based on the idea = 10 of Complete Binary Tree.
  • Binary Tree: Empty or Nodes to left and right binary tree.
  • Complete Binary Tree: Perfectly Balanced, except for the bottom level and the bottom level has all keys as left as possible.
    • Perfectly Balanced, except for Level 3
    • Height of a Complete Binary Tree of N node is Log N. In the shown tree, there are 9 nodes ==> Log 9 = Log 3^2 = 2 Log 3. (3 Levels)

Binary Tree Levels

Implementation: Array representation of the heap ordered complete binary tree.

Heap Ordered Binary Tree:

  • Keys in nodes.
  • Parent's key is greater than children's keys. This is important.

Properties of Binary Heap:

  • Largest key is arr[1], which is the root of the binary tree.
  • Parent of node at 'k' index is at k/2 index. (It's integer divide. No floats)
  • Children of a node 'k' are at index '2k' and '2k + 1', given we start indexing from 1 instead of 0.

Consider this array:

index 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 arr[index] [_, T, S, R, P, N, O, A, E, I, H, G]

Heap Example

What's the parent of H & G?

Parent of node at 'k' is at k/2. Here k = 10(H), so parent will be at 10/2 = 5. Element at index 5 is N, which is correct!
So, we don't need actual tree to represent these data structures. Array indices are sufficient.

Time complexity for building a Binary Heap is O(N). Read more about the complexity here

Implementation

type Comparator<T> = (a: T, b: T) => number class Heap<T> { items: T[] comparator: Comparator<T> constructor(list: T[], comparator: Comparator<T>) { this.items = list || [] this.comparator = comparator this.buildHeap() } buildHeap(): void { for (let i = Math.floor(this.size() / 2) - 1; i >= 0; i--) { this.moveDown(i) } } moveDown(curIdx: number): void { const lastIndex = this.size() - 1 const items = this.items while (this.leftChild(curIdx) <= lastIndex) { let i = this.leftChild(curIdx) // if i is not the last element -> compare i with i+1 // if i was the last element, i+1 would give us index out of bound! if (i < lastIndex && this.comparator(items[i], items[i + 1]) < 0) { i++ } if (this.comparator(items[i], items[curIdx]) < 0) { break } this.exchange(curIdx, i) curIdx = i } } moveUp(curIdx: number): void { while (curIdx > 0) { let parent = this.parent(curIdx) if (this.comparator(this.items[curIdx], this.items[parent]) < 0) break this.exchange(parent, curIdx) curIdx = parent } } offer(value: T) { this.items.push(value) this.moveUp(this.size() - 1) } poll(): T | null { if (this.size() === 0) return null const item = this.items[0] const last = this.items.pop() if (this.size() && last) { this.items[0] = last this.moveDown(0) } return item } leftChild(parent: number): number { return 2 * parent + 1 } rightChild(parent: number): number { return 2 * parent + 2 } parent(child: number): number { return Math.floor((child - 1) / 2) } peek(): T { return this.items[0] } size(): number { return this.items.length } isEmpty(): boolean { return this.size() === 0 } exchange(i: number, j: number): void { let temp = this.items[i] this.items[i] = this.items[j] this.items[j] = temp } }