大顶堆和小顶堆
大顶堆和小顶堆是二叉堆的两种形式,它们都是完全二叉树。
自己实现大顶堆小顶堆的优势
-
学习目的:自己动手实现大顶堆是理解其原理和运作机制的最佳方式。通过编码过程,可以深入学习堆的数据结构,包括它的插入、删除、调整等操作以及它们的时间复杂度。
-
特定需求的定制:虽然许多编程语言的标准库中都提供了堆的实现(如 Python 的 heapq),但这些实现可能不完全符合特定场景下的需求。自己实现大顶堆可以让你根据具体需求调整数据结构的细节,例如,实现一个支持查找任意元素、删除任意元素或合并两个堆的高效版本。
-
性能优化:在性能关键的应用中,标准库提供的堆实现可能无法满足性能需求。自己实现堆可以针对特定场景进行优化,比如使用更高效的内存结构或者减少不必要的操作来提高性能。
大顶堆(最大堆)
在大顶堆中,任何一个父节点的值都大于或等于它的子节点的值。这意味着,堆的根节点是所有节点中的最大值。
大顶堆的主要特性包括:
-
插入操作:新元素被添加到树的末端,然后向上调整其位置,以满足大顶堆的性质。
-
删除操作(通常指删除最大元素):根节点被删除(从堆中移除最大元素),最后一个元素被移动到根节点位置,然后向下调整其位置,以满足大顶堆的性质。
大顶堆适用于需要快速访问最大元素的场景,如实现优先队列,其中优先级最高的元素(即值最大的元素)需要被首先处理。
class MaxHeap:
def __init__(self):
self.heap = [] # 初始化空堆
def parent(self, index):
# 返回给定索引的父节点的索引
return (index - 1) // 2
def leftChild(self, index):
# 返回给定索引的左子节点的索引
return 2 * index + 1
def rightChild(self, index):
# 返回给定索引的右子节点的索引
return 2 * index + 2
def swap(self, i, j):
# 交换堆中两个元素的位置
self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
def insert(self, key):
# 向堆中插入一个新元素
self.heap.append(key) # 将新元素添加到堆的末尾
self.heapifyUp(len(self.heap) - 1) # 调整堆以维持大顶堆的性质
def heapifyUp(self, index):
# 从给定索引向上调整堆
while index > 0 and self.heap[self.parent(index)] < self.heap[index]:
# 如果当前节点比父节点大,则交换它们的位置
self.swap(index, self.parent(index))
index = self.parent(index) # 更新索引到父节点,继续向上调整
def deleteMax(self):
# 删除并返回堆中的最大元素
if len(self.heap) > 1:
self.swap(0, len(self.heap) - 1) # 将最大元素与堆的最后一个元素交换
max_value = self.heap.pop() # 移除并返回最后一个元素(原最大元素)
self.heapifyDown(0) # 从根节点开始向下调整堆
elif self.heap:
max_value = self.heap.pop() # 如果堆中只有一个元素,直接移除并返回
else:
return None # 如果堆为空,返回None
return max_value
def heapifyDown(self, index):
# 从给定索引向下调整堆
largest = index
left = self.leftChild(index)
right = self.rightChild(index)
# 如果左子节点存在且大于当前节点,更新最大值索引
if left < len(self.heap) and self.heap[left] > self.heap[largest]:
largest = left
# 如果右子节点存在且大于当前最大值,更新最大值索引
if right < len(self.heap) and self.heap[right] > self.heap[largest]:
largest = right
# 如果最大值不是当前节点,交换它们,并继续向下调整
if largest != index:
self.swap(index, largest)
self.heapifyDown(largest)
def getMax(self):
# 返回堆中的最大元素(位于根节点)
return self.heap[0] if self.heap else None
# 使用大顶堆示例
heap = MaxHeap()
heap.insert(3)
heap.insert(2)
heap.insert(15)
heap.insert(5)
heap.insert(4)
heap.insert(45)
print("删除的最大值:", heap.deleteMax()) # 删除并打印最大值
print("现在的最大值:", heap.getMax()) # 打印当前最大值
小顶堆(最小堆)
在小顶堆中,任何一个父节点的值都小于或等于它的子节点的值。这意味着,堆的根节点是所有节点中的最小值。小顶堆的主要特性包括:
-
插入操作:新元素被添加到树的末端,然后向上调整其位置,以满足小顶堆的性质。
-
删除操作(通常指删除最小元素):根节点被删除(从堆中移除最小元素),最后一个元素被移动到根节点位置,然后向下调整其位置,以满足小顶堆的性质。
小顶堆适用于需要快速访问最小元素的场景,如实现优先队列,其中优先级最低的元素(即值最小的元素)需要被首先处理。
class MinHeap:
def __init__(self):
self.heap = [] # 初始化空堆
def parent(self, index):
# 返回给定索引的父节点的索引
return (index - 1) // 2
def leftChild(self, index):
# 返回给定索引的左子节点的索引
return 2 * index + 1
def rightChild(self, index):
# 返回给定索引的右子节点的索引
return 2 * index + 2
def swap(self, i, j):
# 交换堆中两个元素的位置
self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
def insert(self, key):
# 向堆中插入一个新元素
self.heap.append(key) # 将新元素添加到堆的末尾
self.heapifyUp(len(self.heap) - 1) # 调整堆以维持小顶堆的性质
def heapifyUp(self, index):
# 从给定索引向上调整堆
while index > 0 and self.heap[self.parent(index)] > self.heap[index]:
# 如果当前节点比父节点小,则交换它们的位置
self.swap(index, self.parent(index))
index = self.parent(index) # 更新索引到父节点,继续向上调整
def deleteMin(self):
# 删除并返回堆中的最小元素
if len(self.heap) > 1:
self.swap(0, len(self.heap) - 1) # 将最小元素与堆的最后一个元素交换
min_value = self.heap.pop() # 移除并返回最后一个元素(原最小元素)
self.heapifyDown(0) # 从根节点开始向下调整堆
elif self.heap:
min_value = self.heap.pop() # 如果堆中只有一个元素,直接移除并返回
else:
return None # 如果堆为空,返回None
return min_value
def heapifyDown(self, index):
# 从给定索引向下调整堆
smallest = index
left = self.leftChild(index)
right = self.rightChild(index)
if left < len(self.heap) and self.heap[left] < self.heap[smallest]:
smallest = left
if right < len(self.heap) and self.heap[right] < self.heap[smallest]:
smallest = right
if smallest != index:
self.swap(index, smallest)
self.heapifyDown(smallest) # 递归地向下调整
def getMin(self):
# 返回堆中的最小元素(不删除)
return self.heap[0] if self.heap else None
# 使用小顶堆
heap = MinHeap()
heap.insert(3)
heap.insert(2)
heap.insert(15)
heap.insert(5)
heap.insert(4)
heap.insert(45)
print("删除的最小值:", heap.deleteMin())
print("现在的最小值:", heap.getMin())
实现和使用
在实际应用中,大顶堆和小顶堆通常通过数组来实现。
对于数组中任意位置i上的元素:
它的父节点位置是 (i-1)/2 (向下取整)。
它的左子节点位置是 2i + 1。
它的右子节点位置是 2i + 2。
这种表示方法使得堆可以高效地进行插入和删除操作,而不需要实际的树形结构。
使用场景对比
大顶堆:适合需要频繁访问或删除集合中最大元素的场景。
小顶堆:适合需要频繁访问或删除集合中最小元素的场景。
大顶堆和小顶堆的使用场景
大顶堆
在大顶堆中,父节点的值总是大于或等于其子节点的值。这种性质使得大顶堆特别适用于需要快速访问最大元素的场景,如:
-
优先队列:在任务调度、事件驱动编程中,需要优先处理最“重要”或优先级最高的任务。
-
Top K 问题:如从一系列元素中找出最大的 K 个元素。
-
数据流中的最大元素:如实时监控系统中需要持续跟踪并更新最大值。
小顶堆
在小顶堆中,父节点的值总是小于或等于其子节点的值。这种性质使得小顶堆适用于需要快速访问最小元素的场景,如:
-
Dijkstra 算法:在图论中,用于寻找最短路径时,需要快速获取当前未处理节点中距离最小的节点。
-
合并有序文件:如将多个已排序的文件合并成一个大的有序文件时,可以用小顶堆维护各个文件当前最小的未处理元素。
中,父节点的值总是小于或等于其子节点的值。这种性质使得小顶堆适用于需要快速访问最小元素的场景,如: -
Dijkstra 算法:在图论中,用于寻找最短路径时,需要快速获取当前未处理节点中距离最小的节点。
-
合并有序文件:如将多个已排序的文件合并成一个大的有序文件时,可以用小顶堆维护各个文件当前最小的未处理元素。
-
数据流中的最小元素:在需要持续跟踪最小值的应用场景中非常有用