大小顶堆:原理、实现与应用
堆(Heap)是一种基于完全二叉树的数据结构,核心特性是 "父节点与子节点的优先级关系固定"。根据优先级规则的不同,可分为大顶堆 和小顶堆,二者是实现 "Top K 问题""优先级队列" 等场景的核心工具。
一、核心定义:大顶堆 vs 小顶堆
堆的本质是 "完全二叉树"(除最后一层外,每一层节点数均满;最后一层节点从左到右排列),但需满足以下优先级规则:
类型 | 核心规则(父节点 vs 子节点) | 关键特性 |
---|---|---|
大顶堆 | 父节点的值 大于等于 其左右子节点的值 | 根节点是整个堆的 最大值 |
小顶堆 | 父节点的值 小于等于 其左右子节点的值 | 根节点是整个堆的 最小值 |
示例(数组存储堆)
完全二叉树可通过数组高效存储(无需指针,通过索引计算父子关系),假设数组索引从 0 开始:
- 父节点索引 i 的左子节点:2*i + 1
- 父节点索引 i 的右子节点:2*i + 2
- 子节点索引 j 的父节点:(j - 1) // 2
以数组 [5,3,4,1,2] 为例:
- 对应大顶堆的完全二叉树:根为 5,左子树 3(子节点 1、2),右子树 4(无后代),满足 "父≥子"。
- 若数组为 [1,2,4,5,3],则对应小顶堆,根为 1(最小值),满足 "父≤子"。
二、堆的核心操作(以大顶堆为例)
堆的关键操作需保证 "操作后仍满足堆的优先级规则",核心包括插入 和删除堆顶 ,时间复杂度均为 O(log n) (n 为堆的大小,操作需沿树的高度调整)。
1. 插入操作(Heap Insert)
新元素先加入堆的末尾(数组最后一位),再通过 "向上调整(Heapify Up)" 恢复堆结构:
- 将新元素放到数组末尾(完全二叉树的最后一个位置)。
- 比较该元素与父节点:若为大顶堆且新元素 > 父节点,则交换二者。
- 重复步骤 2,直到该元素≤父节点(或成为根节点),堆结构恢复。
2. 删除堆顶操作(Extract Max/Min)
堆顶(根节点,最大 / 最小值)删除后,需用末尾元素补位,再通过 "向下调整(Heapify Down)" 恢复堆结构:
- 取出堆顶元素(根节点),将数组末尾元素移到根节点位置。
- 比较该元素与左右子节点:若为大顶堆,找出左右子节点中的最大值,若该元素 < 最大值,则交换二者。
- 重复步骤 2,直到该元素≥所有子节点(或成为叶子节点),堆结构恢复。
三、经典应用场景
大小顶堆的核心价值是 "快速获取极值",因此广泛用于需优先处理 "最大 / 最小" 元素的场景:
场景 | 堆的选择 | 核心逻辑 |
---|---|---|
Top K 问题 | 小顶堆(求 Top K 大)大顶堆(求 Top K 小) | 求 "前 K 个最大元素":用容量为 K 的小顶堆,遍历元素时,比堆顶大则替换堆顶,最终堆内即 Top K。 |
优先级队列 | 大顶堆 / 小顶堆 | 按优先级处理任务:如 "最高优先级任务先执行" 用大顶堆(优先级为键),"时间最早任务先执行" 用小顶堆(时间为键)。 |
中位数查找 | 大顶堆 + 小顶堆 | 大顶堆存 "左半部分较小元素"(堆顶为左半最大,即中位数候选),小顶堆存 "右半部分较大元素",动态维持两堆大小平衡。 |
堆排序 | 大顶堆 | 1. 将数组构建为大顶堆;2. 反复删除堆顶(放到数组末尾)并调整堆,最终数组升序排列。 |
四、Python 实现(以小顶堆为例)
Python 标准库 heapq 仅提供小顶堆的实现,若需大顶堆,可通过 "存入负数" 间接实现(取元素时再转负)。
1. 小顶堆基础操作(heapq)
ini
import heapq
# 1. 初始化堆(将列表转化为小顶堆,in-place操作)
heap = [3, 1, 4, 2]
heapq.heapify(heap) # 转化后:[1, 2, 4, 3](满足小顶堆规则)
# 2. 插入元素(自动向上调整)
heapq.heappush(heap, 0) # 插入后:[0, 1, 4, 3, 2]
# 3. 删除并返回堆顶(最小元素,自动向下调整)
min_val = heapq.heappop(heap) # 返回0,堆变为[1, 2, 4, 3]
# 4. 查看堆顶(不删除)
print(heap[0]) # 输出1(当前堆顶)
2. 大顶堆实现(间接方式)
通过 "存储元素的负数",利用小顶堆的逻辑实现大顶堆效果:
ini
import heapq
# 初始化大顶堆(存入负数)
max_heap = [-3, -1, -4, -2]
heapq.heapify(max_heap) # 转化后:[-4, -2, -3, -1](小顶堆的"小"对应原数的"大")
# 插入元素(存负数)
heapq.heappush(max_heap, -0) # 插入后:[-4, -2, -3, -1, 0]
# 删除并返回堆顶(原数的最大值,取负后返回)
max_val = -heapq.heappop(max_heap) # 返回4,堆变为[-3, -2, 0, -1]
# 查看堆顶(原数的最大值)
print(-max_heap[0]) # 输出3
五、关键总结
- 结构本质:堆是 "完全二叉树" 的数组实现,核心是 "父节点与子节点的固定优先级关系"。
- 操作效率:插入、删除堆顶的时间复杂度均为 O (log n),远优于 "直接排序后取极值" 的 O (n log n)。
- 场景适配:求极值、Top K、优先级队列用堆;Python 需注意 heapq 仅支持小顶堆,大顶堆需间接实现。
- 局限性:堆仅能高效获取 "堆顶极值",若需查找任意元素,效率为 O (n)(需遍历数组),此时需结合哈希表等结构优化。