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的逻辑上

posted @ 2024-03-28 10:03  樊顺  阅读(56)  评论(0编辑  收藏  举报