在计算机科学中, 一个 ** 堆(heap)** 是一种特殊的基于树的数据结构,它满足下面描述的堆属性。

在一个 最小堆(min heap) 中, 如果 P 是 C 的一个父级节点, 那么 P 的key(或value)应小于或等于 C 的对应值.

在一个 最大堆(max heap) 中, P 的key(或value)大于 C 的对应值。

Heap:

import Comparator from '../../utils/comparator/Comparator';

/**
 * Parent class for Min and Max Heaps.
 */
export default class Heap {//堆类
  /**
   * @constructs Heap
   * @param {Function} [comparatorFunction]
   */
  constructor(comparatorFunction) {//构造函数,参数是自定义比较方法
    if (new.target === Heap) {
      //new.target属性允许你检测函数或构造方法是否是通过new运算符被调用的。
      //在普通的函数调用中,new.target 的值是undefined。
      //如果没有通过new调用,就抛出错误
      throw new TypeError('Cannot construct Heap instance directly');
    }

    // Array representation of the heap.
    this.heapContainer = [];//堆容器,是一个数组
    this.compare = new Comparator(comparatorFunction);//工具比较函数
  }

  /**
   * @param {number} parentIndex
   * @return {number}
   */
  getLeftChildIndex(parentIndex) {//根据节点索引计算出左子节点索引,根节点从0开始
    return (2 * parentIndex) + 1;//节点索引乘2加1
  }

  /**
   * @param {number} parentIndex
   * @return {number}
   */
  getRightChildIndex(parentIndex) {//根据节点索引计算出右子节点索引
    return (2 * parentIndex) + 2;//节点索引乘2加2
  }

  /**
   * @param {number} childIndex
   * @return {number}
   */
  getParentIndex(childIndex) {//根据节点索引计算出父节点索引
    return Math.floor((childIndex - 1) / 2);//节点索引减1除以2向下取整
  }

  /**
   * @param {number} childIndex
   * @return {boolean}
   */
  hasParent(childIndex) {//根据一个节点索引判断这个节点有没有父节点
    return this.getParentIndex(childIndex) >= 0;
  }

  /**
   * @param {number} parentIndex
   * @return {boolean}
   */
  hasLeftChild(parentIndex) {//根据一个节点索引判断这个节点有没有左子节点
    return this.getLeftChildIndex(parentIndex) < this.heapContainer.length;
    //如果获取到的左子节点索引小于堆的长度,那么说明这个节点有子节点
  }

  /**
   * @param {number} parentIndex
   * @return {boolean}
   */
  hasRightChild(parentIndex) {//根据一个节点索引判断这个节点有没有左子节点
    return this.getRightChildIndex(parentIndex) < this.heapContainer.length;
    //如果获取到的右子节点索引小于堆的长度,那么说明这个节点有子节点
  }

  /**
   * @param {number} parentIndex
   * @return {*}
   */
  leftChild(parentIndex) {//根据节点索引返回此节点的左子节点
    return this.heapContainer[this.getLeftChildIndex(parentIndex)];
  }

  /**
   * @param {number} parentIndex
   * @return {*}
   */
  rightChild(parentIndex) {//根据节点索引返回此节点的右子节点
    return this.heapContainer[this.getRightChildIndex(parentIndex)];
  }

  /**
   * @param {number} childIndex
   * @return {*}
   */
  parent(childIndex) {//根据节点索引返回此节点的父节点
    return this.heapContainer[this.getParentIndex(childIndex)];
  }

  /**
   * @param {number} indexOne
   * @param {number} indexTwo
   */
  swap(indexOne, indexTwo) {//两个节点互换位置
    const tmp = this.heapContainer[indexTwo];
    this.heapContainer[indexTwo] = this.heapContainer[indexOne];
    this.heapContainer[indexOne] = tmp;
  }

  /**
   * @return {*}
   */
  peek() {//获取堆的根节点
    if (this.heapContainer.length === 0) {//如果堆没有节点,直接返回null
      return null;
    }

    return this.heapContainer[0];//否则返回第一个节点
  }

  /**
   * @return {*}
   */
  poll() {//删除根节点
    if (this.heapContainer.length === 0) {//如果堆没有节点,直接返回null
      return null;
    }

    if (this.heapContainer.length === 1) {//如果堆只有一个节点,就删除最后一个节点,然后返回
      return this.heapContainer.pop();
    }

    const item = this.heapContainer[0];//有多个节点,先存下根节点

    // Move the last element from the end to the head.
    //移动最后一个元素从结尾到根部
    this.heapContainer[0] = this.heapContainer.pop();
    this.heapifyDown();//删除节点后使堆再次成为堆

    return item;//返回被删除的根节点
  }

  /**
   * @param {*} item
   * @return {Heap}
   */
  add(item) {//往堆中插入新节点
    this.heapContainer.push(item);//堆结尾插入新节点
    this.heapifyUp();//新增节点后使堆再次成为堆
    return this;
  }

  /**
   * @param {*} item
   * @param {Comparator} [comparator]
   * @return {Heap}
   */
  remove(item, comparator = this.compare) {//删除与给定值相等的节点
    // Find number of items to remove.
    const numberOfItemsToRemove = this.find(item, comparator).length;//查找所有要删除的节点的个数

    for (let iteration = 0; iteration < numberOfItemsToRemove; iteration += 1) {
      // We need to find item index to remove each time after removal since
      // indices are being changed after each heapify process.
      //需要找到需要删除的节点索引,因为每一次删除后的heapify都会让堆结构发生变化
      const indexToRemove = this.find(item, comparator).pop();//从要删除的元素索引的数组里拿出最后一个索引

      // If we need to remove last child in the heap then just remove it.
      // There is no need to heapify the heap afterwards.
      if (indexToRemove === (this.heapContainer.length - 1)) {//如过是堆的最后一个节点,直接删除,不需要做heapify操作
        this.heapContainer.pop();
      } else {//如果删除的是中间的节点,需要做处理
        // Move last element in heap to the vacant (removed) position.
        //移动最后一个节点到被删除节点的位置
        this.heapContainer[indexToRemove] = this.heapContainer.pop();

        // Get parent.
        const parentItem = this.parent(indexToRemove);//获取到当前被删除位置的父节点

        // If there is no parent or parent is in correct order with the node
        // we're going to delete then heapify down. Otherwise heapify up.
        //如果没有父节点或者父节点的次序正确,就进行向下的heapify,否则向上heapify
        if (
          this.hasLeftChild(indexToRemove)
          && (
            !parentItem
            || this.pairIsInCorrectOrder(parentItem, this.heapContainer[indexToRemove])
          )
        ) {
          this.heapifyDown(indexToRemove);
        } else {
          this.heapifyUp(indexToRemove);
        }
      }
    }

    return this;
  }

  /**
   * @param {*} item
   * @param {Comparator} [comparator]
   * @return {Number[]}
   */
  find(item, comparator = this.compare) {//查找与给定值相等的堆节点
    const foundItemIndices = [];//结果数组

    for (let itemIndex = 0; itemIndex < this.heapContainer.length; itemIndex += 1) {
      if (comparator.equal(item, this.heapContainer[itemIndex])) {//循环堆,判断如果与给定值相等,就插入结果数组
        foundItemIndices.push(itemIndex);
      }
    }

    return foundItemIndices;
  }

  /**
   * @return {boolean}
   */
  isEmpty() {//堆是否为空
    return !this.heapContainer.length;
  }

  /**
   * @return {string}
   */
  toString() {//返回堆数组的字符串格式
    return this.heapContainer.toString();
  }

  /**
   * @param {number} [customStartIndex]
   */
  heapifyUp(customStartIndex) {//新增节点后使堆再次成为堆
    // Take the last element (last in array or the bottom left in a tree)
    // in the heap container and lift it up until it is in the correct
    // order with respect to its parent element.
    //获取到最后一个节点,将它提升已使之和父节点在一个正确的次序
    let currentIndex = customStartIndex || this.heapContainer.length - 1;//当前最后一个节点索引

    while (
      this.hasParent(currentIndex)
      && !this.pairIsInCorrectOrder(this.parent(currentIndex), this.heapContainer[currentIndex])
      //当前节点有父节点,并且当前节点和父节点的次序不正确
    ) {
      this.swap(currentIndex, this.getParentIndex(currentIndex));//交换当前节点和父节点
      currentIndex = this.getParentIndex(currentIndex);//当前节点索引赋值为父节点索引,继续判断循环
    }
  }

  /**
   * @param {number} [customStartIndex]
   */
  heapifyDown(customStartIndex = 0) {//删除某一节点后,将堆再次形成堆
    // Compare the parent element to its children and swap parent with the appropriate
    // child (smallest child for MinHeap, largest child for MaxHeap).
    // Do the same for next children after swap.
    //将父节点和子节点比较后,将父节点和合适的子节点交换位置(最小堆就将父节点和最小的子节点交换位置;最大堆就将父节点和最大的子节点交换位置)
    //交换后对后面的子节点做同样的交换操作
    let currentIndex = customStartIndex;//需要最先处理的节点的索引
    let nextIndex = null;//下一个节点索引

    while (this.hasLeftChild(currentIndex)) {//循环条件,当前节点还有左子节点的时候就继续循环
      if (//考虑右孩子
        this.hasRightChild(currentIndex)
        && this.pairIsInCorrectOrder(this.rightChild(currentIndex), this.leftChild(currentIndex))
        //如果当前节点也有右子节点,并且当前节点的左右两个子节点的次序是正确的(最小堆和最大堆)
      ) {
        nextIndex = this.getRightChildIndex(currentIndex);//那么下一个节点就是当前节点的右子节点
      } else {
        nextIndex = this.getLeftChildIndex(currentIndex);//否则是左子节点
      }

      if (this.pairIsInCorrectOrder(//考虑父子结点位置
        this.heapContainer[currentIndex],
        this.heapContainer[nextIndex],
      )) {//如果当前节点和下一个节点的次序正确,跳出循环结束操作
        break;
      }

      this.swap(currentIndex, nextIndex);//否则交换当前节点和下一个节点
      currentIndex = nextIndex;//当前节点到了下一个位置,继续循环
    }
  }

  /**
   * Checks if pair of heap elements is in correct order.
   * For MinHeap the first element must be always smaller or equal.
   * For MaxHeap the first element must be always bigger or equal.
   *
   * @param {*} firstElement
   * @param {*} secondElement
   * @return {boolean}
   */
  /* istanbul ignore next */
  //检查一对堆节点是否是正确的次序
  //对于最小堆 第一个节点必须小于等于第二个节点
  //对于最大堆 第一个节点必须大于等于第二个节点
  //当你新实例化MinHeap和MaxHeap的时候,它们原型上的pairIsInCorrectOrder方法会隔离掉Heap类上的同名方法
  pairIsInCorrectOrder(firstElement, secondElement) {
    throw new Error(`
      You have to implement heap pair comparision method
      for ${firstElement} and ${secondElement} values.
    `);
  }
}

MaxHeap:对于最大堆 第一个节点必须大于等于第二个节点

import Heap from './Heap';

export default class MaxHeap extends Heap {
  /**
   * Checks if pair of heap elements is in correct order.
   * For MinHeap the first element must be always smaller or equal.
   * For MaxHeap the first element must be always bigger or equal.
   *
   * @param {*} firstElement
   * @param {*} secondElement
   * @return {boolean}
   */
  pairIsInCorrectOrder(firstElement, secondElement) {
    return this.compare.greaterThanOrEqual(firstElement, secondElement);
  }
}

MinHeap:对于最小堆 第一个节点必须小于等于第二个节点

import Heap from './Heap';

export default class MinHeap extends Heap {
  /**
   * Checks if pair of heap elements is in correct order.
   * For MinHeap the first element must be always smaller or equal.
   * For MaxHeap the first element must be always bigger or equal.
   *
   * @param {*} firstElement
   * @param {*} secondElement
   * @return {boolean}
   */
  pairIsInCorrectOrder(firstElement, secondElement) {
    return this.compare.lessThanOrEqual(firstElement, secondElement);
  }
}

note:

1.对于constructor(comparatorFunction)的解释

constructor(comparatorFunction) {
    if (new.target === Heap) {
      throw new TypeError('Cannot construct Heap instance directly');
    }

    // Array representation of the heap.
    this.heapContainer = [];
    this.compare = new Comparator(comparatorFunction);
  }

解释代码:不能直接new一个堆出来

 const instantiateHeap = () => {
      const heap = new Heap();
      heap.add(5);
};

expect(instantiateHeap).toThrow();

2.移除元素,自定义比较方法:

const maxHeap = new MaxHeap();
    maxHeap.add('a');
    maxHeap.add('bb');
    maxHeap.add('ccc');
    maxHeap.add('dddd');

    expect(maxHeap.toString()).toBe('dddd,ccc,bb,a');

    const comparator = new Comparator((a, b) => {
      if (a.length === b.length) {
        return 0;
      }

      return a.length < b.length ? -1 : 1;
    });

    maxHeap.remove('hey', comparator);
    expect(maxHeap.toString()).toBe('dddd,a,bb');
 
posted @ 2019-01-08 14:54  Archer-Fang  阅读(169)  评论(0编辑  收藏  举报