一.引言
前面提到了 PriorityQueue 踩坑,优先队列排序底层原理就是基于 Max Heap,下面捋一捋最大堆的基本实现与用法。首先明确最大堆及其实现的相关概念:
A. 最大堆,又称大根堆(大顶堆)是指根节点(亦称为堆顶)的值是堆里所有节点值中的最大者
(1) 每个根节点的值都大于其叶子节点
(2) 最大堆是完全二叉树
B.完全二叉树,一个深度为k,节点个数为 2^k - 1 的二叉树即为完全二叉树
(1) 给定 index 寻找父节点
parent = (index - 1) // 2 = (index - 1) >> 2
(2) 给定 index 寻找子节点
left = 2 * index + 1 = (index << 1) + 1
right = 2 * index + 2 = left + 1
二.最大堆实现
1.初始化
(1) maxSize 为最大堆的初始化数组长度
(2) count 为当前最大堆中元素的个数
(3) show 函数返回当前数组
class MaxHeap:
def __init__(self, maxSize=None):
self.maxSize = maxSize
self.list = [None] * maxSize
self.count = 0
# 当前堆中的元素
def length(self):
# 求数组的长度
return self.count
# 展示当前堆中所有元素,乱序
def show(self):
if self.count <= 0:
print('null')
else:
print(self.list[: self.count])
# 判断堆是否为空
def isEmpty(self):
if self.count <= 0:
return True
else:
return False
2.添加元素
(1) 将节点添加至树的最后一位,对应操作就是将数组 count 处置为新的值,并且 count +1
(2) shift_up 首先找到当前节点的父节点,如果小于父节点直接退出;如果大于父节点与父节点交换位置,递归调用 shift_up 继续向上寻找,直到小于父节点,退出循环
[9, 6, 5, 3, 1] => [9, 6, 7, 3, 1, 5]
# 向堆中添加元素
def add(self, value):
if self.count >= self.maxSize:
raise Exception('full')
self.list[self.count] = value # 将新节点增加到最后
self._shift_up(self.count) # 递归构建大堆
self.count += 1
def _shift_up(self, index):
if index > 0:
parent = (index - 1) // 2 # 找到根节点
if self.list[index] > self.list[parent]: # 交换结点
self.list[index], self.list[parent] = self.list[parent], self.list[index]
self._shift_up(parent) # 继续递归从底往上判断
3.弹出堆顶
A. 弹出元素
(1) 根据最大堆定义数组第一位即二叉树第一位为最大数值的数字,返回该数字
(2) 将最后一位的值放到第一位准备重建最大堆
(3) 将最后一位元素的值置为 None
B.重建堆
(1) 根据完全二叉树公式获得左右子节点
(2) 比较堆顶元素与左右子节点元素大小,注意判断左右子节点是否存在
(3) 如果当前值非最大,互换当前索引与最大索引,并向下继续递归探索比当前点 largest 点更大的点
(4) 当 index = largest 时代表当前值大于左右子节点,退出循环,堆重建完成
[9, 6, 7, 3, 1, 5] 弹出最大元素9
def extract(self):
# 弹出最大堆的根节点,即最大值
if not self.count:
raise Exception('null')
value = self.list[0]
self.count -= 1
# 最后的值放在顶端
self.list[0] = self.list[self.count]
# 最后一位置为 None
self.list[self.count] = None
self._shift_down(0)
return value
def _shift_down(self, index):
# 获取左右节点索引
left = 2 * index + 1
right = 2 * index + 2
largest = index
# 分别与左右节点比较大小
if left < self.length() and self.list[left] > self.list[largest]:
largest = left
if right < self.length() and self.list[right] > self.list[largest]:
largest = right
# 向下递归寻找
if largest != index:
self.list[index], self.list[largest] = self.list[largest], self.list[index]
self._shift_down(largest)
4.测试
可以看到 maxHead show 展示全部数组数据时,数据并非顺序,这也就是为什么遍历 PriorityQueue 时返回的数组不保序的原因
# 添加元素
for num in nums:
maxHeap.add(num)
maxHeap.show()
result: [0.0165529, 0.0164001, 0.0164731, -2.38064e-05, 0.011048, 0.01504692]
# 弹出最大值
while not maxHeap.isEmpty():
print(maxHeap.extract())
0.0165529
0.0164731
0.0164001
0.01504692
0.011048
-2.38064e-05