JavaScript系列-堆

110 阅读2分钟

JavaScript系列-堆

堆数据结构,是树中的一种,我们常用的堆数据是二叉堆;

二叉堆能够实现优先级队列

Linux内核中对各个进程的调度分配,定时器的实现原理,React filber的任务调度分配,都利用优先级队列的思维来解决问题。

二叉堆中,每一个结点都必须是可比较的,否则就无法判断优先级

二叉堆是一颗完全二叉树<即在树结构中,除了最后一层,其他结点都是完整的[每一个结点都拥有左右两个子节点]>:

分为两种类型:

  1. 最大堆,即任何父节点的键值,都大于等于任何一个子节点,最大堆的根节点为堆顶,堆顶元素是整个堆中的最大值。
  2. 最小堆,即任何父节点的键值,都小于等于任何一个子节点,最笑对的根节点为堆顶,堆顶元素是整个堆中的最小值。

对于二叉堆,只有几个常规操作:插入节点,删除结点,以及构建二叉堆

这几种操作都是基于堆的自我调整

  • 插入节点

    • 二叉堆的节点插入,只能是二叉堆结构中的最后一个位置,然后遵循最小堆原则,需要进行上浮操作
  • 删除节点

    • 二叉堆的删除节点,与插入节点正好相反,我们只会删除处于堆顶的元素,但是删除之后,二叉堆的结构就出现了混乱,为了维持完全二叉树的结构,我们把堆的最后一个节点补到原本堆顶的位置。
    • 补充后我们发现,树结构不符合最小堆的特性,因此需要将新的堆顶元素与子元素比较,找到最小的子元素与其交换位置,这个行为我们称为下沉
    • 直到完全符合最小堆的规则为止。
  • 构建二叉堆

    • 二叉堆虽然是一颗完全二叉树,但它的存储方式不是链式存储,而是顺序存储,二叉堆的所有节点都存储在数组中。

实现一个构建二叉堆的算法

class BinaryHead {
  /**
   *
   * @param {*} compare 比较函数,构建最大堆还是最小堆
   * @param {*} array 初始数组
   */
  constructor(compare, array) {
    this.compare = compare;
    if (array) {
      this.heap = array;
      this.size = this.heap.length;
      this.buildHead();
    } else {
      this.heap = [];
      this.size = 0;
    }
  }
​
  isEmpty() {
    return this.size === 0;
  }
​
  // 通过子节点下标,找到父节点的下标
  parentIndex(i) {
    return Math.floor((i - 1) / 2);
  }
​
  leftIndex(i) {
    return 2 * i + 1;
  }
​
  rightIndex(i) {
    return 2 * i + 2;
  }
​
  swap(i, j) {
    const temp = this.heap[i];
    this.heap[i] = this.heap[j];
    this.heap[j] = temp;
  }
​
  parent(i) {
    return this.heap[this.parentIndexp(i)];
  }
  // 插入节点
  push(node) {
    if (this.size === 0) {
      this.size++;
      this.heap[0] = node;
      return;
    }
    this.size++;
    let i = this.size - 1;
    this.heap[i] = node;
    while (i !== 0 && this.compare(this.heap[i], this.parent[i])) {
      this.swap(i, this.parentIndex(i));
      i = this.parentIndex(i);
    }
  }
​
  // 删除节点
  pop() {
    if (this.size <= 0) {
      return null;
    }
    if (this.size === 1) {
      let node = this.heap[this.size - 1];
      this.size--;
      this.heap.length = this.size;
      return node;
    }
    // 将堆顶元素拿出来
    const root = this.heap[0];
    // 最下方的节点拿出来,放在堆顶
    this.heap[0] = this.heap[this.size - 1];
    this.size--;
    this.heap.length = this.size;
    this.heapify(0);
    return root;
  }
​
  // 删除或者构建二叉树,都需要重新排序
  heapify(i) {
    const l = this.leftIndex(i);
    const r = this.rightIndex(i);
    const [pv, lv, rv] = [this.heap[i], this.heap[l], this.heap[r]];
    let small = i;
    if (l < this.size && this.compare(lv, pv)) {
      small = l;
    }
    if (r < this.size && this.compare(rv, this.heap[small])) {
      console.log(2);
      small = r;
    }
    if (small !== i) {
      this.swap(i, small);
      this.heapify(small);
    }
  }
​
  // 构建堆,从最后一个非叶子节点开始遍历构建
  // 非叶子节点: 拥有子树,度不为0的节点
  buildHead() {
    for (let i = this.parentIndex(this.size - 1); i >= 0; i--) {
      // 传递父节点下标
      this.heapify(i);
    }
  }
}
​
const compare = (a, b) => {
  return a < b;
};
​
let h1 = new BinaryHead(compare);
h1.push(1);
h1.push(2);
h1.push(3);
h1.push(4);
h1.push(5);
console.log(h1.heap);
h1.pop();
console.log(h1.heap);
var array = [150, 80, 40, 30, 10, 70, 110, 100, 20, 90, 60, 50, 120, 140, 130];
const h2 = new BinaryHead(compare, array);
console.log(h2.heap);
​

\