|
1 | 1 | /**
|
2 |
| - * A binary heap is a complete binary tree which |
3 |
| - * satisfies the heap ordering property. |
| 2 | + * A binary heap is a complete binary tree that satisfies the heap ordering property. |
| 3 | + * This implementation supports both minimum and maximum heaps, depending on the |
| 4 | + * comparison function provided. If no comparison function is provided, it defaults to |
| 5 | + * a minimum heap. |
4 | 6 | *
|
5 | 7 | * @example
|
6 | 8 | * var Heap = require('path-to-algorithms/src/data-structures/heap').Heap;
|
7 | 9 | *
|
| 10 | + * // Example for a max heap using birthyear for comparison: |
8 | 11 | * var heap = new Heap(function(a, b) {
|
9 | 12 | * return a.birthyear - b.birthyear;
|
10 | 13 | * });
|
11 | 14 | *
|
12 |
| - * heap.add({ |
13 |
| - * name: 'John', |
14 |
| - * birthyear: 1981 |
15 |
| - * }); |
16 |
| - * heap.add({ |
17 |
| - * name: 'Pavlo', |
18 |
| - * birthyear: 2000 |
19 |
| - * }); |
20 |
| - * heap.add({ |
21 |
| - * name: 'Garry', |
22 |
| - * birthyear: 1989 |
23 |
| - * }); |
24 |
| - * heap.add({ |
25 |
| - * name: 'Derek', |
26 |
| - * birthyear: 1990 |
27 |
| - * }); |
28 |
| - * heap.add({ |
29 |
| - * name: 'Ivan', |
30 |
| - * birthyear: 1966 |
31 |
| - * }); |
| 15 | + * heap.add({ name: 'John', birthyear: 1981 }); |
| 16 | + * heap.add({ name: 'Pavlo', birthyear: 2000 }); |
| 17 | + * heap.add({ name: 'Garry', birthyear: 1989 }); |
| 18 | + * heap.add({ name: 'Derek', birthyear: 1990 }); |
| 19 | + * heap.add({ name: 'Ivan', birthyear: 1966 }); |
32 | 20 | *
|
33 | 21 | * console.log(heap.extract()); // { name: 'Pavlo', birthyear: 2000 }
|
34 | 22 | * console.log(heap.extract()); // { name: 'Derek', birthyear: 1990 }
|
|
39 | 27 | * @module data-structures/heap
|
40 | 28 | */
|
41 | 29 | (function (exports) {
|
42 |
| - |
43 |
| - 'use strict'; |
| 30 | + "use strict"; |
44 | 31 |
|
45 | 32 | /**
|
46 |
| - * Minimum heap constructor. |
| 33 | + * Heap constructor. |
| 34 | + * The heap can be either a min-heap or max-heap, determined by the comparison function. |
| 35 | + * By default, it behaves as a min-heap if no comparison function is provided. |
47 | 36 | *
|
48 | 37 | * @public
|
49 | 38 | * @constructor
|
50 |
| - * @param {Function} cmp Function used for comparison between the elements. |
| 39 | + * @param {Function} cmp A function used for comparing elements (optional). |
| 40 | + * It should return a positive number if a > b, |
| 41 | + * a negative number if a < b, and 0 if they are equal. |
51 | 42 | */
|
52 | 43 | exports.Heap = function (cmp) {
|
53 | 44 | this._heap = [];
|
54 |
| - if (typeof cmp === 'function') { |
55 |
| - this._cmp = cmp; |
56 |
| - } else { |
57 |
| - this._cmp = function (a, b) { |
58 |
| - return a - b; |
59 |
| - }; |
60 |
| - } |
| 45 | + // If comparison function is provided, use it; otherwise, use default for min-heap. |
| 46 | + this._cmp = |
| 47 | + typeof cmp === "function" |
| 48 | + ? cmp |
| 49 | + : function (a, b) { |
| 50 | + return a - b; |
| 51 | + }; |
61 | 52 | };
|
62 | 53 |
|
63 | 54 | /**
|
64 |
| - * Exchange indexes with start index given as argument |
65 |
| - * to turn the tree into a valid heap. On a single call |
66 |
| - * this method maintains only a single "branch" of the heap.<br><br> |
| 55 | + * Heapifies the subtree rooted at the given index, maintaining the heap property. |
| 56 | + * This method assumes that the subtrees are already valid heaps and fixes the |
| 57 | + * heap rooted at the provided index. |
67 | 58 | *
|
68 | 59 | * Time complexity: O(log N).
|
69 | 60 | *
|
70 | 61 | * @private
|
71 |
| - * @param {Number} index The parent. |
| 62 | + * @param {Number} index Index of the node to heapify. |
72 | 63 | */
|
73 | 64 | exports.Heap.prototype._heapify = function (index) {
|
74 |
| - var extr = index; |
75 |
| - var left = 2 * index + 1; |
76 |
| - var right = 2 * index + 2; |
| 65 | + var largest = index; |
| 66 | + var left = 2 * index + 1; // Left child index |
| 67 | + var right = 2 * index + 2; // Right child index |
77 | 68 | var temp;
|
78 | 69 |
|
79 |
| - if (left < this._heap.length && |
80 |
| - this._cmp(this._heap[left], this._heap[index]) > 0) { |
81 |
| - extr = left; |
| 70 | + // Compare the left child with the current node (index). |
| 71 | + if ( |
| 72 | + left < this._heap.length && |
| 73 | + this._cmp(this._heap[left], this._heap[index]) > 0 |
| 74 | + ) { |
| 75 | + largest = left; |
82 | 76 | }
|
83 | 77 |
|
84 |
| - if (right < this._heap.length && |
85 |
| - this._cmp(this._heap[right], this._heap[index]) > 0 && |
86 |
| - this._cmp(this._heap[right], this._heap[left]) > 0) { |
87 |
| - extr = right; |
| 78 | + // Compare the right child with the current largest node. |
| 79 | + if ( |
| 80 | + right < this._heap.length && |
| 81 | + this._cmp(this._heap[right], this._heap[largest]) > 0 |
| 82 | + ) { |
| 83 | + largest = right; |
88 | 84 | }
|
89 | 85 |
|
90 |
| - if (index !== extr) { |
| 86 | + // If the largest node is not the current index, swap and heapify. |
| 87 | + if (largest !== index) { |
91 | 88 | temp = this._heap[index];
|
92 |
| - this._heap[index] = this._heap[extr]; |
93 |
| - this._heap[extr] = temp; |
94 |
| - this._heapify(extr); |
| 89 | + this._heap[index] = this._heap[largest]; |
| 90 | + this._heap[largest] = temp; |
| 91 | + // Recursively heapify the affected subtree. |
| 92 | + this._heapify(largest); |
95 | 93 | }
|
96 | 94 | };
|
97 | 95 |
|
98 | 96 | /**
|
99 |
| - * Changes the key.<br><br> |
100 |
| - * Complexity: O(log N). |
| 97 | + * Changes the key/value of the element at the given index and repositions it |
| 98 | + * to maintain the heap property. |
| 99 | + * |
| 100 | + * Time complexity: O(log N). |
101 | 101 | *
|
102 | 102 | * @public
|
103 |
| - * @param {Number} index Index of the value which should be changed. |
104 |
| - * @param {Number|Object} value New value according to the index. |
105 |
| - * @return {Number} New position of the element. |
| 103 | + * @param {Number} index Index of the element to change. |
| 104 | + * @param {Number|Object} value The new value for the element. |
| 105 | + * @return {Number} The new position of the element. |
106 | 106 | */
|
107 | 107 | exports.Heap.prototype.changeKey = function (index, value) {
|
108 | 108 | this._heap[index] = value;
|
109 | 109 | var elem = this._heap[index];
|
110 |
| - var parent = Math.floor(index / 2); |
| 110 | + var parent = Math.floor((index - 1) / 2); // Parent index (use (index-1)/2 for zero-based index). |
111 | 111 | var temp;
|
112 |
| - if (elem !== undefined) { |
113 |
| - while (parent >= 0 && this._cmp(elem, this._heap[parent]) > 0) { |
114 |
| - temp = this._heap[parent]; |
115 |
| - this._heap[parent] = elem; |
116 |
| - this._heap[index] = temp; |
117 |
| - index = parent; |
118 |
| - parent = Math.floor(parent / 2); |
119 |
| - } |
120 |
| - this._heapify(index); |
| 112 | + |
| 113 | + // Move the element up the tree if it's greater than its parent. |
| 114 | + while (index > 0 && this._cmp(elem, this._heap[parent]) > 0) { |
| 115 | + temp = this._heap[parent]; |
| 116 | + this._heap[parent] = elem; |
| 117 | + this._heap[index] = temp; |
| 118 | + index = parent; |
| 119 | + parent = Math.floor((parent - 1) / 2); |
121 | 120 | }
|
122 |
| - return parent; |
| 121 | + |
| 122 | + // Ensure the heap is valid after key change. |
| 123 | + this._heapify(index); |
| 124 | + return index; |
123 | 125 | };
|
124 | 126 |
|
125 | 127 | /**
|
126 |
| - * Updates a given node. This operation is useful |
127 |
| - * in algorithms like Dijkstra, A* where we need |
128 |
| - * to decrease/increase value of the given node. |
| 128 | + * Updates a specific node by repositioning it in the heap to maintain the heap property. |
| 129 | + * This is useful in algorithms where node values need to be updated, such as Dijkstra's or A*. |
| 130 | + * |
| 131 | + * Time complexity: O(log N). |
129 | 132 | *
|
130 | 133 | * @public
|
131 |
| - * @param {Number|Object} node Node which should be updated. |
| 134 | + * @param {Number|Object} node The node to be updated. |
132 | 135 | */
|
133 | 136 | exports.Heap.prototype.update = function (node) {
|
134 | 137 | var idx = this._heap.indexOf(node);
|
|
138 | 141 | };
|
139 | 142 |
|
140 | 143 | /**
|
141 |
| - * Adds new element to the heap.<br><br> |
142 |
| - * Complexity: O(log N). |
| 144 | + * Adds a new element to the heap, maintaining the heap property. |
| 145 | + * |
| 146 | + * Time complexity: O(log N). |
143 | 147 | *
|
144 | 148 | * @public
|
145 |
| - * @param {Number|Object} value Value which will be inserted. |
146 |
| - * @return {Number} Index of the inserted value. |
| 149 | + * @param {Number|Object} value The value to be added to the heap. |
| 150 | + * @return {Number} The index of the inserted value. |
147 | 151 | */
|
148 | 152 | exports.Heap.prototype.add = function (value) {
|
| 153 | + // Insert the value at the end of the array and then move it to the correct position. |
149 | 154 | this._heap.push(value);
|
150 | 155 | return this.changeKey(this._heap.length - 1, value);
|
151 | 156 | };
|
152 | 157 |
|
153 | 158 | /**
|
154 |
| - * Returns current value which is on the top of the heap.<br><br> |
155 |
| - * Complexity: O(1). |
| 159 | + * Retrieves, but does not remove, the element at the top of the heap (the extremum). |
| 160 | + * |
| 161 | + * Time complexity: O(1). |
156 | 162 | *
|
157 | 163 | * @public
|
158 |
| - * @return {Number|Object} Current top value. |
| 164 | + * @return {Number|Object} The current top value of the heap. |
159 | 165 | */
|
160 | 166 | exports.Heap.prototype.top = function () {
|
161 | 167 | return this._heap[0];
|
162 | 168 | };
|
163 | 169 |
|
164 | 170 | /**
|
165 |
| - * Removes and returns the current extremum value |
166 |
| - * which is on the top of the heap.<br><br> |
167 |
| - * Complexity: O(log N). |
| 171 | + * Removes and returns the top (extremum) element from the heap. |
| 172 | + * After removal, the heap property is restored. |
| 173 | + * |
| 174 | + * Time complexity: O(log N). |
168 | 175 | *
|
169 | 176 | * @public
|
170 |
| - * @returns {Number|Object} The extremum value. |
| 177 | + * @return {Number|Object} The extremum value removed from the heap. |
| 178 | + * @throws Will throw an error if the heap is empty. |
171 | 179 | */
|
172 | 180 | exports.Heap.prototype.extract = function () {
|
173 |
| - if (!this._heap.length) { |
174 |
| - throw 'The heap is already empty!'; |
| 181 | + if (this._heap.length === 0) { |
| 182 | + throw new Error("The heap is already empty!"); |
| 183 | + } |
| 184 | + |
| 185 | + // Swap the top element with the last one, remove the last, and heapify the top. |
| 186 | + var extr = this._heap[0]; |
| 187 | + var last = this._heap.pop(); |
| 188 | + if (this._heap.length > 0) { |
| 189 | + this._heap[0] = last; |
| 190 | + this._heapify(0); |
175 | 191 | }
|
176 |
| - var extr = this._heap.shift(); |
177 |
| - this._heapify(0); |
178 | 192 | return extr;
|
179 | 193 | };
|
180 | 194 |
|
| 195 | + /** |
| 196 | + * Returns the entire collection of the heap's elements. |
| 197 | + * |
| 198 | + * @public |
| 199 | + * @return {Array} An array representing the heap's internal collection. |
| 200 | + */ |
181 | 201 | exports.Heap.prototype.getCollection = function () {
|
182 | 202 | return this._heap;
|
183 | 203 | };
|
184 | 204 |
|
185 | 205 | /**
|
186 |
| - * Checks or heap is empty. |
| 206 | + * Checks if the heap is empty. |
187 | 207 | *
|
188 | 208 | * @public
|
189 |
| - * @returns {Boolean} Returns true if heap is empty. |
| 209 | + * @return {Boolean} True if the heap is empty, otherwise false. |
190 | 210 | */
|
191 | 211 | exports.Heap.prototype.isEmpty = function () {
|
192 |
| - return !this._heap.length; |
| 212 | + return this._heap.length === 0; |
193 | 213 | };
|
194 |
| - |
195 |
| -})(typeof window === 'undefined' ? module.exports : window); |
| 214 | +})(typeof window === "undefined" ? module.exports : window); |
0 commit comments