堆 (Heap) 与优先队列

355 阅读4分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

  • 前情回顾,完全二叉树的 思维逻辑结构实际的程序存储结构
  • 下面左图为完全二叉树的逻辑结构,右图为其在程序中的实际存储结构

什么是堆

  • 可以理解为是基于完全二叉树的一种数据结构
  • 大顶堆
    • 在二叉树中任意有关联的三个节点,都满足 父节点的值 都大于 两个子节点的值
  • 小顶堆
    • 在二叉树中任意有关联的三个节点,都满足 父节点的值 都小于 两个字节点的值

堆的尾部插入调整

  • 大顶堆 为例
  • 由于堆就是完全二叉树,所以当有新增元素时,需要将新增元素放到完全二叉树的最后一层的最左侧
  • 对应的,也就是在实际程序的连续存储区域的尾部,插入新增的元素

  • 插入之后需要进行向上调整,使得整棵树再次满足 大顶堆 的性质,调整过程如下:
    • 子节点 6 大于父节点 3,所以要将 6 和 3 调换位置
    • 在父节点 5 与两个子节点 4,6 中,6 最大,所以将 6 与 5 调换位置
    • 最后,整颗树满足 大顶堆 的性质,停止调整

堆的头部弹出调整

  • 以上面插入元素后的 大顶堆 为例
  • 当弹出堆顶元素后,也就是对应的连续存储空间的头部元素被弹出
  • 为了维护堆是完全二叉树的性质,通常需要将完全二叉树的,最底层的最右边的元素,放至堆顶
  • 也就是将对应连续存储空间的尾部元素,放至头部,使得现在的连续存储空间的前 n - 1 位都是连续的存储着元素

  • 然后再次为了维护 大顶堆 的性质,需要将堆顶元素进行向下调整,调整过程如下:
  • 在父节点 3 和两个子节点 4、5中,5 最大,所以只需要将 3 和 5 交换位置
  • 最后,整颗树满足 大顶堆 的性质,停止调整

堆排序

  • 步骤
    • 将原始元素进行建堆操作
    • 将堆顶元素与堆尾元素调换位置,将该操作视为堆顶元素的弹出操作
    • 头部元素弹出之后,按照堆的性质,调整剩余的堆元素,也就是连续存储空间中的前 count - 1 个元素
    • 重复上述调换操作,直到堆的合法元素个数为 0,此时此前所有被弹出的元素组成的序列是有序的
  • 每次调换位置,使得堆顶部元素不属于堆的合法位置,但是该元素依然存在于这片连续存储区
  • 所以经过 n 次调换,当前连续存储区的所有元素是有序排列的

优先队列

  • 通常情况下,优先队列 是用 来实现的,即 优先队列 的一种实现方式
  • 优先队列 实际上也是,我们变换了思维方式后,对 的一种叫法
  • 堆 的两个操作 弹出堆顶元素尾部插入元素,与队列的两个操作 出队头部元素尾部入队元素 是极其相似的
  • 但是不同的是,堆每次弹出的元素,都是当前所有元素中的 最值
  • 如果将 最值 看做成 优先级,则每次弹出的元素都是当前所有元素中 优先级最高 的元素
  • 所以可以将 实际对应的 连续存储的序列,称为 优先队列
普通队列优先队列
尾部入队尾部可以插入
头部出队头部可以弹出
先进先出每次出队,队列中的最(大/小)值
可以用数组实现可以用数组实现,思维逻辑上看作成一个堆

堆适合维护集合最值

最后

  • 堆的分享就到这里了,欢迎大家在评论区里面讨论自己对堆的理解 👏。
  • 如果觉得文章写的不错的话,希望大家不要吝惜点赞,大家的鼓励是我分享的最大动力🥰