什么是堆?深入理解堆数据结构及其应用
粉丝提问
⭐算法OJ⭐数据流的中位数【最小堆】Find Median from Data Stream 发表后收到一位粉丝的私信询问:
“经常听说堆、堆排序、优先队列这些概念,但一直不太明白堆到底是什么,能简单解释一下吗?它和内存分配中的堆是一回事吗?”
这是一个很好的问题!堆确实是计算机科学中一个重要但容易混淆的概念。下面我就来详细解释一下。
堆的基本概念
首先需要明确的是,数据结构中的"堆"(Heap)和内存管理中的"堆"是两个完全不同的概念,只是中文翻译相同而已。
数据结构中的堆是一种特殊的完全二叉树,它满足堆属性:
- 完全二叉树性质:除了最后一层,其他层都是完全填满的,且最后一层的节点都靠左排列
- 堆序性质:
- 在最大堆中,每个节点的值都大于或等于其子节点的值
- 在最小堆中,每个节点的值都小于或等于其子节点的值
最大堆和最小堆示例
堆的操作
堆通常支持以下几种基本操作:
1. 插入操作(O(log n))
向堆中插入一个新元素:
- 将新元素添加到堆的末尾(保持完全二叉树性质)
- 从该节点开始向上调整(称为"上浮"或"堆化向上"),直到堆的性质恢复
def heap_insert(heap, value):
heap.append(value)
current = len(heap) - 1
while current > 0:
parent = (current - 1) // 2
if heap[parent] < heap[current]: # 最大堆示例
heap[parent], heap[current] = heap[current], heap[parent]
current = parent
else:
break
2. 删除堆顶元素(O(log n))
从堆中移除并返回堆顶元素(最大或最小元素):
- 保存堆顶元素
- 将堆的最后一个元素移到堆顶
- 从堆顶开始向下调整(称为"下沉"或"堆化向下"),直到堆的性质恢复
def heap_pop(heap):
if not heap:
return None
top = heap[0]
heap[0] = heap[-1]
heap.pop()
current = 0
while True:
left = 2 * current + 1
right = 2 * current + 2
largest = current
if left < len(heap) and heap[left] > heap[largest]:
largest = left
if right < len(heap) and heap[right] > heap[largest]:
largest = right
if largest != current:
heap[current], heap[largest] = heap[largest], heap[current]
current = largest
else:
break
return top
3. 构建堆(O(n))
将一个无序数组构建成堆:
- 从最后一个非叶子节点开始(索引为n//2 - 1)
- 对每个节点执行下沉操作
def build_heap(arr):
n = len(arr)
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
堆的应用
堆数据结构有许多重要应用:
- 堆排序:利用堆实现的 O ( n l o g n ) O(n log n) O(nlogn) 排序算法
- 优先队列:堆是优先队列的高效实现方式
- 图算法:如Dijkstra最短路径算法、Prim最小生成树算法
- Top K问题:快速找出前K个最大或最小元素
- 中位数维护:使用两个堆可以动态维护数据流的中位数
堆与内存分配中的堆的区别
最后回答粉丝的第二个问题:数据结构中的堆和内存分配中的堆是完全不同的概念。
- 数据结构中的堆:是一种树形数据结构,用于高效地获取和操作最大/最小元素
- 内存分配中的堆:是操作系统提供的一块内存区域,用于动态内存分配(
malloc/free
,new/delete
等),与栈内存相对
它们唯一的共同点是英文都叫"Heap",但中文翻译相同纯属巧合。
总结
堆是一种高效的数据结构,特别适合需要频繁访问最大或最小元素的场景。理解堆的原理和实现,对于掌握许多高级算法和解决实际问题都非常有帮助。希望这篇解释能帮助到有同样疑问的朋友们!
如果你有更多关于堆或其他数据结构的问题,欢迎继续留言私信讨论。