揭秘堆排序:大顶堆和小顶堆的高效利用

堆排序、大顶堆和小顶堆原理介绍


引言

堆(Heap)是一种特殊的完全二叉树结构,广泛应用于计算机科学中,特别是在排序算法和优先队列的实现中。堆可以分为大顶堆(Max Heap)和小顶堆(Min Heap)。本文将详细介绍堆排序的原理,以及大顶堆和小顶堆的概念、特性,并通过具体例子更好地理解这些概念。

在这里插入图片描述


1. 堆的基本概念

1.1 定义

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

  • 完全二叉树:除了最后一层外,其他每一层都被完全填充,并且所有节点都尽可能靠左排列。
  • 堆序性:每个节点与其子节点之间满足特定的关系,具体取决于堆的类型(大顶堆或小顶堆)。

1.2 大顶堆(Max Heap)

在大顶堆中,每个节点的值都不小于其子节点的值。因此,堆顶(根节点)始终是堆中的最大元素。

性质

对于任意节点 iii,如果它有左子节点 2i+12i+12i+1 和右子节点 2i+22i+22i+2,则满足:

  • A[i]≥A[2i+1]A[i] \geq A[2i+1]A[i]A[2i+1]
  • A[i]≥A[2i+2]A[i] \geq A[2i+2]A[i]A[2i+2]
示例

考虑一个数组 [4, 10, 3, 5, 1],我们将其构建成一个大顶堆:

  1. 初始数组[4, 10, 3, 5, 1]
  2. 构建大顶堆
    • 从最后一个非叶子节点开始调整(即索引为 ⌊n2⌋−1\lfloor \frac{n}{2} \rfloor - 12n1 的节点),这里是索引 1。
    • 调整索引 1 的节点 10,它已经满足大顶堆的条件,不需要调整。
    • 调整索引 0 的节点 4,它不满足大顶堆的条件,需要与较大的子节点 10 交换。

最终构建的大顶堆如下:

      10
     /  \
    5    3
   / \
  4   1

对应的数组表示为 [10, 5, 3, 4, 1]

1.3 小顶堆(Min Heap)

在小顶堆中,每个节点的值都不大于其子节点的值。因此,堆顶(根节点)始终是堆中的最小元素。

性质

对于任意节点 iii,如果它有左子节点 2i+12i+12i+1 和右子节点 2i+22i+22i+2,则满足:

  • A[i]≤A[2i+1]A[i] \leq A[2i+1]A[i]A[2i+1]
  • A[i]≤A[2i+2]A[i] \leq A[2i+2]A[i]A[2i+2]
示例

考虑相同的数组 [4, 10, 3, 5, 1],我们将其构建成一个小顶堆:

  1. 初始数组[4, 10, 3, 5, 1]
  2. 构建小顶堆
    • 从最后一个非叶子节点开始调整(即索引为 ⌊n2⌋−1\lfloor \frac{n}{2} \rfloor - 12n1 的节点),这里是索引 1。
    • 调整索引 1 的节点 10,它不满足小顶堆的条件,需要与较小的子节点 1 交换。
    • 调整索引 0 的节点 4,它不满足小顶堆的条件,需要与较小的子节点 1 交换。

最终构建的小顶堆如下:

      1
     / \
    4   3
   / \
  10  5

对应的数组表示为 [1, 4, 3, 10, 5]


2. 堆排序原理

2.1 步骤

  1. 建堆:将输入数组构建成一个大顶堆或小顶堆。
  2. 排序
    • 大顶堆排序:每次取出堆顶的最大元素,将其与堆的最后一个元素交换,然后重新调整剩余元素为大顶堆,重复此过程直到所有元素都被排序。
    • 小顶堆排序:每次取出堆顶的最小元素,将其与堆的最后一个元素交换,然后重新调整剩余元素为小顶堆,重复此过程直到所有元素都被排序。

2.2 图示示例

大顶堆排序示例

考虑一个无序数组 [4, 10, 3, 5, 1],我们通过大顶堆进行排序:

  1. 建堆:首先将数组构建成一个大顶堆。

    Initial array: [4, 10, 3, 5, 1]
    After building Max Heap:
    
          10
         /  \
        5    3
       / \
      4   1
    
  2. 排序

    • 第一次交换:将堆顶 10 与最后一个元素 1 交换,得到 [1, 5, 3, 4, 10],然后调整剩余部分 [1, 5, 3, 4] 为大顶堆。
    • 第二次交换:将新的堆顶 5 与最后一个元素 4 交换,得到 [1, 4, 3, 5, 10],然后调整剩余部分 [1, 4, 3] 为大顶堆。
    • 第三次交换:将新的堆顶 4 与最后一个元素 3 交换,得到 [1, 3, 4, 5, 10],然后调整剩余部分 [1, 3] 为大顶堆。
    • 第四次交换:将新的堆顶 3 与最后一个元素 1 交换,得到 [1, 3, 4, 5, 10],此时堆已为空,排序完成。

最终排序结果:

Sorted array: [1, 3, 4, 5, 10]
小顶堆排序示例

类似地,我们可以使用小顶堆对同一个数组进行排序。步骤相同,只是每次取出堆顶的最小元素进行交换。


3. 堆操作实例图示

为了更直观地理解大顶堆和小顶堆的操作过程,我们将通过图示展示插入和删除操作的具体步骤。

3.1 大顶堆操作实例

插入操作

假设我们有一个大顶堆 [10, 8, 3, 4, 1, 5],现在要插入元素 8

  1. 插入新元素:将 8 添加到堆的末尾。

          10
         /  \
        8    3
       / \   /
      4   1 5
             \
              8
    
  2. 上浮调整:比较 8 和它的父节点 3,发现 8 > 3,交换它们的位置。

          10
         /  \
        8    8
       / \   /
      4   1 5
             \
              3
    
  3. 继续上浮调整:比较 8 和它的新父节点 8,发现 8 == 8,停止调整。

最终结果:

         10
        /  \
       8    8
      / \   /
     4   1 5
            \
             3
删除操作

假设我们有一个大顶堆 [10, 8, 3, 4, 1, 5],现在要删除堆顶元素 10

  1. 删除堆顶元素:将堆顶 10 与堆的最后一个元素 5 交换,然后移除最后一个元素 10

          5
         / \
        8   3
       / \
      4   1
    
  2. 下沉调整:比较 5 和它的子节点 83,发现 8 > 5,交换它们的位置。

          8
         / \
        5   3
       / \
      4   1
    
  3. 继续下沉调整:比较 5 和它的新子节点 41,发现 5 已经满足大顶堆的条件,调整结束。

最终结果:

         8
        / \
       5   3
      / \
     4   1

3.2 小顶堆操作实例

插入操作

假设我们有一个小顶堆 [1, 2, 3, 10, 5, 4],现在要插入元素 2

  1. 插入新元素:将 2 添加到堆的末尾。

          1
         / \
        2   3
       / \
      10  5
           \
            4
               \
                2
    
  2. 上浮调整:比较 2 和它的父节点 3,发现 2 < 3,交换它们的位置。

          1
         / \
        2   2
       / \
      10  5
           \
            4
               \
                3
    
  3. 继续上浮调整:比较 2 和它的新父节点 2,发现 2 == 2,停止调整。

最终结果:

         1
        / \
       2   2
      / \
     10  5
          \
           4
              \
               3
删除操作

假设我们有一个小顶堆 [1, 2, 3, 10, 5, 4],现在要删除堆顶元素 1

  1. 删除堆顶元素:将堆顶 1 与堆的最后一个元素 4 交换,然后移除最后一个元素 1

          4
         / \
        2   3
       / \
      10  5
    
  2. 下沉调整:比较 4 和它的子节点 23,发现 2 < 4,交换它们的位置。

          2
         / \
        4   3
       / \
      10  5
    
  3. 继续下沉调整:比较 4 和它的新子节点 105,发现 4 已经满足小顶堆的条件,调整结束。

最终结果:

      2
     / \
    4   3
   / \
  10  5

4. 时间复杂度分析

  • 建堆时间复杂度O(n)O(n)O(n),其中 nnn 是数组长度。
  • 插入/删除操作时间复杂度:单次操作的时间复杂度为 O(log⁡n)O(\log n)O(logn),因为每次调整最多需要遍历树的高度。
  • 堆排序时间复杂度:总体时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn),因为每次删除堆顶元素后需要调整堆。

5. 应用场景

5.1 堆排序

堆排序适用于需要稳定排序性能的场景,尤其是在内存有限的情况下,因为它只需要常数级别的额外空间。

5.2 优先队列

堆常用于实现优先队列,特别是大顶堆用于最大优先队列,小顶堆用于最小优先队列。

5.3 数据流处理

堆可用于实时数据流处理中的Top K问题,例如找到前K个最大的元素或最小的元素。


6. 总结

堆是一种高效的完全二叉树结构,广泛应用于排序和优先队列的实现中。大顶堆和小顶堆分别用于维护最大值和最小值的优先级。堆排序通过建堆和反复调整堆顶元素,实现了稳定的 O(nlog⁡n)O(n \log n)O(nlogn) 排序性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进一步有进一步的欢喜

您的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值