Skip to content

Commit 2c67b48

Browse files
authored
Ad hoc versions of MinHeap, MaxHeap, and DisjointSet (#1117)
* Add DisjointSetMinimalistic * Add MinHeapMinimalistic and MaxHeapMinimalistic * Rename minimalistic to adhoc * Update README
1 parent ac78353 commit 2c67b48

File tree

8 files changed

+552
-0
lines changed

8 files changed

+552
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* The minimalistic (ad hoc) version of a DisjointSet (or a UnionFind) data structure
3+
* that doesn't have external dependencies and that is easy to copy-paste and
4+
* use during the coding interview if allowed by the interviewer (since many
5+
* data structures in JS are missing).
6+
*
7+
* Time Complexity:
8+
*
9+
* - Constructor: O(N)
10+
* - Find: O(α(N))
11+
* - Union: O(α(N))
12+
* - Connected: O(α(N))
13+
*
14+
* Where N is the number of vertices in the graph.
15+
* α refers to the Inverse Ackermann function.
16+
* In practice, we assume it's a constant.
17+
* In other words, O(α(N)) is regarded as O(1) on average.
18+
*/
19+
class DisjointSetAdhoc {
20+
/**
21+
* Initializes the set of specified size.
22+
* @param {number} size
23+
*/
24+
constructor(size) {
25+
// The index of a cell is an id of the node in a set.
26+
// The value of a cell is an id (index) of the root node.
27+
// By default, the node is a parent of itself.
28+
this.roots = new Array(size).fill(0).map((_, i) => i);
29+
30+
// Using the heights array to record the height of each node.
31+
// By default each node has a height of 1 because it has no children.
32+
this.heights = new Array(size).fill(1);
33+
}
34+
35+
/**
36+
* Finds the root of node `a`
37+
* @param {number} a
38+
* @returns {number}
39+
*/
40+
find(a) {
41+
if (a === this.roots[a]) return a;
42+
this.roots[a] = this.find(this.roots[a]);
43+
return this.roots[a];
44+
}
45+
46+
/**
47+
* Joins the `a` and `b` nodes into same set.
48+
* @param {number} a
49+
* @param {number} b
50+
* @returns {number}
51+
*/
52+
union(a, b) {
53+
const aRoot = this.find(a);
54+
const bRoot = this.find(b);
55+
56+
if (aRoot === bRoot) return;
57+
58+
if (this.heights[aRoot] > this.heights[bRoot]) {
59+
this.roots[bRoot] = aRoot;
60+
} else if (this.heights[aRoot] < this.heights[bRoot]) {
61+
this.roots[aRoot] = bRoot;
62+
} else {
63+
this.roots[bRoot] = aRoot;
64+
this.heights[aRoot] += 1;
65+
}
66+
}
67+
68+
/**
69+
* Checks if `a` and `b` belong to the same set.
70+
* @param {number} a
71+
* @param {number} b
72+
*/
73+
connected(a, b) {
74+
return this.find(a) === this.find(b);
75+
}
76+
}
77+
78+
export default DisjointSetAdhoc;

src/data-structures/disjoint-set/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ _MakeSet_ creates 8 singletons.
1919

2020
After some operations of _Union_, some sets are grouped together.
2121

22+
## Implementation
23+
24+
- [DisjointSet.js](./DisjointSet.js)
25+
- [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).
26+
2227
## References
2328

2429
- [Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import DisjointSetAdhoc from '../DisjointSetAdhoc';
2+
3+
describe('DisjointSetAdhoc', () => {
4+
it('should create unions and find connected elements', () => {
5+
const set = new DisjointSetAdhoc(10);
6+
7+
// 1-2-5-6-7 3-8-9 4
8+
set.union(1, 2);
9+
set.union(2, 5);
10+
set.union(5, 6);
11+
set.union(6, 7);
12+
13+
set.union(3, 8);
14+
set.union(8, 9);
15+
16+
expect(set.connected(1, 5)).toBe(true);
17+
expect(set.connected(5, 7)).toBe(true);
18+
expect(set.connected(3, 8)).toBe(true);
19+
20+
expect(set.connected(4, 9)).toBe(false);
21+
expect(set.connected(4, 7)).toBe(false);
22+
23+
// 1-2-5-6-7 3-8-9-4
24+
set.union(9, 4);
25+
26+
expect(set.connected(4, 9)).toBe(true);
27+
expect(set.connected(4, 3)).toBe(true);
28+
expect(set.connected(8, 4)).toBe(true);
29+
30+
expect(set.connected(8, 7)).toBe(false);
31+
expect(set.connected(2, 3)).toBe(false);
32+
});
33+
34+
it('should keep the height of the tree small', () => {
35+
const set = new DisjointSetAdhoc(10);
36+
37+
// 1-2-6-7-9 1 3 4 5
38+
set.union(7, 6);
39+
set.union(1, 2);
40+
set.union(2, 6);
41+
set.union(1, 7);
42+
set.union(9, 1);
43+
44+
expect(set.connected(1, 7)).toBe(true);
45+
expect(set.connected(6, 9)).toBe(true);
46+
expect(set.connected(4, 9)).toBe(false);
47+
48+
expect(Math.max(...set.heights)).toBe(3);
49+
});
50+
});
+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* The minimalistic (ad hoc) version of a MaxHeap data structure that doesn't have
3+
* external dependencies and that is easy to copy-paste and use during the
4+
* coding interview if allowed by the interviewer (since many data
5+
* structures in JS are missing).
6+
*/
7+
class MaxHeapAdhoc {
8+
constructor(heap = []) {
9+
this.heap = [];
10+
heap.forEach(this.add);
11+
}
12+
13+
add(num) {
14+
this.heap.push(num);
15+
this.heapifyUp();
16+
}
17+
18+
peek() {
19+
return this.heap[0];
20+
}
21+
22+
poll() {
23+
if (this.heap.length === 0) return undefined;
24+
const top = this.heap[0];
25+
this.heap[0] = this.heap[this.heap.length - 1];
26+
this.heap.pop();
27+
this.heapifyDown();
28+
return top;
29+
}
30+
31+
isEmpty() {
32+
return this.heap.length === 0;
33+
}
34+
35+
toString() {
36+
return this.heap.join(',');
37+
}
38+
39+
heapifyUp() {
40+
let nodeIndex = this.heap.length - 1;
41+
while (nodeIndex > 0) {
42+
const parentIndex = this.getParentIndex(nodeIndex);
43+
if (this.heap[parentIndex] >= this.heap[nodeIndex]) break;
44+
this.swap(parentIndex, nodeIndex);
45+
nodeIndex = parentIndex;
46+
}
47+
}
48+
49+
heapifyDown() {
50+
let nodeIndex = 0;
51+
52+
while (
53+
(
54+
this.hasLeftChild(nodeIndex) && this.heap[nodeIndex] < this.leftChild(nodeIndex)
55+
)
56+
|| (
57+
this.hasRightChild(nodeIndex) && this.heap[nodeIndex] < this.rightChild(nodeIndex)
58+
)
59+
) {
60+
const leftIndex = this.getLeftChildIndex(nodeIndex);
61+
const rightIndex = this.getRightChildIndex(nodeIndex);
62+
const left = this.leftChild(nodeIndex);
63+
const right = this.rightChild(nodeIndex);
64+
65+
if (this.hasLeftChild(nodeIndex) && this.hasRightChild(nodeIndex)) {
66+
if (left >= right) {
67+
this.swap(leftIndex, nodeIndex);
68+
nodeIndex = leftIndex;
69+
} else {
70+
this.swap(rightIndex, nodeIndex);
71+
nodeIndex = rightIndex;
72+
}
73+
} else if (this.hasLeftChild(nodeIndex)) {
74+
this.swap(leftIndex, nodeIndex);
75+
nodeIndex = leftIndex;
76+
}
77+
}
78+
}
79+
80+
getLeftChildIndex(parentIndex) {
81+
return (2 * parentIndex) + 1;
82+
}
83+
84+
getRightChildIndex(parentIndex) {
85+
return (2 * parentIndex) + 2;
86+
}
87+
88+
getParentIndex(childIndex) {
89+
return Math.floor((childIndex - 1) / 2);
90+
}
91+
92+
hasLeftChild(parentIndex) {
93+
return this.getLeftChildIndex(parentIndex) < this.heap.length;
94+
}
95+
96+
hasRightChild(parentIndex) {
97+
return this.getRightChildIndex(parentIndex) < this.heap.length;
98+
}
99+
100+
leftChild(parentIndex) {
101+
return this.heap[this.getLeftChildIndex(parentIndex)];
102+
}
103+
104+
rightChild(parentIndex) {
105+
return this.heap[this.getRightChildIndex(parentIndex)];
106+
}
107+
108+
swap(indexOne, indexTwo) {
109+
const tmp = this.heap[indexTwo];
110+
this.heap[indexTwo] = this.heap[indexOne];
111+
this.heap[indexOne] = tmp;
112+
}
113+
}
114+
115+
export default MaxHeapAdhoc;
+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* The minimalistic (ad hoc) version of a MinHeap data structure that doesn't have
3+
* external dependencies and that is easy to copy-paste and use during the
4+
* coding interview if allowed by the interviewer (since many data
5+
* structures in JS are missing).
6+
*/
7+
class MinHeapAdhoc {
8+
constructor(heap = []) {
9+
this.heap = [];
10+
heap.forEach(this.add);
11+
}
12+
13+
add(num) {
14+
this.heap.push(num);
15+
this.heapifyUp();
16+
}
17+
18+
peek() {
19+
return this.heap[0];
20+
}
21+
22+
poll() {
23+
if (this.heap.length === 0) return undefined;
24+
const top = this.heap[0];
25+
this.heap[0] = this.heap[this.heap.length - 1];
26+
this.heap.pop();
27+
this.heapifyDown();
28+
return top;
29+
}
30+
31+
isEmpty() {
32+
return this.heap.length === 0;
33+
}
34+
35+
toString() {
36+
return this.heap.join(',');
37+
}
38+
39+
heapifyUp() {
40+
let nodeIndex = this.heap.length - 1;
41+
while (nodeIndex > 0) {
42+
const parentIndex = this.getParentIndex(nodeIndex);
43+
if (this.heap[parentIndex] <= this.heap[nodeIndex]) break;
44+
this.swap(parentIndex, nodeIndex);
45+
nodeIndex = parentIndex;
46+
}
47+
}
48+
49+
heapifyDown() {
50+
let nodeIndex = 0;
51+
52+
while (
53+
(
54+
this.hasLeftChild(nodeIndex)
55+
&& this.heap[nodeIndex] > this.leftChild(nodeIndex)
56+
)
57+
|| (
58+
this.hasRightChild(nodeIndex)
59+
&& this.heap[nodeIndex] > this.rightChild(nodeIndex)
60+
)
61+
) {
62+
const leftIndex = this.getLeftChildIndex(nodeIndex);
63+
const rightIndex = this.getRightChildIndex(nodeIndex);
64+
const left = this.leftChild(nodeIndex);
65+
const right = this.rightChild(nodeIndex);
66+
67+
if (this.hasLeftChild(nodeIndex) && this.hasRightChild(nodeIndex)) {
68+
if (left <= right) {
69+
this.swap(leftIndex, nodeIndex);
70+
nodeIndex = leftIndex;
71+
} else {
72+
this.swap(rightIndex, nodeIndex);
73+
nodeIndex = rightIndex;
74+
}
75+
} else if (this.hasLeftChild(nodeIndex)) {
76+
this.swap(leftIndex, nodeIndex);
77+
nodeIndex = leftIndex;
78+
}
79+
}
80+
}
81+
82+
getLeftChildIndex(parentIndex) {
83+
return 2 * parentIndex + 1;
84+
}
85+
86+
getRightChildIndex(parentIndex) {
87+
return 2 * parentIndex + 2;
88+
}
89+
90+
getParentIndex(childIndex) {
91+
return Math.floor((childIndex - 1) / 2);
92+
}
93+
94+
hasLeftChild(parentIndex) {
95+
return this.getLeftChildIndex(parentIndex) < this.heap.length;
96+
}
97+
98+
hasRightChild(parentIndex) {
99+
return this.getRightChildIndex(parentIndex) < this.heap.length;
100+
}
101+
102+
leftChild(parentIndex) {
103+
return this.heap[this.getLeftChildIndex(parentIndex)];
104+
}
105+
106+
rightChild(parentIndex) {
107+
return this.heap[this.getRightChildIndex(parentIndex)];
108+
}
109+
110+
swap(indexOne, indexTwo) {
111+
const tmp = this.heap[indexTwo];
112+
this.heap[indexTwo] = this.heap[indexOne];
113+
this.heap[indexOne] = tmp;
114+
}
115+
}
116+
117+
export default MinHeapAdhoc;

src/data-structures/heap/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ Where:
5858

5959
> In this repository, the [MaxHeap.js](./MaxHeap.js) and [MinHeap.js](./MinHeap.js) are examples of the **Binary** heap.
6060
61+
## Implementation
62+
63+
- [MaxHeap.js](./MaxHeap.js) and [MinHeap.js](./MinHeap.js)
64+
- [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).
65+
6166
## References
6267

6368
- [Wikipedia](https://en.wikipedia.org/wiki/Heap_(data_structure))

0 commit comments

Comments
 (0)