一文深度了解堆

什么是堆?

堆(Heap)是一种基于数组的树形数据结构,其中每个节点都有一个值,且每个节点的值都大于等于(或小于等于)其子节点的值。堆分为大顶堆(Max Heap)和小顶堆(Min Heap)两种。 在本教程中,我们将使用JavaScript编写大顶堆小顶堆的相关代码。

堆的类型 

大顶堆

大顶堆的特点是每个节点的值都大于等于其子节点的值。因此,大顶堆的根节点的值一定是最大值。

定义一个数组来存储大顶堆

this.heap = []; // 使用数组来表示大顶堆

获取父节点的索引

  // 获取父节点的索引
  getParentIndex(index) {
    return index >> 1; // 右移1位 等价于 Math.floor((index - 1) / 2);
  }

 获取左右子节点的索引

  // 获取左子节点的索引
  getLeftChildIndex(index) {
    return 2 * index + 1;
  }

  // 获取右子节点的索引
  getRightChildIndex(index) {
    return 2 * index + 2;
  }

交换两个元素的位置swap

  // 交换两个元素的位置
  swap(i, j) {
    [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
  }

向上调整shiftUp

// 向上调整,使得以index为根的子树满足大顶堆特性
 shiftUp(index) {
    if (index === 0) return; // 已经到达根节点,无需再调整

    const parentIndex = this.getParentIndex(index);
    if (this.heap[parentIndex] < this.heap[index]) {
      this.swap(parentIndex, index); // 如果父节点的值小于当前节点,交换它们的位置
      this.shiftUp(parentIndex); // 递归向上调整
    }
  }

 向下调整shiftDown

  // 向下调整,使得以index为根的子树满足大顶堆特性
  shiftDown(index) {
    const leftChildIndex = this.getLeftChildIndex(index);
    const rightChildIndex = this.getRightChildIndex(index);
    let largestIndex = index; // 先假设当前节点是最大值

    // 找出当前节点、左子节点和右子节点中的最大值的索引
    if (leftChildIndex < this.heap.length && this.heap[leftChildIndex] > this.heap[largestIndex]) {
      largestIndex = leftChildIndex;
    }
    if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] > this.heap[largestIndex]) {
      largestIndex = rightChildIndex;
    }

    if (largestIndex !== index) {
      this.swap(largestIndex, index); // 如果最大值不是当前节点,交换它们的位置
      this.shiftDown(largestIndex); // 递归向下调整
    }
  }

 插入元素

insert函数用于插入元素,我们将元素插入到数组末尾,然后向上调整新元素的位置

// 插入元素
  insert(value) {
    this.heap.push(value); // 将新元素放到数组末尾
    this.shiftUp(this.heap.length - 1); // 向上调整新元素的位置
  }

移除并返回堆顶元素

extractMax函数用于删除元素,我们首先将根节点(即最大值)移动到数组的末尾,然后将其与子节点比较,如果小于子节点的值,则与较大的子节点交换位置,一直重复此操作,直到不再小于子节点或者到达叶子节点位置。

 // 移除并返回堆顶元素
  extractMax() {
    if (this.heap.length === 0) return null;

    const maxValue = this.heap[0]; // 保存堆顶元素的值
    this.swap(0, this.heap.length - 1); // 将堆顶元素与最后一个元素交换位置
    this.heap.pop(); // 移除最后一个元素
    this.shiftDown(0); // 向下调整新的堆顶元素的位置
    return maxValue;
  }

 

小顶堆

小顶堆的特点是每个节点的值都小于等于其子节点的值。因此,小顶堆的根节点的值一定是最小值。

在代码实现上,大顶堆和小顶堆的区别主要体现在两个方法:shiftUp(向上调整)和shiftDown(向下调整)。

参考大顶堆的代码,将其中的比较符号(><)互换即可实现小顶堆:

向上调整


  // 向上调整,使得以index为根的子树满足小顶堆特性
  shiftUp(index) {
    if (index === 0) return; // 已经到达根节点,无需再调整

    const parentIndex = this.getParentIndex(index);
    if (this.heap[parentIndex] > this.heap[index]) {
      this.swap(parentIndex, index); // 如果父节点的值大于当前节点,交换它们的位置
      this.shiftUp(parentIndex); // 递归向上调整
    }
  }

向下调整

 // 向下调整,使得以index为根的子树满足小顶堆特性
  shiftDown(index) {
    const leftChildIndex = this.getLeftChildIndex(index);
    const rightChildIndex = this.getRightChildIndex(index);
    let smallestIndex = index; // 先假设当前节点是最小值

    // 找出当前节点、左子节点和右子节点中的最小值的索引
    if (leftChildIndex < this.heap.length && this.heap[leftChildIndex] < this.heap[smallestIndex]) {
      smallestIndex = leftChildIndex;
    }
    if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] < this.heap[smallestIndex]) {
      smallestIndex = rightChildIndex;
    }

    if (smallestIndex !== index) {
      this.swap(smallestIndex, index); // 如果最小值不是当前节点,交换它们的位置
      this.shiftDown(smallestIndex); // 递归向下调整
    }
  }

堆的特点

  • 堆是一个完全二叉树,即除了最后一层外,每一层都是满二叉树。这使得堆的插入和删除操作可以在O(log n)的时间复杂度内完成。

  • 堆可以看作是一个优先队列,每次取出的元素都是当前堆中优先级最高的元素。

  • 堆的操作通常涉及到堆化(heapify)和上浮(percolate)两个过程,这两个过程可以帮助调整堆的结构,使其满足堆的性质。

堆的操作

  • 建立堆:首先将给定的元素按照从小到大的顺序排列,然后从最后一个非叶子节点开始,依次向上调整每个节点及其子节点的值,使其满足堆的性质。这个过程称为堆化。

  • 插入元素:在堆的任意位置插入一个新元素,首先将其与其父节点进行比较,如果新元素大于父节点,则交换它们的位置;然后继续向上调整父节点及其子节点的值,使其满足堆的性质。这个过程称为上浮。

  • 删除元素:从堆中删除指定位置的元素,首先将其与其父节点进行比较,如果新元素小于父节点,则交换它们的位置;然后继续向上调整父节点及其子节点的值,使其满足堆的性质。这个过程称为下沉。

  • 获取最大值:在堆中获取最大值的方法是从根节点开始,依次向下查找,直到找到一个没有右子节点的节点,即为最大值。

  • 获取最小值在堆中获取最小值的方法是从根节点开始,依次向下查找,直到找到一个没有左子节点的节点,即为最小值。注意,如果堆为空,则无法获取最大值和最小值。

堆的应用实例

堆排序算法

堆排序算法利用堆的性质对元素集合进行排序,主要分为下面两个实现步骤:

  • 构建堆:首先,将给定的无序数组构造成一个最小堆(或最大堆);

  • 排序:然后,不断的导出堆顶元素并记录,直到堆为空。

优先队列 

优先队列是一种特殊的数据结构,它允许元素按照其优先级进行插入和删除,并确保具有最高优先级的元素位于堆顶。优先队列通常支持插入和删除操作(对应堆的插入和删除操作):

  • 插入操作 push:元素可以被插入到优先队列中,插入操作需要根据元素的优先级来确定其在队列中的位置。

  • 删除操作 pop:删除操作通常是移除优先级最高的元素并返回。

下面我们结合一道力扣上的题目,使用堆来实现一个优先队列以解决问题:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值