JavaScript实现堆

239 阅读3分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

上一篇讲了堆和优先队列的基本概念,有兴趣的小伙伴请冲 堆与优先队列概念。 这篇我们用js来实现一个大顶堆。

一、基础代码

我们先来写下基础代码

  1. 先创建一个类Heap表示大顶堆;
  2. 初始化基本数据:用数组data来表示堆,用变量length表示堆的元素个数;
  3. 设置top方法:返回堆的最顶上元素,也即最大的元素;
  4. 设置size方法:返回堆的元素个数。
class Heap {
  constructor() {
    this.data = [];
    this.length = 0; // 堆元素个数
  }
  top() {
    return this.data[0];
  }
  size() {
    return this.length;
  }
}

二、插入元素

在堆中插入元素,是先将元素插入堆的最底层的最右边,然后再与其父元素进行对比,若大于父元素,则与父元素调换位置,调换位置后再与其现在父元素对比...直到不大于其父元素才确认位置。可以简单概括为插入元素时上浮。

  • 那如何知道堆的元素个数呢?

设定一个变量length,每次插入时都给length+1。

  • 那如何确定父元素的索引呢?

根据堆的性质,假如当前元素的索引为ind,则其父节点的索引为Math.floor((ind - 1) / 2)

如下图所示:

image.png

下面我们直接上代码啦~~

push(x) {
    // 将x放入堆的最下层的最右边,并将长度+1
    this.data[this.length++] = x;
    let ind = this.length - 1; // ind设为最后一个元素的索引
    // 判断当前元素是否大于父元素,若是则交换位置,交换位置后再继续对比...
    while (ind && this.data[ind] > this.data[Math.floor((ind - 1) / 2)]) {
      this.swap(ind, Math.floor((ind - 1) / 2));
      ind = Math.floor((ind - 1) / 2);
    }
}
swap(a, b) {
    let temp = this.data[a];
    this.data[a] = this.data[b];
    this.data[b] = temp;
}

测试代码:

  • let h = new Heap();
  • h.push(4); // [4]
  • h.push(1); // [4,1]
  • h.push(2); // [4, 1, 2]
  • h.push(3); // [4, 3, 2, 1]
  • h.push(5); // [5, 4, 2, 1, 3]

经过测试,没啥毛病,奈斯~~

三、删除元素

删除元素的步骤:

  • 先将大顶堆中最顶部元素(即最大元素)与最底层最右边的元素互换位置,然后删除最大元素,堆元素长度-1。
  • 判断当前元素是否有子节点,若有子节点执行下沉操作。 下沉操作的实质是对比当前节点与左右 节点哪个大,将当前节点与最大的节点互换位置,换完后再进行对比...

根据堆的性质,若当前元素索引为ind,则左节点的索引为 ind * 2 + 1,右节点的索引为 ind * 2 + 2

知道了基本操作步骤后我们来看看代码~~~

pop() {
  if (this.size() == 0) return; // 若堆为空则不操作
  this.swap(0, this.length - 1); // 调换堆中最顶部元素 和 最尾部元素的位置
  this.data.pop();
  this.length--; // 将堆元素个数-1
  // 如果有孩子执行下沉操作
  let ind = 0;
  //   对比当前节点与左右 节点哪个大,将当前节点与最大的节点互换位置
  // 当ind有子节点时才能操作,左节点的索引ind*2+1小于等于堆长度-1时证明左节点存在
  while (ind * 2 + 1 <= this.length - 1) {
    // 当前节点先跟左子树对比,则将temp设为左节点的索引
    let temp = ind;
    if (this.data[temp] < this.data[ind * 2 + 1]) {
      temp = ind * 2 + 1;
    }
    // 如果右节点存在,则再和右节点对比
    if (
      this.data[ind * 2 + 2] &&
      this.data[temp] < this.data[ind * 2 + 2]
    ) {
      temp = ind * 2 + 2;
    }
    // 若temp等于ind,则不需要交换位置,否则,交换temp和ind的元素位置,ind重新赋值
    if (temp == ind) break;
    this.swap(ind, temp);
    ind = temp;
  }
}

测试一下:

  • let h = new Heap();
  • h.push(4); // [4]
  • h.push(1); // [4,1]
  • h.push(2); // [4, 1, 2]
  • h.push(3); // [4, 3, 2, 1]
  • h.push(5); // [5, 4, 2, 1, 3]
  • h.pop(); // [4, 3, 2, 1]
  • h.pop(); // [3, 1, 2]

好的,完全没毛病,结束战斗,明天见~~~