堆继承了有序数组和列表的优点,易于增加、删除元素,也易于指定的查询最小元素。本质上说,堆是一个平衡二叉树,其中每个根节点都小于(或大于,这里只讨论小于)它的左右子节点。由于堆的这些性质,可以在O(log(n))
的时间内实现堆内元素的增删,在常数时间内实现堆内最小元素的查询,而不改变其原有的性质。
堆的平衡二叉树性质使得对某个节点的子节点和父节点的查询变得十分容易:
- 节点
i
的两个子节点是节点2i
和节点2i+1
- 节点
i
的父节点是节点i>>1
(i/2
向下取整)注意这里
i
是从1开始的
因此不需要构建树的节点类。
为了在增删元素后保持堆的基本性质,需要一些设计:
- 增加元素在堆的末尾,增加后堆可能不符合其性质,但是只有最后一个元素不符合,此时只需要递归地根据大小关系调整这个新增的元素和他的父节点即可
- 要删除指定位置的元素,先把堆尾的元素和这个指定位置的元素换位,就可以实现删除这个元素而不破坏堆的结构(因为它在堆尾,删除后不会产生“空洞”)。此时堆的性质可能不再满足,因为本来位于堆尾的元素(是较大的元素)被调换到了堆内(应该是较小的元素),这时只需要根据大小关系递归地调整此节点和它的子节点们的位置即可。调整时参与比较的是最小的子节点,这样就可以保证在调整后这个地方不会因为两个子节点的大小关系产生新的问题。
在这样的设计下,可以通过以下方式实现堆的数据结构:
class Heap():
def __init__(self, l=[]) -> None:
self.array = []
for i in l:
self.add(i)
def find_min(self):
if not self.array:
return None
return self.array[0]
def add(self, n):
"""添加一个元素n"""
self.array.append(n) # 先添加到堆尾
self.heapify_up(len(self.array)) # 再递归地调整它的位置
def delete(self, i):
"""删除位置i的元素,注意i是从1开始的"""
self.array[i-1] = self.array.pop() # 先把要删除的元素和堆尾换位
self.heapify_down(i) # 再递归地调整
def heapify_down(self, i):
it_left_child = i<<1
it_right_child = (i<<1) + 1
if it_left_child > len(self.array):
return
elif it_left_child == len(self.array):
target_child = it_left_child
else:
target_child = it_left_child if self.array[it_left_child-1] < self.array[it_right_child-1] else it_right_child
if self.array[i-1]>self.array[target_child-1]:
self.array[i-1], self.array[target_child-1] = self.array[target_child-1], self.array[i-1]
self.heapify_down(target_child)
def heapify_up(self, i):
if i > 1:
it_parrent = i>>1
if self.array[i-1]<self.array[it_parrent-1]:
self.array[i-1], self.array[it_parrent-1] = self.array[it_parrent-1], self.array[i-1]
self.heapify_up(it_parrent)
def __repr__(self) -> str:
return str(self.array)
if __name__ == '__main__':
h = Heap([4,6,3,7,5,6,2,1,0])
print(h) # [0, 1, 3, 2, 6, 6, 4, 7, 5]
h.delete(1)
print(h) # [1, 2, 3, 5, 6, 6, 4, 7]
h.add(2)
print(h) # [1, 2, 3, 2, 6, 6, 4, 7, 5]
堆的结构可以用来实现优先队列。