一、堆的定义
- 堆是一种完全二叉树 结构,同时满足堆的性质,即父节点和子节点之间存在固定的大小关系。它主要用于高效地获取和维护极值(最大值或最小值),常见的堆有大顶堆和小顶堆两种。
- 资料:
https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf
二、堆的核心性质
-
结构性质
- 堆是一棵完全二叉树,即除了最后一层,其他层的节点数都达到最大值,且最后一层的节点都靠左排列。
- 通常用数组来存储堆,对于数组中索引为
i的节点(从0开始),其左子节点索引为2i+1,右子节点索引为2i+2,父节点索引为(i-1)//2。
-
数值性质
- 大顶堆 :每个父节点的值都大于或等于其左右子节点的值,堆顶(根节点)是整个堆的最大值。
- 小顶堆 :每个父节点的值都小于或等于其左右子节点的值,堆顶(根节点)是整个堆的最小值。
三、堆的核心操作
1. 上浮(Up-Heapify)
当向堆中插入新元素时,新元素会被放在数组末尾,然后需要通过上浮操作调整其位置,以满足堆的性质。
- 操作逻辑:比较当前节点与其父节点的值,若不满足堆的性质(大顶堆中当前节点值大于父节点,小顶堆中当前节点值小于父节点),则交换两者位置,重复此过程直到满足堆性质或到达堆顶。
2. 下沉(Down-Heapify)
当堆顶元素被移除或堆中元素值被修改时,需要通过下沉操作调整堆的结构,维持堆的性质。
- 操作逻辑:比较当前节点与其左右子节点的值,选择符合堆性质的子节点(大顶堆选值最大的子节点,小顶堆选值最小的子节点),若当前节点与该子节点不满足堆性质则交换,重复此过程直到满足堆性质或成为叶子节点。
3. 插入元素
- 步骤1:将新元素添加到数组末尾;
- 步骤2:对新元素执行上浮操作,调整堆结构。
4. 删除堆顶元素
- 步骤1:将堆顶元素与数组末尾元素交换;
- 步骤2:删除数组末尾的原堆顶元素;
- 步骤3:对新的堆顶元素执行下沉操作,调整堆结构。
5. 构建堆
将一个无序数组转换为堆结构,通常从最后一个非叶子节点开始,从下到上依次执行下沉操作。
- 最后一个非叶子节点的索引为
(n//2)-1(n为数组长度)。
四、堆的时间复杂度
- 插入操作:时间复杂度为
O(log n),上浮操作最多遍历堆的高度次节点。 - 删除堆顶操作:时间复杂度为
O(log n),下沉操作最多遍历堆的高度次节点。 - 构建堆:时间复杂度为
O(n),优于逐个插入的O(n log n)。 - 获取堆顶元素:时间复杂度为
O(1),可直接访问数组首个元素。
五、堆的实现示例(小顶堆)
python
class MinHeap:
def __init__(self):
self.heap = []
def parent(self, idx):
return (idx - 1) // 2
def left_child(self, idx):
return 2 * idx + 1
def right_child(self, idx):
return 2 * idx + 2
def swap(self, i, j):
self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
def up_heapify(self, idx):
# 上浮操作,维护小顶堆性质
while idx > 0 and self.heap[idx] < self.heap[self.parent(idx)]:
self.swap(idx, self.parent(idx))
idx = self.parent(idx)
def down_heapify(self, idx):
# 下沉操作,维护小顶堆性质
n = len(self.heap)
while True:
smallest = idx
left = self.left_child(idx)
right = self.right_child(idx)
if left < n and self.heap[left] < self.heap[smallest]:
smallest = left
if right < n and self.heap[right] < self.heap[smallest]:
smallest = right
if smallest == idx:
break
self.swap(idx, smallest)
idx = smallest
def insert(self, val):
# 插入元素
self.heap.append(val)
self.up_heapify(len(self.heap) - 1)
def extract_min(self):
# 删除并返回堆顶最小值
if len(self.heap) == 0:
return None
if len(self.heap) == 1:
return self.heap.pop()
min_val = self.heap[0]
self.heap[0] = self.heap.pop()
self.down_heapify(0)
return min_val
def get_min(self):
# 获取堆顶最小值
return self.heap[0] if self.heap else None
def build_heap(self, arr):
# 构建小顶堆
self.heap = arr.copy()
n = len(self.heap)
# 从最后一个非叶子节点开始下沉
for i in range((n // 2) - 1, -1, -1):
self.down_heapify(i)
使用示例
python
heap = MinHeap()
# 插入元素
heap.insert(5)
heap.insert(3)
heap.insert(8)
heap.insert(1)
print(heap.get_min()) # 输出1
# 提取堆顶
print(heap.extract_min()) # 输出1
print(heap.get_min()) # 输出3
# 构建堆
arr = [9, 4, 7, 1, 3, 6]
heap.build_heap(arr)
print(heap.get_min()) # 输出1
六、堆的常见应用
- 优先队列:堆是优先队列的底层实现,可根据优先级(极值)快速获取和删除元素,例如任务调度、事件驱动系统。
- 堆排序 :利用堆的性质实现排序,时间复杂度为
O(n log n),分为构建堆和逐次提取堆顶两个阶段。 - Top-K问题 :从海量数据中获取前K大或前K小的元素,通过维护一个大小为K的小顶堆(求Top-K大)或大顶堆(求Top-K小),可在
O(n log K)时间内完成。 - 中位数维护:用大顶堆存储左半部分数据,小顶堆存储右半部分数据,可快速获取中位数。
- 多路归并排序:合并多个有序数组时,用堆快速获取各数组当前最小值,实现高效归并。
七、堆的扩展
- 二项堆:支持快速合并两个堆,适用于频繁合并的场景。
- 斐波那契堆 :理论上部分操作(如合并、插入)的时间复杂度为
O(1),但实现复杂,多用于理论分析。