数据结构-堆

1.堆的分类

堆(Heap)是一种特殊的数据结构,通常用于实现优先队列、堆排序等算法。堆分为最大堆和最小堆两种形式。

  • 最大堆(Max Heap):

    • 在最大堆中,每个节点的值都大于或等于其子节点的值。
    • 最大堆的根节点是堆中的最大元素。
    • 通常用数组实现最大堆,数组中的元素按照堆的结构排列。
  • 最小堆(Min Heap):

    • 在最小堆中,每个节点的值都小于或等于其子节点的值。
    • 最小堆的根节点是堆中的最小元素。
    • 类似最大堆,通常用数组实现,数组中的元素按照堆的结构排列。

2.堆的性质:

  • 全二叉树结构: 堆通常是一棵完全二叉树,除了最底层,其他层的节点都是满的,而且最底层的节点尽量靠左排列。

  • 父节点与子节点的关系:

    • 最大堆: 对于任意节点 i,其值大于或等于其左右子节点的值。
    • 最小堆: 对于任意节点 i,其值小于或等于其左右子节点的值。
  • 数组表示: 堆通常通过数组来表示,其中父节点和子节点之间的关系可以通过数组的索引关系直接映射。在最大堆中,父节点的索引 i 对应的左子节点索引为 2i+1,右子节点索引为 2i+2;在最小堆中,左右子节点的关系略有不同。

  • 堆序性质:

    • 最大堆: 对于每个节点 i,父节点的值大于或等于其子节点的值。
    • 最小堆: 对于每个节点 i,父节点的值小于或等于其子节点的值。
  • 高效的插入和删除操作: 堆结构支持高效的插入和删除操作。插入操作通常是在堆的末尾添加元素,然后通过一系列的向上调整(上浮)操作保持堆的性质。删除操作通常是删除堆顶元素,将堆尾元素放到堆顶,然后通过一系列的向下调整(下沉)操作保持堆的性质。

  • 堆化操作: 堆化是指将一个无序序列构建成堆的过程。堆化分为自上而下和自下而上两种方式,分别用于构建最小堆和最大堆。

    • 自下而上堆化(Bottom-Up Heapify): 从堆的最后一个非叶子节点开始,对每个节点执行向下调整(下沉)操作,保持堆的性质。

    • 自上而下堆化(Top-Down Heapify): 从堆的根节点开始,对每个节点执行向上调整(上浮)操作,保持堆的性质。

3.稳定性: 

堆排序中的堆(通常是最大堆)是一种不稳定的排序算法。稳定性指的是当存在相同关键字的元素时,排序前后它们的相对位置是否保持不变。

在堆排序中,由于在堆的构建和调整过程中对元素的比较和交换可能导致相同关键字的元素交换位置,因此堆排序是不稳定的排序算法。这意味着如果有两个相同关键字的元素,它们在排序后的相对顺序可能会改变。

与之不同的是,某些排序算法,如稳定排序算法中的冒泡排序、插入排序和归并排序,保持了相同关键字元素的相对顺序,因此它们是稳定的排序算法。

4.堆与其他数据结构的比较:

  • 堆 vs 数组: 堆提供了高效的插入和删除操作,而数组支持随机访问。选择使用堆还是数组取决于具体的应用场景和操作需求。

  • 堆 vs 二叉搜索树(BST): 堆是一种特殊的二叉树,用于实现优先队列等场景。与二叉搜索树相比,堆更适合处理插入和删除等操作。

  • 堆 vs 队列: 堆和队列都可以用于实现优先队列,但堆在插入和删除的操作上更高效。

5.二叉堆(Binomial Heap):

二叉堆是一种常见的堆实现,分为最大堆和最小堆两种。它是一棵完全二叉树,可以使用数组来表示。在最大堆中,每个节点的值都大于或等于其子节点的值;在最小堆中,每个节点的值都小于或等于其子节点的值。

操作:

  • 插入(Insert): 在堆的末尾插入新元素,然后通过一系列的向上调整(上浮)操作保持堆的性质。

  • 删除(Delete): 通常删除堆顶元素,将堆尾元素放到堆顶,然后通过一系列的向下调整(下沉)操作保持堆的性质。

  • 堆化(Heapify): 将一个无序序列构建成堆的过程,可以是自上而下或自下而上的堆化。

时间复杂度:

  • 插入和删除操作的时间复杂度通常为 O(log n),其中 n 是堆中元素的个数。这是由于堆的高度为 log n。

二叉堆的优势:

  • 实现简单:二叉堆的实现相对简单,而且具有较小的常数因子,因此在实际应用中广泛使用。

  • 空间效率:使用数组实现的二叉堆相对紧凑,占用的空间较小。

缺点:

  • 不适用于动态删除操作:对于删除操作,通常是删除堆顶元素。如果需要删除堆中的任意元素,可能需要先将其标记为无效,然后再进行调整,这会引入额外的复杂性。

  • 不适用于大规模堆化:在大规模的无序序列堆化时,可能效率较低。在这种情况下,斐波那契堆等数据结构可能更为适用。

虽然二叉堆在某些方面存在一些局限性,但由于其简单和常用性,它仍然是一种重要的数据结构。在许多场景中,二叉堆都能够提供高效的性能。

6.斐波那契堆(Fibonacci Heap): 

斐波那契堆是一种特殊的堆数据结构,它是由多个树(称为树堆)组成的森林。斐波那契堆相对于二叉堆等传统堆实现,具有更低的均摊复杂度,特别适用于某些动态图算法和优先队列的场景。

斐波那契堆的主要特点:

  • 多树结构: 斐波那契堆由多个不相交的树组成,这些树可能是二项树或 Fibonacci 树。

  • 懒惰合并: 斐波那契堆具有懒惰合并(Lazy Merging)的特性。在删除最小元素时,仅将树的根节点删除,而不立即进行合并。合并会在后续的插入和合并操作中进行,以保持堆的性质。

  • 松弛性能: 斐波那契堆允许节点的度数(子节点数量)可以不受限制地增长,而不像二叉堆那样有固定的度数。这带来了更好的松弛性能。

  • 减小关键字操作: 斐波那契堆允许减小关键字的操作,而不需要执行删除和插入操作。这是在某些算法中非常有用的特性。

主要操作:

  • 插入(Insert): 将新元素插入堆中。

  • 合并(Union): 将两个斐波那契堆合并成一个。

  • 删除最小元素(Extract-Min): 删除并返回堆中具有最小关键字的元素。

  • 减小关键字(Decrease-Key): 将堆中某个节点的关键字减小,维护堆的性质。

  • 删除节点(Delete): 从堆中删除指定节点。

时间复杂度:

  • 插入、合并、减小关键字和删除节点等操作的均摊时间复杂度为常数级别 O(1)。

  • 删除最小元素的均摊时间复杂度为 O(log n),其中 n 是堆中元素的个数。

斐波那契堆的应用:

  • 图算法: 在某些图算法中,如 Prim 最小生成树算法和 Dijkstra 最短路径算法,斐波那契堆能够提供更好的性能。

  • 优先队列: 斐波那契堆常被用于实现高效的优先队列,特别是在需要频繁减小关键字的场景。

尽管斐波那契堆在某些方面提供了更好的性能,但由于其复杂度和实现难度相对较高,因此在一般情况下,对于不需要其特殊性能优势的应用,二叉堆等传统堆实现可能更为实用。

7.左偏树(Leftist Tree):

左偏树(Leftist Tree)是一种二叉堆的变体,具有较好的合并性能。它通过维护每个节点的零路径长(null path length)来实现。在左偏树中,左子树的零路径长始终大于或等于右子树的零路径长。

左偏树的性质:

  • 零路径长: 每个节点都有一个与之关联的零路径长,定义为从该节点到最远的叶节点的距离。左偏树中,每个节点的左子树的零路径长大于或等于其右子树的零路径长。

  • 合并性能: 左偏树的主要优势在于合并操作。两个左偏树可以在 O(log n) 的时间内合并成一个新的左偏树,其中 n 是树的节点数。这种合并性能使得左偏树特别适用于优先队列的实现。

基本操作:

  • 合并(Merge): 合并两个左偏树,将具有较小根节点的树作为合并后的树的根。通过递归实现,每次选择具有较小根节点的树进行合并,以确保左偏树性质。

  • 插入(Insert): 将新节点插入到左偏树中,可以将新节点视为一个左偏树,然后与原树进行合并。

  • 删除最小元素(DeleteMin): 删除左偏树的根节点,然后将左右子树合并。

左偏树的合并操作示意图:

  Tree1              Tree2               Merged Tree
   /  \               /  \                 / \
  A    B            C    D               A   B
 /    /            /      \             /     \
E    F            G        H           E       C
                                      /        \
                                     F          D

左偏树的应用:

  • 优先队列: 左偏树常被用于实现高效的优先队列,具有较好的插入和合并性能。

  • 图算法: 在某些图算法中,左偏树能够提供高效的节点合并操作,如 Kruskal 最小生成树算法。

左偏树相对于其他堆实现的优势主要在于其合并性能,适用于需要频繁进行合并操作的场景。然而,在某些情况下,由于实现较为复杂,使用其他堆实现可能更为简便。

斐波那契堆和左偏树相比于二项堆,更为复杂,但在特定应用场景中能够提供更好的性能。

8.应用场景:

  • 优先队列: 堆常被用于实现优先队列,其中每个元素都有一个关联的优先级。最小堆通常被用于实现最小优先队列,最大堆则用于最大优先队列。这使得能够高效地找到具有最高或最低优先级的元素。

  • 堆排序(Heap Sort): 堆排序是一种原地的、稳定的排序算法,利用最大堆或最小堆进行排序。堆排序的时间复杂度为 O(n log n),并且不需要额外的存储空间。

  • 图算法中的最短路径: 在图算法中,Dijkstra 最短路径算法通常使用最小堆来高效地找到当前距离最短的节点。这提供了在稀疏图中查找最短路径的一种有效方法。

  • 图算法中的最小生成树: Prim 和 Kruskal 最小生成树算法使用最小堆来选择具有最小权重的边,从而生成图的最小生成树。

  • 任务调度: 堆可以用于任务调度,根据任务的优先级动态调整任务的执行顺序。

  • 内存管理: 操作系统的内存管理中,堆通常被用于动态内存分配,例如 malloc 和 free 操作。

  • 模拟退火算法: 在某些优化问题中,模拟退火算法使用堆来管理和调整状态的优先级,以更有可能找到全局最优解。

  • 哈夫曼编码: 堆结构也被用于构建哈夫曼编码树,一种用于数据压缩的算法。

这些应用场景突显了堆在许多计算机科学领域中的实用性。其高效的插入和删除操作,以及对优先级的灵活管理,使得堆成为处理有序数据集合中的重要工具。

  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
十大经典排序算法是指在计算机科学中常用的排序算法,它们分别是: 1. 冒泡排序(Bubble Sort):重复地比较相邻的两个元素,将较大的元素逐渐向右移动。 2. 选择排序(Selection Sort):每次从未排序的部分选择最小(或最大)的元素,并放在已排序的部分的末尾。 3. 插入排序(Insertion Sort):将未排序的元素逐个插入到已排序的部分中的正确位置。 4. 希尔排序(Shell Sort):将待排序的数组按照一定步长进行分组,对每组进行插入排序,逐渐减小步长。 5. 归并排序(Merge Sort):将待排序的数组递归地分成两半,对每一半进行排序,然后合并两个有序数组。 6. 快速排序(Quick Sort):选择一个基准元素,将数组划分为两部分,左边部分都小于基准,右边部分都大于基准,递归地对两部分进行排序。 7. 堆排序(Heap Sort):将待排序的数组构建成一个最大堆(或最小堆),然后依次取出堆顶元素并调整堆结构。 8. 计数排序(Counting Sort):统计数组中每个元素出现的次数,然后根据统计结果对元素进行排序。 9. 桶排序(Bucket Sort):将待排序的数组划分为多个桶,对每个桶中的元素进行排序,最后将桶中的元素按顺序合并。 10. 基数排序(Radix Sort):按照元素的位数,将待排序的数组从低位到高位进行排序。 以上是十大经典排序算法,每种算法都有其适用的场景和性能特点,选择合适的排序算法可以提高程序的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值