文章目录
- 堆:数据结构与应用
- 堆的概念
- 堆的特点
- 堆的操作
- 调整操作:向上调整(AdjustUp)
- 删除操作:Pop
- 调整操作:向下调整(AdjustDown)
- 堆排序
- 方法一:先建堆后排序
- 方法二:原地建堆
堆:数据结构与应用
在数据结构的世界中,堆作为一种特殊形式的完全二叉树,以其独特的属性和高效的操作脱颖而出。本文将深入探讨堆的概念、特点、基本操作,以及其在解决实际问题如Top-K问题和实现堆排序算法中的广泛应用。
堆的概念
堆是一种完全二叉树,其核心特征在于节点值的有序性。根据节点值与子节点值的关系,堆可分为两大类:
- 小根堆(Min Heap):每个节点的值小于或等于其子节点的值。直观地看,小根堆的根节点总是整个树中的最小值,如下图所示。
16 / \ 14 10/ \ / \ 8 7 9 3 / \
2 4
- 大根堆(Max Heap):每个节点的值大于或等于其子节点的值。相应地,大根堆的根节点始终是树中的最大值,如下图所示。
90 / \ 85 75/ \ / \ 70 65 50 45 / \
40 35
尽管堆在逻辑上表现为完全二叉树,但在物理实现上通常采用数组存储。这样,每个节点的位置与其在数组中的索引紧密关联,便于快速访问父节点、子节点以及进行堆的操作。
堆的特点
-
堆顶特性:小根堆的根节点始终是最小值,大根堆的根节点始终是最大值。这一特性使得堆在处理诸如寻找最小或最大元素等问题时,无需遍历整个数据集,只需查看根节点即可。
-
局部有序:尽管堆整体可能并不严格有序,但每个节点与其直接子节点之间保持特定的大小关系(小根堆中父节点小于等于子节点,大根堆中父节点大于等于子节点)。这种局部有序性赋予堆结构良好的稳定性,有利于实现高效的操作。
-
高效操作:堆支持插入、删除、调整等操作,且这些操作的时间复杂度均为 ( O(\log N) ),其中 ( N ) 是堆中节点的数量。堆的高效性源于其完全二叉树的特性,使得操作主要集中在树的顶层,避免了对整个数据集的线性扫描。
堆的操作
调整操作:向上调整(AdjustUp)
当向堆中插入新节点或更新节点值后,可能导致堆的性质被破坏。此时,需执行向上调整操作,确保新节点(或更新后的节点)及其祖先节点满足堆的性质。具体做法是从新节点开始,与父节点比较,若违反堆性质则交换两者位置,然后继续与新的父节点比较,直至到达根节点或满足堆性质为止。如下图所示为向上调整的一个示例。
删除操作:Pop
堆顶元素(最小值或最大值)往往是最感兴趣的,故堆提供了高效的删除操作。为了避免直接删除堆顶后导致堆结构破坏,通常采用以下策略:
- 用堆尾元素替换堆顶元素。
- 删除替换后堆顶元素(原堆尾),此时堆尾已空。
- 对新的堆顶元素执行向下调整操作,恢复堆的性质。
调整操作:向下调整(AdjustDown)
向下调整主要用于删除操作后恢复堆的性质,也用于新建堆时对整个数据集进行初始化。其过程如下:
- 假定当前节点的左右子树已满足堆的性质(小根堆或大根堆)。
- 比较当前节点与左右子节点,找出较小(大根堆时为较大)者。
- 若当前节点不是所选子节点中最小(大根堆时为最大)的,交换当前节点与选定子节点的值。
- 在交换后子节点的位置继续执行向下调整,直至达到叶子节点或满足堆性质。
堆排序
堆排序是一种利用堆结构实现的排序算法,其核心思想是将待排序数组转化为堆,然后通过反复删除堆顶元素(最小或最大值),并重新调整堆,最终实现数组的整体有序。堆排序有两种常见实现方式:
方法一:先建堆后排序
-
步骤:首先创建一个空堆,然后将待排序数组中的元素逐个插入堆中,每次插入后执行向上调整操作,确保堆性质得以维持。一旦所有元素都被插入堆中,堆即构建完成。接着,重复执行堆的删除操作(取走堆顶元素并调整),每次删除后将得到一个已排序元素。重复此过程直至堆为空,此时待排序数组已按要求排序。
-
缺点:这种方法需要额外的堆数据结构,增加了空间复杂度。而且,在插入元素过程中可能会产生一些未使用的空间,造成空间浪费。
方法二:原地建堆
-
步骤:直接将待排序数组视为完全二叉树,从数组第二个元素(索引为1的元素)开始,逐个进行向上调整,直至整个数组在原地完成建堆。接下来,通过反复执行堆的删除操作(取走堆顶元素并调整),实现数组的排序。
-
排序策略:
- 降序排序:构建一个小根堆,每次取走堆顶最小值,剩余元素重新调整为小根堆,重复此过程直至排序完成。
- 升序排序:构建一个大根堆,每次取走堆顶最大值,剩余元素重新调整为大根堆,同样重复直至排序完成。
-
时间复杂度:无论是先建堆后排序还是原地建堆,堆排序的时间复杂度均为 ( O(N \log N) ),其中 ( N ) 为待排序数组的长度。这种时间复杂度使得堆排序成为处理大规模数据时的高效选择。
综上所述,堆作为基于完全二叉树的高效数据结构,凭借其堆顶特性、局部有序性和高效操作,广泛应用于解决Top-K问题、优先级队列管理、事件调度等多种实际场景。同时,堆排序算法利用堆的特性实现了稳定、快速的排序,是数据结构与算法领域不可或缺的工具。通过理解堆的内在机制及其操作原理,开发者能够更好地驾驭这一强大工具,提升程序性能和解决问题的能力。