当前位置:   article > 正文

堆:数据结构与应用

堆:数据结构与应用

堆:数据结构与应用

在数据结构的世界中,堆作为一种特殊形式的完全二叉树,以其独特的属性和高效的操作脱颖而出。本文将深入探讨堆的概念、特点、基本操作,以及其在解决实际问题如Top-K问题和实现堆排序算法中的广泛应用。

堆的概念

堆是一种完全二叉树,其核心特征在于节点值的有序性。根据节点值与子节点值的关系,堆可分为两大类:

  • 小根堆(Min Heap):每个节点的值小于或等于其子节点的值。直观地看,小根堆的根节点总是整个树中的最小值,如下图所示。
       16 
     /   \ 
    14    10
   /  \  /  \ 
  8    7 9   3 
 / \ 
2   4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 大根堆(Max Heap):每个节点的值大于或等于其子节点的值。相应地,大根堆的根节点始终是树中的最大值,如下图所示。
       90 
     /    \ 
    85     75
   /  \   /  \ 
  70  65 50   45 
 / \ 
40  35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

尽管堆在逻辑上表现为完全二叉树,但在物理实现上通常采用数组存储。这样,每个节点的位置与其在数组中的索引紧密关联,便于快速访问父节点、子节点以及进行堆的操作。

堆的特点

  1. 堆顶特性:小根堆的根节点始终是最小值,大根堆的根节点始终是最大值。这一特性使得堆在处理诸如寻找最小或最大元素等问题时,无需遍历整个数据集,只需查看根节点即可。

  2. 局部有序:尽管堆整体可能并不严格有序,但每个节点与其直接子节点之间保持特定的大小关系(小根堆中父节点小于等于子节点,大根堆中父节点大于等于子节点)。这种局部有序性赋予堆结构良好的稳定性,有利于实现高效的操作。

  3. 高效操作:堆支持插入、删除、调整等操作,且这些操作的时间复杂度均为 ( O(\log N) ),其中 ( N ) 是堆中节点的数量。堆的高效性源于其完全二叉树的特性,使得操作主要集中在树的顶层,避免了对整个数据集的线性扫描。

堆的操作

调整操作:向上调整(AdjustUp)

当向堆中插入新节点或更新节点值后,可能导致堆的性质被破坏。此时,需执行向上调整操作,确保新节点(或更新后的节点)及其祖先节点满足堆的性质。具体做法是从新节点开始,与父节点比较,若违反堆性质则交换两者位置,然后继续与新的父节点比较,直至到达根节点或满足堆性质为止。如下图所示为向上调整的一个示例。

删除操作:Pop

堆顶元素(最小值或最大值)往往是最感兴趣的,故堆提供了高效的删除操作。为了避免直接删除堆顶后导致堆结构破坏,通常采用以下策略:

  • 用堆尾元素替换堆顶元素。
  • 删除替换后堆顶元素(原堆尾),此时堆尾已空。
  • 对新的堆顶元素执行向下调整操作,恢复堆的性质。
调整操作:向下调整(AdjustDown)

向下调整主要用于删除操作后恢复堆的性质,也用于新建堆时对整个数据集进行初始化。其过程如下:

  • 假定当前节点的左右子树已满足堆的性质(小根堆或大根堆)。
  • 比较当前节点与左右子节点,找出较小(大根堆时为较大)者。
  • 若当前节点不是所选子节点中最小(大根堆时为最大)的,交换当前节点与选定子节点的值。
  • 在交换后子节点的位置继续执行向下调整,直至达到叶子节点或满足堆性质。

堆排序

堆排序是一种利用堆结构实现的排序算法,其核心思想是将待排序数组转化为堆,然后通过反复删除堆顶元素(最小或最大值),并重新调整堆,最终实现数组的整体有序。堆排序有两种常见实现方式:

方法一:先建堆后排序

  1. 步骤:首先创建一个空堆,然后将待排序数组中的元素逐个插入堆中,每次插入后执行向上调整操作,确保堆性质得以维持。一旦所有元素都被插入堆中,堆即构建完成。接着,重复执行堆的删除操作(取走堆顶元素并调整),每次删除后将得到一个已排序元素。重复此过程直至堆为空,此时待排序数组已按要求排序。

  2. 缺点:这种方法需要额外的堆数据结构,增加了空间复杂度。而且,在插入元素过程中可能会产生一些未使用的空间,造成空间浪费。

方法二:原地建堆

  1. 步骤:直接将待排序数组视为完全二叉树,从数组第二个元素(索引为1的元素)开始,逐个进行向上调整,直至整个数组在原地完成建堆。接下来,通过反复执行堆的删除操作(取走堆顶元素并调整),实现数组的排序。

  2. 排序策略

    • 降序排序:构建一个小根堆,每次取走堆顶最小值,剩余元素重新调整为小根堆,重复此过程直至排序完成。
    • 升序排序:构建一个大根堆,每次取走堆顶最大值,剩余元素重新调整为大根堆,同样重复直至排序完成。
  3. 时间复杂度:无论是先建堆后排序还是原地建堆,堆排序的时间复杂度均为 ( O(N \log N) ),其中 ( N ) 为待排序数组的长度。这种时间复杂度使得堆排序成为处理大规模数据时的高效选择。

综上所述,堆作为基于完全二叉树的高效数据结构,凭借其堆顶特性、局部有序性和高效操作,广泛应用于解决Top-K问题、优先级队列管理、事件调度等多种实际场景。同时,堆排序算法利用堆的特性实现了稳定、快速的排序,是数据结构与算法领域不可或缺的工具。通过理解堆的内在机制及其操作原理,开发者能够更好地驾驭这一强大工具,提升程序性能和解决问题的能力。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号