Ad hoc versions of MinHeap, MaxHeap, and DisjointSet (#1117)

* Add DisjointSetMinimalistic

* Add MinHeapMinimalistic and MaxHeapMinimalistic

* Rename minimalistic to adhoc

* Update README
This commit is contained in:
Oleksii Trekhleb
2024-03-09 17:15:19 +01:00
committed by GitHub
parent ac78353e3c
commit 2c67b48c21
8 changed files with 552 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
/**
* 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;

View File

@@ -0,0 +1,117 @@
/**
* 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;

View File

@@ -58,6 +58,11 @@ Where:
> In this repository, the [MaxHeap.js](./MaxHeap.js) and [MinHeap.js](./MinHeap.js) are examples of the **Binary** heap.
## Implementation
- [MaxHeap.js](./MaxHeap.js) and [MinHeap.js](./MinHeap.js)
- [MaxHeapAdhoc.js](./MaxHeapAdhoc.js) and [MinHeapAdhoc.js](./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).
## References
- [Wikipedia](https://en.wikipedia.org/wiki/Heap_(data_structure))

View File

@@ -0,0 +1,91 @@
import MaxHeap from '../MaxHeapAdhoc';
describe('MaxHeapAdhoc', () => {
it('should create an empty max heap', () => {
const maxHeap = new MaxHeap();
expect(maxHeap).toBeDefined();
expect(maxHeap.peek()).toBe(undefined);
expect(maxHeap.isEmpty()).toBe(true);
});
it('should add items to the heap and heapify it up', () => {
const maxHeap = new MaxHeap();
maxHeap.add(5);
expect(maxHeap.isEmpty()).toBe(false);
expect(maxHeap.peek()).toBe(5);
expect(maxHeap.toString()).toBe('5');
maxHeap.add(3);
expect(maxHeap.peek()).toBe(5);
expect(maxHeap.toString()).toBe('5,3');
maxHeap.add(10);
expect(maxHeap.peek()).toBe(10);
expect(maxHeap.toString()).toBe('10,3,5');
maxHeap.add(1);
expect(maxHeap.peek()).toBe(10);
expect(maxHeap.toString()).toBe('10,3,5,1');
maxHeap.add(1);
expect(maxHeap.peek()).toBe(10);
expect(maxHeap.toString()).toBe('10,3,5,1,1');
expect(maxHeap.poll()).toBe(10);
expect(maxHeap.toString()).toBe('5,3,1,1');
expect(maxHeap.poll()).toBe(5);
expect(maxHeap.toString()).toBe('3,1,1');
expect(maxHeap.poll()).toBe(3);
expect(maxHeap.toString()).toBe('1,1');
});
it('should poll items from the heap and heapify it down', () => {
const maxHeap = new MaxHeap();
maxHeap.add(5);
maxHeap.add(3);
maxHeap.add(10);
maxHeap.add(11);
maxHeap.add(1);
expect(maxHeap.toString()).toBe('11,10,5,3,1');
expect(maxHeap.poll()).toBe(11);
expect(maxHeap.toString()).toBe('10,3,5,1');
expect(maxHeap.poll()).toBe(10);
expect(maxHeap.toString()).toBe('5,3,1');
expect(maxHeap.poll()).toBe(5);
expect(maxHeap.toString()).toBe('3,1');
expect(maxHeap.poll()).toBe(3);
expect(maxHeap.toString()).toBe('1');
expect(maxHeap.poll()).toBe(1);
expect(maxHeap.toString()).toBe('');
expect(maxHeap.poll()).toBe(undefined);
expect(maxHeap.toString()).toBe('');
});
it('should heapify down through the right branch as well', () => {
const maxHeap = new MaxHeap();
maxHeap.add(3);
maxHeap.add(12);
maxHeap.add(10);
expect(maxHeap.toString()).toBe('12,3,10');
maxHeap.add(11);
expect(maxHeap.toString()).toBe('12,11,10,3');
expect(maxHeap.poll()).toBe(12);
expect(maxHeap.toString()).toBe('11,3,10');
});
});

View File

@@ -0,0 +1,91 @@
import MinHeapAdhoc from '../MinHeapAdhoc';
describe('MinHeapAdhoc', () => {
it('should create an empty min heap', () => {
const minHeap = new MinHeapAdhoc();
expect(minHeap).toBeDefined();
expect(minHeap.peek()).toBe(undefined);
expect(minHeap.isEmpty()).toBe(true);
});
it('should add items to the heap and heapify it up', () => {
const minHeap = new MinHeapAdhoc();
minHeap.add(5);
expect(minHeap.isEmpty()).toBe(false);
expect(minHeap.peek()).toBe(5);
expect(minHeap.toString()).toBe('5');
minHeap.add(3);
expect(minHeap.peek()).toBe(3);
expect(minHeap.toString()).toBe('3,5');
minHeap.add(10);
expect(minHeap.peek()).toBe(3);
expect(minHeap.toString()).toBe('3,5,10');
minHeap.add(1);
expect(minHeap.peek()).toBe(1);
expect(minHeap.toString()).toBe('1,3,10,5');
minHeap.add(1);
expect(minHeap.peek()).toBe(1);
expect(minHeap.toString()).toBe('1,1,10,5,3');
expect(minHeap.poll()).toBe(1);
expect(minHeap.toString()).toBe('1,3,10,5');
expect(minHeap.poll()).toBe(1);
expect(minHeap.toString()).toBe('3,5,10');
expect(minHeap.poll()).toBe(3);
expect(minHeap.toString()).toBe('5,10');
});
it('should poll items from the heap and heapify it down', () => {
const minHeap = new MinHeapAdhoc();
minHeap.add(5);
minHeap.add(3);
minHeap.add(10);
minHeap.add(11);
minHeap.add(1);
expect(minHeap.toString()).toBe('1,3,10,11,5');
expect(minHeap.poll()).toBe(1);
expect(minHeap.toString()).toBe('3,5,10,11');
expect(minHeap.poll()).toBe(3);
expect(minHeap.toString()).toBe('5,11,10');
expect(minHeap.poll()).toBe(5);
expect(minHeap.toString()).toBe('10,11');
expect(minHeap.poll()).toBe(10);
expect(minHeap.toString()).toBe('11');
expect(minHeap.poll()).toBe(11);
expect(minHeap.toString()).toBe('');
expect(minHeap.poll()).toBe(undefined);
expect(minHeap.toString()).toBe('');
});
it('should heapify down through the right branch as well', () => {
const minHeap = new MinHeapAdhoc();
minHeap.add(3);
minHeap.add(12);
minHeap.add(10);
expect(minHeap.toString()).toBe('3,12,10');
minHeap.add(11);
expect(minHeap.toString()).toBe('3,11,10,12');
expect(minHeap.poll()).toBe(3);
expect(minHeap.toString()).toBe('10,11,12');
});
});