Heap (data-structure)
Data Structure · Core
Step through it: Top K Elements pattern
Step 1 of 6
Goal: find the 3 biggest numbers. Sorting everything works but does more than needed. Instead keep a small club of the best 3 so far and always know its weakest member, the first to be kicked out. (A min-heap gives you the weakest in one step.)
Step 2 of 6
Take in 5, 1 and 9. The club is full: 1, 5, 9. The weakest member is 1.
Step 3 of 6
Next is 3. It beats the weakest (1), so 1 leaves and 3 joins: 3, 5, 9.
Step 4 of 6
7 beats the new weakest (3). Club: 5, 7, 9.
Step 5 of 6
2 is smaller than the weakest (5), so ignore it. The club is the answer: 5, 7, 9. Each number costs one quick heap update, about n log k steps in all. This is the Top K pattern.
Step 6 of 6
Your turn.
Complexity
Here are time complexities of various heap data structures. Function names assume a max-heap.
| Operation | find-max | delete-max | insert | increase-key | meld |
|---|---|---|---|---|---|
| Binary | Θ(1) | Θ(log n) | O(log n) | O(log n) | Θ(n) |
| Leftist | Θ(1) | Θ(log n) | Θ(log n) | O(log n) | Θ(log n) |
| Binomial | Θ(1) | Θ(log n) | Θ(1) | O(log n) | O(log n) |
| Fibonacci | Θ(1) | Θ(log n) | Θ(1) | Θ(1) | Θ(1) |
| Pairing | Θ(1) | Θ(log n) | Θ(1) | o(log n) | Θ(1) |
| Brodal | Θ(1) | Θ(log n) | Θ(1) | Θ(1) | Θ(1) |
| Rank-pairing | Θ(1) | Θ(log n) | Θ(1) | Θ(1) | Θ(1) |
| Strict Fibonacci | Θ(1) | Θ(log n) | Θ(1) | Θ(1) | Θ(1) |
| 2-3 heap | O(log n) | O(log n) | O(log n) | Θ(1) | ? |
Where:
- find-max (or find-min): find a maximum item of a max-heap, or a minimum item of a min-heap, respectively (a.k.a. peek)
- delete-max (or delete-min): removing the root node of a max heap (or min heap), respectively
- insert: adding a new key to the heap (a.k.a., push)
- increase-key or decrease-key: updating a key within a max- or min-heap, respectively
- meld: joining two heaps to form a valid new heap containing all the elements of both, destroying the original heaps.
In this repository, the MaxHeap.js and MinHeap.js are examples of the Binary heap.
In computer science, a heap is a specialized tree-based data structure that satisfies the heap property described below.
In a min heap, if P is a parent node of C, then the
key (the value) of P is less than or equal to the
key of C.

Made with okso.app
In a max heap, the key of P is greater than or equal
to the key of C


The node at the "top" of the heap with no parents is called the root node.
Implementation
- MaxHeap.js and MinHeap.js
- MaxHeapAdhoc.js and MinHeapAdhoc.js - The minimalistic (ad hoc) version of a MinHeap/MaxHeap data structure that doesn't have external dependencies and that is easy to copy-paste and use during the coding interview if allowed by the interviewer (since many data structures in JS are missing).
Code
import Comparator from '../../utils/comparator/Comparator';
/**
* Parent class for Min and Max Heaps.
*/
export default class Heap {
/**
* @constructs Heap
* @param {Function} [comparatorFunction]
*/
constructor(comparatorFunction) {
if (new.target === Heap) {
throw new TypeError('Cannot construct Heap instance directly');
}
// Array representation of the heap.
this.heapContainer = [];
this.compare = new Comparator(comparatorFunction);
}
/**
* @param {number} parentIndex
* @return {number}
*/
getLeftChildIndex(parentIndex) {
return (2 * parentIndex) + 1;
}
/**
* @param {number} parentIndex
* @return {number}
*/
getRightChildIndex(parentIndex) {
return (2 * parentIndex) + 2;
}
/**
* @param {number} childIndex
* @return {number}
*/
getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
}
/**
* @param {number} childIndex
* @return {boolean}
*/
hasParent(childIndex) {
return this.getParentIndex(childIndex) >= 0;
}
/**
* @param {number} parentIndex
* @return {boolean}
*/
hasLeftChild(parentIndex) {
return this.getLeftChildIndex(parentIndex) < this.heapContainer.length;
}
/**
* @param {number} parentIndex
* @return {boolean}
*/
hasRightChild(parentIndex) {
return this.getRightChildIndex(parentIndex) < this.heapContainer.length;
}
/**
* @param {number} parentIndex
* @return {*}
*/
leftChild(parentIndex) {
return this.heapContainer[this.getLeftChildIndex(parentIndex)];
}
/**
* @param {number} parentIndex
* @return {*}
*/
rightChild(parentIndex) {
return this.heapContainer[this.getRightChildIndex(parentIndex)];
}
/**
* @param {number} childIndex
* @return {*}
*/
parent(childIndex) {
return this.heapContainer[this.getParentIndex(childIndex)];
}
/**
* @param {number} indexOne
* @param {number} indexTwo
*/
swap(indexOne, indexTwo) {
const tmp = this.heapContainer[indexTwo];
this.heapContainer[indexTwo] = this.heapContainer[indexOne];
this.heapContainer[indexOne] = tmp;
}
/**
* @return {*}
*/
peek() {
if (this.heapContainer.length === 0) {
return null;
}
return this.heapContainer[0];
}
/**
* @return {*}
*/
poll() {
if (this.heapContainer.length === 0) {
return null;
}
if (this.heapContainer.length === 1) {
return this.heapContainer.pop();
}
const item = this.heapContainer[0];
// Move the last element from the end to the head.
this.heapContainer[0] = this.heapContainer.pop();
this.heapifyDown();
return item;
}
/**
* @param {*} item
* @return {Heap}
*/
add(item) {
this.heapContainer.push(item);
this.heapifyUp();
return this;
}
/**
* @param {*} item
* @param {Comparator} [comparator]
* @return {Heap}
*/
remove(item, comparator = this.compare) {
// Find number of items to remove.
const numberOfItemsToRemove = this.find(item, comparator).length;
for (let iteration = 0; iteration < numberOfItemsToRemove; iteration += 1) {
// We need to find item index to remove each time after removal since
// indices are being changed after each heapify process.
const indexToRemove = this.find(item, comparator).pop();
// If we need to remove last child in the heap then just remove it.
// There is no need to heapify the heap afterwards.
if (indexToRemove === (this.heapContainer.length - 1)) {
this.heapContainer.pop();
} else {
// Move last element in heap to the vacant (removed) position.
this.heapContainer[indexToRemove] = this.heapContainer.pop();
// Get parent.
const parentItem = this.parent(indexToRemove);
// If there is no parent or parent is in correct order with the node
// we're going to delete then heapify down. Otherwise heapify up.
if (
this.hasLeftChild(indexToRemove)
&& (
!parentItem
|| this.pairIsInCorrectOrder(parentItem, this.heapContainer[indexToRemove])
)
) {
this.heapifyDown(indexToRemove);
} else {
this.heapifyUp(indexToRemove);
}
}
}
return this;
}
/**
* @param {*} item
* @param {Comparator} [comparator]
* @return {Number[]}
*/
find(item, comparator = this.compare) {
const foundItemIndices = [];
for (let itemIndex = 0; itemIndex < this.heapContainer.length; itemIndex += 1) {
if (comparator.equal(item, this.heapContainer[itemIndex])) {
foundItemIndices.push(itemIndex);
}
}
return foundItemIndices;
}
/**
* @return {boolean}
*/
isEmpty() {
return !this.heapContainer.length;
}
/**
* @return {string}
*/
toString() {
return this.heapContainer.toString();
}
/**
* @param {number} [customStartIndex]
*/
heapifyUp(customStartIndex) {
// Take the last element (last in array or the bottom left in a tree)
// in the heap container and lift it up until it is in the correct
// order with respect to its parent element.
let currentIndex = customStartIndex || this.heapContainer.length - 1;
while (
this.hasParent(currentIndex)
&& !this.pairIsInCorrectOrder(this.parent(currentIndex), this.heapContainer[currentIndex])
) {
this.swap(currentIndex, this.getParentIndex(currentIndex));
currentIndex = this.getParentIndex(currentIndex);
}
}
/**
* @param {number} [customStartIndex]
*/
heapifyDown(customStartIndex = 0) {
// Compare the parent element to its children and swap parent with the appropriate
// child (smallest child for MinHeap, largest child for MaxHeap).
// Do the same for next children after swap.
let currentIndex = customStartIndex;
let nextIndex = null;
while (this.hasLeftChild(currentIndex)) {
if (
this.hasRightChild(currentIndex)
&& this.pairIsInCorrectOrder(this.rightChild(currentIndex), this.leftChild(currentIndex))
) {
nextIndex = this.getRightChildIndex(currentIndex);
} else {
nextIndex = this.getLeftChildIndex(currentIndex);
}
if (this.pairIsInCorrectOrder(
this.heapContainer[currentIndex],
this.heapContainer[nextIndex],
)) {
break;
}
this.swap(currentIndex, nextIndex);
currentIndex = nextIndex;
}
}
/**
* Checks if pair of heap elements is in correct order.
* For MinHeap the first element must be always smaller or equal.
* For MaxHeap the first element must be always bigger or equal.
*
* @param {*} firstElement
* @param {*} secondElement
* @return {boolean}
*/
/* istanbul ignore next */
pairIsInCorrectOrder(firstElement, secondElement) {
throw new Error(`
You have to implement heap pair comparison method
for ${firstElement} and ${secondElement} values.
`);
}
}import Heap from './Heap';
export default class MaxHeap extends Heap {
/**
* Checks if pair of heap elements is in correct order.
* For MinHeap the first element must be always smaller or equal.
* For MaxHeap the first element must be always bigger or equal.
*
* @param {*} firstElement
* @param {*} secondElement
* @return {boolean}
*/
pairIsInCorrectOrder(firstElement, secondElement) {
return this.compare.greaterThanOrEqual(firstElement, secondElement);
}
}/**
* The minimalistic (ad hoc) version of a MaxHeap data structure that doesn't have
* external dependencies and that is easy to copy-paste and use during the
* coding interview if allowed by the interviewer (since many data
* structures in JS are missing).
*/
class MaxHeapAdhoc {
constructor(heap = []) {
this.heap = [];
heap.forEach(this.add);
}
add(num) {
this.heap.push(num);
this.heapifyUp();
}
peek() {
return this.heap[0];
}
poll() {
if (this.heap.length === 0) return undefined;
const top = this.heap[0];
this.heap[0] = this.heap[this.heap.length - 1];
this.heap.pop();
this.heapifyDown();
return top;
}
isEmpty() {
return this.heap.length === 0;
}
toString() {
return this.heap.join(',');
}
heapifyUp() {
let nodeIndex = this.heap.length - 1;
while (nodeIndex > 0) {
const parentIndex = this.getParentIndex(nodeIndex);
if (this.heap[parentIndex] >= this.heap[nodeIndex]) break;
this.swap(parentIndex, nodeIndex);
nodeIndex = parentIndex;
}
}
heapifyDown() {
let nodeIndex = 0;
while (
(
this.hasLeftChild(nodeIndex) && this.heap[nodeIndex] < this.leftChild(nodeIndex)
)
|| (
this.hasRightChild(nodeIndex) && this.heap[nodeIndex] < this.rightChild(nodeIndex)
)
) {
const leftIndex = this.getLeftChildIndex(nodeIndex);
const rightIndex = this.getRightChildIndex(nodeIndex);
const left = this.leftChild(nodeIndex);
const right = this.rightChild(nodeIndex);
if (this.hasLeftChild(nodeIndex) && this.hasRightChild(nodeIndex)) {
if (left >= right) {
this.swap(leftIndex, nodeIndex);
nodeIndex = leftIndex;
} else {
this.swap(rightIndex, nodeIndex);
nodeIndex = rightIndex;
}
} else if (this.hasLeftChild(nodeIndex)) {
this.swap(leftIndex, nodeIndex);
nodeIndex = leftIndex;
}
}
}
getLeftChildIndex(parentIndex) {
return (2 * parentIndex) + 1;
}
getRightChildIndex(parentIndex) {
return (2 * parentIndex) + 2;
}
getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
}
hasLeftChild(parentIndex) {
return this.getLeftChildIndex(parentIndex) < this.heap.length;
}
hasRightChild(parentIndex) {
return this.getRightChildIndex(parentIndex) < this.heap.length;
}
leftChild(parentIndex) {
return this.heap[this.getLeftChildIndex(parentIndex)];
}
rightChild(parentIndex) {
return this.heap[this.getRightChildIndex(parentIndex)];
}
swap(indexOne, indexTwo) {
const tmp = this.heap[indexTwo];
this.heap[indexTwo] = this.heap[indexOne];
this.heap[indexOne] = tmp;
}
}
export default MaxHeapAdhoc;import Heap from './Heap';
export default class MinHeap extends Heap {
/**
* Checks if pair of heap elements is in correct order.
* For MinHeap the first element must be always smaller or equal.
* For MaxHeap the first element must be always bigger or equal.
*
* @param {*} firstElement
* @param {*} secondElement
* @return {boolean}
*/
pairIsInCorrectOrder(firstElement, secondElement) {
return this.compare.lessThanOrEqual(firstElement, secondElement);
}
}/**
* The minimalistic (ad hoc) version of a MinHeap data structure that doesn't have
* external dependencies and that is easy to copy-paste and use during the
* coding interview if allowed by the interviewer (since many data
* structures in JS are missing).
*/
class MinHeapAdhoc {
constructor(heap = []) {
this.heap = [];
heap.forEach(this.add);
}
add(num) {
this.heap.push(num);
this.heapifyUp();
}
peek() {
return this.heap[0];
}
poll() {
if (this.heap.length === 0) return undefined;
const top = this.heap[0];
this.heap[0] = this.heap[this.heap.length - 1];
this.heap.pop();
this.heapifyDown();
return top;
}
isEmpty() {
return this.heap.length === 0;
}
toString() {
return this.heap.join(',');
}
heapifyUp() {
let nodeIndex = this.heap.length - 1;
while (nodeIndex > 0) {
const parentIndex = this.getParentIndex(nodeIndex);
if (this.heap[parentIndex] <= this.heap[nodeIndex]) break;
this.swap(parentIndex, nodeIndex);
nodeIndex = parentIndex;
}
}
heapifyDown() {
let nodeIndex = 0;
while (
(
this.hasLeftChild(nodeIndex)
&& this.heap[nodeIndex] > this.leftChild(nodeIndex)
)
|| (
this.hasRightChild(nodeIndex)
&& this.heap[nodeIndex] > this.rightChild(nodeIndex)
)
) {
const leftIndex = this.getLeftChildIndex(nodeIndex);
const rightIndex = this.getRightChildIndex(nodeIndex);
const left = this.leftChild(nodeIndex);
const right = this.rightChild(nodeIndex);
if (this.hasLeftChild(nodeIndex) && this.hasRightChild(nodeIndex)) {
if (left <= right) {
this.swap(leftIndex, nodeIndex);
nodeIndex = leftIndex;
} else {
this.swap(rightIndex, nodeIndex);
nodeIndex = rightIndex;
}
} else if (this.hasLeftChild(nodeIndex)) {
this.swap(leftIndex, nodeIndex);
nodeIndex = leftIndex;
}
}
}
getLeftChildIndex(parentIndex) {
return 2 * parentIndex + 1;
}
getRightChildIndex(parentIndex) {
return 2 * parentIndex + 2;
}
getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
}
hasLeftChild(parentIndex) {
return this.getLeftChildIndex(parentIndex) < this.heap.length;
}
hasRightChild(parentIndex) {
return this.getRightChildIndex(parentIndex) < this.heap.length;
}
leftChild(parentIndex) {
return this.heap[this.getLeftChildIndex(parentIndex)];
}
rightChild(parentIndex) {
return this.heap[this.getRightChildIndex(parentIndex)];
}
swap(indexOne, indexTwo) {
const tmp = this.heap[indexTwo];
this.heap[indexTwo] = this.heap[indexOne];
this.heap[indexOne] = tmp;
}
}
export default MinHeapAdhoc;References
Practice
Top K Elements
- Kth Largest Element in a StreamEasy
- Last Stone WeightEasy
- Kth Largest Element in an ArrayMedium
- Top K Frequent ElementsMedium
- Top K Frequent WordsMedium
- K Closest Points to OriginMedium
- Sort Characters By FrequencyMedium
- Task SchedulerMedium
- Reorganize StringMedium
- Find K Pairs with Smallest SumsMedium
- Kth Smallest Element in a Sorted MatrixMedium
- Meeting Rooms IIMedium
- Furthest Building You Can ReachMedium
- Merge k Sorted ListsHard
- Find Median from Data StreamHard
- Smallest Range Covering Elements from K ListsHard
Spotted a mistake or something unclear? Report it on GitHub.