Skip to content

Commit

Permalink
Ad hoc versions of MinHeap, MaxHeap, and DisjointSet (#1117)
Browse files Browse the repository at this point in the history
* Add DisjointSetMinimalistic

* Add MinHeapMinimalistic and MaxHeapMinimalistic

* Rename minimalistic to adhoc

* Update README
  • Loading branch information
trekhleb authored Mar 9, 2024
1 parent ac78353 commit 2c67b48
Show file tree
Hide file tree
Showing 8 changed files with 552 additions and 0 deletions.
78 changes: 78 additions & 0 deletions src/data-structures/disjoint-set/DisjointSetAdhoc.js
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 5 additions & 0 deletions src/data-structures/disjoint-set/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 50 additions & 0 deletions src/data-structures/disjoint-set/__test__/DisjointSetAdhoc.test.js
Original file line number Diff line number Diff line change
@@ -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 changes: 115 additions & 0 deletions src/data-structures/heap/MaxHeapAdhoc.js
Original file line number Diff line number Diff line change
@@ -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 changes: 117 additions & 0 deletions src/data-structures/heap/MinHeapAdhoc.js
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 5 additions & 0 deletions src/data-structures/heap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading

0 comments on commit 2c67b48

Please sign in to comment.