JS Heap(最大堆/最小堆)
最大堆Code:
/**
* 最大堆
* @class
*/
class MaxHeap {
/**
* @type {number[]}
*/
#maxHeap;
/**
* 建堆
* @constructor
*/
constructor(nums) {
this.#maxHeap = nums === undefined ? [] : [...nums];
// 从底部叶节点的父元素位置开始从顶至底规范
for (let i = this.#parent(this.size() - 1); i >= 0; i--) {
this.#shiftDown(i);
}
}
/**
* 获取左子节点的索引
* @param {number} i
*/
#left(i) {
return 2 * i + 1;
}
/**
* 获取右子节点的索引
* @param {number} i
*/
#right(i) {
return 2 * i + 2;
}
/**
* 获取节点父元素的索引
* @param {number} i
*/
#parent(i) {
return Math.floor((i - 1) / 2);
}
/**
* 获取堆元素的数量
* @returns
*/
size() {
return this.#maxHeap.length;
}
/**
* 是否为空堆
* @returns
*/
isEmpty() {
return this.#maxHeap.length === 0;
}
/**
* 获取堆顶元素
* @returns
*/
peek() {
return this.#maxHeap[0];
}
/**
* 获取堆的副本元素
* @returns
*/
getHeap() {
return [...this.#maxHeap];
}
/**
* 交换两个元素的位置
* @param {number} i
* @param {number} j
*/
#swap(i, j) {
const tmp = this.#maxHeap[i];
this.#maxHeap[i] = this.#maxHeap[j];
this.#maxHeap[j] = tmp;
}
/**
* 从位置 i 开始,从底至顶堆化
* @param {number} i
*/
#shiftUp(i) {
while (true) {
// 父节点索引
const p = this.#parent(i);
// 节点所在位置合理时,跳出
if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) {
break;
}
this.#swap(i, p);
i = p;
}
}
/**
* 从位置 i 开始,从顶至底堆化
* @param {number} i
*/
#shiftDown(i) {
while (true) {
// 左子节点索引
const l = this.#left(i);
// 右子节点索引
const r = this.#right(i);
// 自身与左子、右子中的最大值
let max = i;
//// l索引在合理范围内 且 l位置的元素大于max(i)位置的元素
if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[max]) {
max = l;
}
//// r索引在合理范围内 且 r位置的元素大于max位置的元素
if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[max]) {
max = r;
}
// max的值依然是i
if (max === i) {
break;
}
// 交互不符合规则的两元素
this.#swap(i, max);
// 更新i,并继续循环
i = max;
}
}
/**
* 添加元素
* @param {number} val
*/
push(val) {
this.#maxHeap.push(val);
// 加入新元素后,由底至上重新规划各元素位置
this.#shiftUp(this.size() - 1);
}
/**
* 移除堆顶元素
* @returns
*/
pop() {
if (this.isEmpty()) {
throw new Error('堆内已无元素!');
}
// 交换头尾元素
this.#swap(this.size() - 1, 0);
// 弹出尾元素(即之前的头元素,堆中的最大值)
const max = this.#maxHeap.pop();
// 由顶至下重新规划各元素位置
this.#shiftDown(0);
return max;
}
}
最小堆Code:
/**
* 最小堆
* @class
*/
class MinHeap {
/**
* @type {number[]}
*/
#minHeap;
/**
* 建堆
* @constructor
*/
constructor(nums) {
this.#minHeap = nums === undefined ? [] : [...nums];
// 从底部叶节点的父元素位置开始从顶至底规范
for (let i = this.#parent(this.size() - 1); i >= 0; i--) {
this.#shiftDown(i);
}
}
/**
* 获取左子节点的索引
* @param {number} i
*/
#left(i) {
return 2 * i + 1;
}
/**
* 获取右子节点的索引
* @param {number} i
*/
#right(i) {
return 2 * i + 2;
}
/**
* 获取节点父元素的索引
* @param {number} i
*/
#parent(i) {
return Math.floor((i - 1) / 2);
}
/**
* 获取堆元素的数量
* @returns
*/
size() {
return this.#minHeap.length;
}
/**
* 是否为空堆
* @returns
*/
isEmpty() {
return this.#minHeap.length === 0;
}
/**
* 获取堆顶元素
* @returns
*/
peek() {
return this.#minHeap[0];
}
/**
* 获取堆的副本元素
* @returns
*/
getHeap() {
return [...this.#minHeap];
}
/**
* 交换两个元素的位置
* @param {number} i
* @param {number} j
*/
#swap(i, j) {
const tmp = this.#minHeap[i];
this.#minHeap[i] = this.#minHeap[j];
this.#minHeap[j] = tmp;
}
/**
* 从位置 i 开始,从底至顶堆化
* @param {number} i
*/
#shiftUp(i) {
while (true) {
// 父节点索引
const p = this.#parent(i);
// 节点所在位置合理时,跳出
if (p < 0 || this.#minHeap[p] <= this.#minHeap[i]) {
break;
}
this.#swap(i, p);
i = p;
}
}
/**
* 从位置 i 开始,从顶至底堆化
* @param {number} i
*/
#shiftDown(i) {
while (true) {
// 左子节点索引
const l = this.#left(i);
// 右子节点索引
const r = this.#right(i);
// 自身与左子、右子中的最小值
let min = i;
//// l索引在合理范围内 且 l位置的元素小于min(i)位置的元素
if (l < this.size() && this.#minHeap[l] < this.#minHeap[min]) {
min = l;
}
//// r索引在合理范围内 且 r位置的元素小于min位置的元素
if (r < this.size() && this.#minHeap[r] < this.#minHeap[min]) {
min = r;
}
// min的值依然是i
if (min === i) {
break;
}
// 交互不符合规则的两元素
this.#swap(i, min);
// 更新i,并继续循环
i = min;
}
}
/**
* 添加元素
* @param {number} val
*/
push(val) {
this.#minHeap.push(val);
// 加入新元素后,由底至上重新规划各元素位置
this.#shiftUp(this.size() - 1);
}
/**
* 移除堆顶元素
* @returns
*/
pop() {
if (this.isEmpty()) {
throw new Error('堆内已无元素!');
}
// 交换头尾元素
this.#swap(this.size() - 1, 0);
// 弹出尾元素(即之前的头元素,堆中的最大值)
const max = this.#minHeap.pop();
// 由顶至下重新规划各元素位置
this.#shiftDown(0);
return max;
}
}
与最大堆的主要差异集中在shiftUp和shiftDown的逻辑上