JavaScript实现最小堆

  • Post author:
  • Post category:java


class MinHeap {
  /**
   * 
   * @param {Array<Number>} data 
   */
  constructor() {
    this.data = [null];
  }

  /**
   * 返回堆大小
   * @returns Number
   */
  get size() {
    return this.data.length - 1;
  }

  /**
   * 返回队列中最小值
   */
  get min() {
    return this.data[1];
  }

  /**
   * 交换两个位置的值
   * @param {Number} i 
   * @param {Number} j 
   */
  _swap(i, j) {
    [this.data[i], this.data[j]] = [this.data[j], this.data[i]];
  }

  /**
   * 先把要插入的元素添加到堆底的最后,然后让其上浮到正确位置
   * @param {any} node 
   */
  insert(node) {
    this.data.push(node);
    this._swim(this.size);
    return this.data.slice(1);
  }

  /**
   * 先把堆顶元素 A 和堆底最后的元素 B 对调,然后删除 A,最后让 B 下沉到正确位置
   */
  delMin() {
    // 交换堆顶和堆底元素
    this._swap(1, this.size);
    // 保留最小值
    const min = this.data.pop();
    // 让堆顶元素下沉
    this._sink(1);
    return min;
  }

  /**
   * 上浮第 i 个元素,以维护最小堆性质
   * @param {Number} i 
   */
  _swim(i) {
    // 如果还没到堆顶并且 i 位置的值小于父元素,则交换
    while(i > 1 && this._less(i, this._getParentNodeIndex(i))) {
      this._swap(i, this._getParentNodeIndex(i));
      i = this._getParentNodeIndex(i);
    }
  }

  /**
   * 下沉第 i 个元素,以维护最小堆性质
   * @param {Number} i 
   */
  _sink(i) {
    while (this._getLeftNodeIndex(i) < this.size) {
      // 假设左子节点比较小
      let minIndex = this._getLeftNodeIndex(i);
      // 看一下右子节点是否存在,如果右子节点存在并且比左子节点小,便更新最小子节点的索引
      if (this._getRightNodeIndex(i) <= this.size && this._less(this._getRightNodeIndex(i), minIndex)) {
        minIndex = this._getRightNodeIndex(i);
      }
      // 如果该节点的值比两个子节点都小,就不用下沉了
      if (this._less(i, minIndex)) {
        break;
      }
      // 否则跟最小的交换
      this._swap(i, minIndex);
      // 更新i
      i = minIndex;
    }
  }

  /**
   * this.data[i] 是否比 this.data[j] 小
   * @param {Number} i 
   * @param {Number} j 
   * @returns Boolean
   */
  _less(i, j) {
    return this.data[i] < this.data[j];
  }

  /**
   * 获取 i 位置的父元素节点
   * @param {Number} i 
   */
  _getParentNode(i) {
    return this.data[this._getParentNodeIndex(i)];
  }

  /**
   * 获取 i 位置的父元素的索引
   * @param {Number} i 
   * @returns Number
   */
  _getParentNodeIndex(i) {
    /**
     * 已知父节点 p 的左子节点为 2p,右子节点为 2p + 1
     * 所以左子节点索引一定是偶数,右子节点索引一定是奇数
     */
    if (this._isLeftNode(i)) {
      return i / 2;
    } else {
      return (i - 1) / 2;
    }
  }

  /**
   * 获取 i 节点的左子节点
   * @param {Number} i 
   */
  _getLeftNode(i) {
    return this.data[this._getLeftNodeIndex(i)];
  }

  /**
   * 获取 i 节点的右子节点
   * @param {Number} i 
   */
  _getRightNode(i) {
    return this.data[this._getRightNodeIndex(i)];
  }

  /**
 * 获取 i 位置节点的左子节点索引
 * @param {Number} i 
 * @returns Number
 */
  _getLeftNodeIndex(i) {
    return 2 * i;
  }

  /**
   * 获取 i 位置节点的右子节点索引
   * @param {Number} i 
   * @returns Number
   */
  _getRightNodeIndex(i) {
    return 2 * i + 1;
  }

  /**
   * 判断 i 位置的节点是不是左节点
   * @param {Number} i 
   */
  _isLeftNode(i) {
    // 左节点索引一定是偶数
    return !this._isOdd(i);
  }

  /**
   * 判断 i 位置的节点是不是右节点
   * @param {Number} i 
   */
  _isRightNode(i) {
    // 左节点索引一定是偶数
    return this._isOdd(i);
  }

  /**
   * 判断一个数是否是奇数
   * @param {Number} num 
   * @returns Boolean
   */
  _isOdd(num) {
    return !!(num % 2);
  }
}



版权声明:本文为ZhangLinHao1997原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。