mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-25 22:46:20 +08:00
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:
parent
ac78353e3c
commit
2c67b48c21
78
src/data-structures/disjoint-set/DisjointSetAdhoc.js
Normal file
78
src/data-structures/disjoint-set/DisjointSetAdhoc.js
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* The minimalistic (ad hoc) version of a DisjointSet (or a UnionFind) 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).
|
||||
*
|
||||
* Time Complexity:
|
||||
*
|
||||
* - Constructor: O(N)
|
||||
* - Find: O(α(N))
|
||||
* - Union: O(α(N))
|
||||
* - Connected: O(α(N))
|
||||
*
|
||||
* Where N is the number of vertices in the graph.
|
||||
* α refers to the Inverse Ackermann function.
|
||||
* In practice, we assume it's a constant.
|
||||
* In other words, O(α(N)) is regarded as O(1) on average.
|
||||
*/
|
||||
class DisjointSetAdhoc {
|
||||
/**
|
||||
* Initializes the set of specified size.
|
||||
* @param {number} size
|
||||
*/
|
||||
constructor(size) {
|
||||
// The index of a cell is an id of the node in a set.
|
||||
// The value of a cell is an id (index) of the root node.
|
||||
// By default, the node is a parent of itself.
|
||||
this.roots = new Array(size).fill(0).map((_, i) => i);
|
||||
|
||||
// Using the heights array to record the height of each node.
|
||||
// By default each node has a height of 1 because it has no children.
|
||||
this.heights = new Array(size).fill(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the root of node `a`
|
||||
* @param {number} a
|
||||
* @returns {number}
|
||||
*/
|
||||
find(a) {
|
||||
if (a === this.roots[a]) return a;
|
||||
this.roots[a] = this.find(this.roots[a]);
|
||||
return this.roots[a];
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the `a` and `b` nodes into same set.
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
* @returns {number}
|
||||
*/
|
||||
union(a, b) {
|
||||
const aRoot = this.find(a);
|
||||
const bRoot = this.find(b);
|
||||
|
||||
if (aRoot === bRoot) return;
|
||||
|
||||
if (this.heights[aRoot] > this.heights[bRoot]) {
|
||||
this.roots[bRoot] = aRoot;
|
||||
} else if (this.heights[aRoot] < this.heights[bRoot]) {
|
||||
this.roots[aRoot] = bRoot;
|
||||
} else {
|
||||
this.roots[bRoot] = aRoot;
|
||||
this.heights[aRoot] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `a` and `b` belong to the same set.
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
*/
|
||||
connected(a, b) {
|
||||
return this.find(a) === this.find(b);
|
||||
}
|
||||
}
|
||||
|
||||
export default DisjointSetAdhoc;
|
@ -19,6 +19,11 @@ _MakeSet_ creates 8 singletons.
|
||||
|
||||
After some operations of _Union_, some sets are grouped together.
|
||||
|
||||
## Implementation
|
||||
|
||||
- [DisjointSet.js](./DisjointSet.js)
|
||||
- [DisjointSetAdhoc.js](./DisjointSetAdhoc.js) - The minimalistic (ad hoc) version of a DisjointSet (or a UnionFind) 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/Disjoint-set_data_structure)
|
||||
|
@ -0,0 +1,50 @@
|
||||
import DisjointSetAdhoc from '../DisjointSetAdhoc';
|
||||
|
||||
describe('DisjointSetAdhoc', () => {
|
||||
it('should create unions and find connected elements', () => {
|
||||
const set = new DisjointSetAdhoc(10);
|
||||
|
||||
// 1-2-5-6-7 3-8-9 4
|
||||
set.union(1, 2);
|
||||
set.union(2, 5);
|
||||
set.union(5, 6);
|
||||
set.union(6, 7);
|
||||
|
||||
set.union(3, 8);
|
||||
set.union(8, 9);
|
||||
|
||||
expect(set.connected(1, 5)).toBe(true);
|
||||
expect(set.connected(5, 7)).toBe(true);
|
||||
expect(set.connected(3, 8)).toBe(true);
|
||||
|
||||
expect(set.connected(4, 9)).toBe(false);
|
||||
expect(set.connected(4, 7)).toBe(false);
|
||||
|
||||
// 1-2-5-6-7 3-8-9-4
|
||||
set.union(9, 4);
|
||||
|
||||
expect(set.connected(4, 9)).toBe(true);
|
||||
expect(set.connected(4, 3)).toBe(true);
|
||||
expect(set.connected(8, 4)).toBe(true);
|
||||
|
||||
expect(set.connected(8, 7)).toBe(false);
|
||||
expect(set.connected(2, 3)).toBe(false);
|
||||
});
|
||||
|
||||
it('should keep the height of the tree small', () => {
|
||||
const set = new DisjointSetAdhoc(10);
|
||||
|
||||
// 1-2-6-7-9 1 3 4 5
|
||||
set.union(7, 6);
|
||||
set.union(1, 2);
|
||||
set.union(2, 6);
|
||||
set.union(1, 7);
|
||||
set.union(9, 1);
|
||||
|
||||
expect(set.connected(1, 7)).toBe(true);
|
||||
expect(set.connected(6, 9)).toBe(true);
|
||||
expect(set.connected(4, 9)).toBe(false);
|
||||
|
||||
expect(Math.max(...set.heights)).toBe(3);
|
||||
});
|
||||
});
|
115
src/data-structures/heap/MaxHeapAdhoc.js
Normal file
115
src/data-structures/heap/MaxHeapAdhoc.js
Normal 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;
|
117
src/data-structures/heap/MinHeapAdhoc.js
Normal file
117
src/data-structures/heap/MinHeapAdhoc.js
Normal 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;
|
@ -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))
|
||||
|
91
src/data-structures/heap/__test__/MaxHeapAdhoc.test.js
Normal file
91
src/data-structures/heap/__test__/MaxHeapAdhoc.test.js
Normal 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');
|
||||
});
|
||||
});
|
91
src/data-structures/heap/__test__/MinHeapAdhoc.test.js
Normal file
91
src/data-structures/heap/__test__/MinHeapAdhoc.test.js
Normal 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');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user