数据结构:堆

堆(Heap) 是一个非常重要的数据结构。

一、堆是什么?

堆是一种特殊的完全二叉树,它满足以下性质:

  • 结构性 :它是一棵完全二叉树 。这意味着除了最后一层,其他层都是满的,并且最后一层的节点都尽可能靠左排列。这个特性使得堆可以非常高效地用数组来存储,而不需要像普通树一样使用链式结构。

  • 有序性:堆中任意一个节点的值都必须满足与它的子节点的关系。根据这个关系,堆主要分为两种:

    1. 最大堆(大顶堆)父节点的值 >= 其所有子节点 的值。因此,堆顶(根节点)是整个堆中的最大元素

    2. 最小堆(小顶堆)父节点的值 <= 其所有子节点 的值。因此,堆顶(根节点)是整个堆中的最小元素

注意:堆不保证兄弟节点之间(比如左孩子和右孩子)有任何特定的大小关系,它只维护"父子"层级间的大小关系。

二、堆的存储:数组表示法

由于堆是一棵完全二叉树,我们可以使用一个一维数组来存储它,这样既节省空间,又可以利用索引快速定位父节点和子节点。

对于数组中索引为 i 的节点(通常从0开始索引):

  • 父节点 的索引:parent(i) = (i - 1) / 2 (向下取整)

  • 左孩子 的索引:left_child(i) = 2 * i + 1

  • 右孩子 的索引:right_child(i) = 2 * i + 2

示例(最大堆)

复制代码
         [90]
         /  \
      [80]  [70]
      /  \   /
   [60] [50][40]

对应的数组为:[90, 80, 70, 60, 50, 40]

三、堆的核心操作

为了维护堆的性质,有两个最基础、最重要的操作:上浮(Sift Up / Swim)下沉(Sift Down / Sink)

1. 插入元素(Insert)

操作步骤:

  1. 将新元素追加到数组的末尾(即完全二叉树的最后一个位置)。

  2. 对这个新元素进行 "上浮" 操作,以恢复堆的性质。

    • 上浮:将新节点与其父节点比较。

      • 最大堆 中,如果它大于其父节点,就交换它们的位置。

      • 最小堆 中,如果它小于其父节点,就交换它们的位置。

    • 重复这个过程,直到它不再大于(或小于)其父节点,或者到达堆顶。

时间复杂度:O(log n),因为树的深度是 log n。

2. 删除堆顶元素(Extract Max / Extract Min)

这是堆最常用的操作,用于获取并移除最大(或最小)元素。

操作步骤:

  1. 取出堆顶元素(这就是我们要的结果)。

  2. 将堆中最后一个元素移到堆顶。

  3. 对新的堆顶元素进行 "下沉" 操作,以恢复堆的性质。

    • 下沉 :将当前节点与其较大的那个子节点 (对于最大堆)或较小的那个子节点(对于最小堆)进行比较。

      • 最大堆 中,如果它小于其子节点中较大的那个,就交换它们的位置。

      • 最小堆 中,如果它大于其子节点中较小的那个,就交换它们的位置。

    • 重复这个过程,直到它大于(或小于)其所有子节点,或者成为叶子节点。

时间复杂度:O(log n)。

四、堆的构建(Heapify)

如何将一个无序的数组构建成一个堆?

一个直观的方法是逐个插入,时间复杂度为 O(n log n)。但有一个更高效的方法,自底向上堆化

算法步骤

  1. 最后一个非叶子节点 开始(索引为 n/2 - 1)。

  2. 向前遍历,对每一个遇到的节点都执行一次 "下沉" 操作。

时间复杂度:O(n)。这个结论有点反直觉,但数学上可以证明。直观理解是,大部分节点都很矮,不需要下沉很多层。

五、堆的应用

堆是一个功能强大且应用极其广泛的数据结构。

  1. 堆排序(Heap Sort)

    • 思路:先将待排序数组构建成一个最大堆。此时,最大元素在堆顶。将其与堆的最后一个元素交换,然后堆的大小减一,并对新的堆顶执行"下沉"。重复此过程,直到堆中只剩一个元素。

    • 时间复杂度 :O(n log n),并且是原地排序

  2. 优先队列(Priority Queue)

    • 这是堆最典型的应用。优先队列不再遵循"先入先出"的原则,而是按照优先级出队。优先级最高的元素先出队。

    • 堆可以完美地实现优先队列的所有核心操作:

      • 插入(enqueue) -> 堆的插入操作。

      • 取出最高优先级元素(dequeue) -> 堆的删除堆顶操作。

      • 查看最高优先级元素(peek) -> 查看堆顶元素。

  3. 求 Top K 问题

    • 求最大的K个元素 :维护一个大小为 K 的最小堆。遍历数据,如果当前元素比堆顶大,就替换堆顶并下沉。最终堆里的就是最大的K个元素。

    • 求最小的K个元素 :维护一个大小为 K 的最大堆。遍历数据,如果当前元素比堆顶小,就替换堆顶并下沉。最终堆里的就是最小的K个元素。

    • 时间复杂度:O(n log K),比全排序 O(n log n) 更高效。

  4. 图算法

    • Dijkstra 算法:用于寻找带权图中的最短路径。它使用优先队列(最小堆)来高效地选择当前距离最短的节点。

    • Prim 算法:用于构建最小生成树。同样使用优先队列来选择当前连接生成树的最小权重的边。

六、总结:堆的优缺点

优点 缺点
获取最大/最小元素极快(O(1)) 除了堆顶,查找其他元素很慢(O(n)),不支持快速查找
插入和删除堆顶元素高效(O(log n)) 堆中元素没有完全的排序,只有偏序关系
可以高效地进行堆排序和构建
是实现优先队列的最佳数据结构

总而言之,堆是一种在需要快速访问最大或最小元素 以及处理动态优先级的场景下无可替代的高效数据结构。

相关推荐
得物技术2 小时前
从数字到版面:得物数据产品里数字格式化的那些事
前端·数据结构·数据分析
百***86462 小时前
Spring Boot应用关闭分析
java·spring boot·后端
tanxiaomi2 小时前
Spring、Spring MVC 和 Spring Boot ,mybatis 相关面试题
java·开发语言·mybatis
弥巷2 小时前
【Android】常见滑动冲突场景及解决方案
android·java
散峰而望2 小时前
C++数组(一)(算法竞赛)
c语言·开发语言·c++·算法·github
间彧2 小时前
GraalVM 深度解析:下一代 Java 技术平台
java
自然常数e2 小时前
深入理解指针(1)
c语言·算法·visual studio
合作小小程序员小小店2 小时前
网页开发,在线%旧版本旅游管理%系统,基于eclipse,html,css,jquery,servlet,jsp,mysql数据库
java·数据库·servlet·eclipse·jdk·旅游·jsp
WWZZ20253 小时前
快速上手大模型:深度学习13(文本预处理、语言模型、RNN、GRU、LSTM、seq2seq)
人工智能·深度学习·算法·语言模型·自然语言处理·大模型·具身智能