While researching binary tree implementations in JavaScript, I ran across Marijn Haverbeke's implementation from the first edition of Eloquent JavaScript. What I especially like about this implementation is the use of sequential representation of a binary tree.
I re-factored (using newer syntax, as the first edition is pre-ES6) and annotated:
1
function BinaryHeap(compare) {
2
this.content = [];
3
this.compare = compare;
4
}
5
6
BinaryHeap.prototype = {
7
push: function(element) {
8
this.content.push(element);
9
this.bubbleUp(this.content.length - 1);
10
},
11
12
pop: function() {
13
// Remove the first element from the tree,
14
// and store it to remove it.
15
let result = this.content.shift();
16
17
// When removing an element from a minHeap,
18
// we remove the last element, place it at
19
// the top, then allow it to sink down the
20
// tree.
21
if (this.content.length) {
22
this.content.unshift(this.content.pop());
23
this.sinkDown(0);
24
}
25
26
return result;
27
},
28
29
peek: function() {
30
return this.content[0];
31
},
32
33
size: function() {
34
return this.content.length;
35
},
36
37
bubbleUp: function(index) {
38
// We now iterate through the tree, which is represented here as an array.
39
// (See: https://www.geeksforgeeks.org/binary-tree-array-implementation/)
40
//
41
// To do so with a zero-indexed tree, which looks like this:
42
//
43
//. A(0)
44
//. / \
45
//. B(1) C(2)
46
//. / \ \
47
//. D(3) E(4) F(5)
48
//
49
// Then, with parent P, then the left child is (2*P)+1, and the right
50
// child is (2*P)+2;
51
//
52
// Thus, the left child of B is:
53
//
54
// (2 * 1) + 1 = 3.
55
//
56
// While the right child is:
57
//
58
// (2 * 1)+ 2 = 4.
59
60
while (index > 0) {
61
let valueToInsert = this.content[index];
62
63
// To calculate the parent of a node, say the parent of node E
64
// in the example above, we can do the following calculation:
65
//
66
// Math.floor((E + 1) / 2) - 1 =
67
// Math.floor(5 / 2) - 1 =
68
// 2 - 1 = 1
69
//
70
// Similarly, to calculate the parent of F, we do the following:
71
//
72
// Math.floor((F + 1) / 2) - 1 =
73
// 3 - 1 = 2
74
75
let parentNodeIndex = Math.floor((index + 1) / 2) - 1;
76
77
if (this.compare(
78
this.content[parentNodeIndex],
79
this.content[index])) {
80
81
// If the child node's value is (for a minHeap) greater than the parent node,
82
// we're done, e.g. the parent node value must be less than
83
break;
84
85
} else {
86
// Otherwise, we're going to need to swap the two values.
87
// this.swap(this.content[index], this.content[parentNodeIndex])
88
let tmp = this.content[parentNodeIndex];
89
this.content[parentNodeIndex] = valueToInsert;
90
this.content[index] = tmp;
91
92
// We're simply climbing up the binary tree
93
// here, comparing each node with its parent.
94
index = parentNodeIndex;
95
}
96
}
97
},
98
99
sinkDown: function(index) {
100
let length = this.content.length;
101
102
// we'll need to sink down the value at the index
103
// the function is given.
104
let valueToInsert = this.content[index];
105
106
// We now traverse the tree downwards.
107
while (true) {
108
let rightChildIndex = (index + 1) * 2;
109
let leftChildIndex = rightChildIndex - 1;
110
let swapIndex = null;
111
112
// see if the left child exists.
113
if (leftChildIndex < length) {
114
// If so, if the left child's value is (for a minHeap)
115
// less than the index's value, prepare to swap the two.
116
if (this.compare(
117
this.content[leftChildIndex],
118
this.content[index]))
119
swapIndex = leftChildIndex;
120
}
121
122
// see if the right child exists.
123
if (rightChildIndex < length) {
124
// If the right element is (for a minHeap) lower than the left element (and
125
// as a function of the logic, lower than the parent index) then swap with the parent
126
// index.
127
if (this.compare(this.content[rightChildIndex],
128
(swapIndex === null ? this.content[index] : this.content[leftChildIndex]))) {
129
swapIndex = rightChildIndex;
130
}
131
}
132
133
// if there's nothing to swap, break.
134
if (swapIndex === null) break;
135
136
// The value with either the parent, or other child.
137
this.content[index] = this.content[swapIndex];
138
this.content[swapIndex] = valueToInsert;
139
140
// Now, do the same process on the same value, but in the position of the
141
// node with which we swapped.
142
index = swapIndex;
143
}
144
}
145
}
146
147
var minHeap = new BinaryHeap((a, b) => a <= b);
148
([10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5].forEach((x) => minHeap.push(x)));
149
while (minHeap.content.length) console.log(minHeap.pop());
150
151
var maxHeap = new BinaryHeap((a, b) => a >= b);
152
([10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5].forEach((x) => maxHeap.push(x)));
153
while (maxHeap.content.length) console.log(maxHeap.pop());