一.引言
前面写到了 最大堆的实现应用,趁热打铁把最小堆的也搞定,最小堆一定意思上和最大堆的实现相同,这不过“大”的概念变成了“小”,不同认知下,可以理解为两个是同一个事情,最小堆常用于大规模数据下寻找 Top-K 小的数字,与之前相似,先明确最小堆及其实现的相关概念:
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 函数返回当前数组
(4) isEmpty 判断堆中是否包含元素
# 构造最小堆
class MinHeap():
# 初始化最小堆
def __init__(self, maxSize=None):
self.maxSize = maxSize
self.array = [None] * maxSize
self._count = 0
def length(self):
return self._count
def show(self):
if self._count <= 0:
print('null')
print(self.array[: self._count], end=', ')
def isEmpty(self):
if self._count <= 0:
return True
else:
return False
2.添加元素
(1) 将新增加的节点添加至树的最后一位,对应的操作就是将数组的对应位置改为新值,并且 count + 1
(2) shift_up 首先找到当前父节点,如果大于父节点直接退出;小于父节点则交换位置,继续 shift_up 寻找比自己小的父节点,直到小于父节点,退出循环
[1, 3, 8, 5, 7, 9] -> [1, 3, 6, 5, 7, 9, 8]
def add(self, value):
# 增加元素
if self._count >= self.maxSize:
raise Exception('Full')
self.array[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.array[parent] > self.array[index]:
self.array[parent], self.array[index] = self.array[index], self.array[parent]
self._shift_up(parent)
3.弹出堆顶
A.弹出元素
(1) 根据最大堆定义数组第一位即二叉树第一位为最小数值的数字,返回该数字
(2) 将最后一位的值放到第一位准备重建最小堆
(3) 将最后一位元素的值置为 None
B.重建堆
(1) 根据完全二叉树公式获得左右子节点
(2) 比较堆顶元素与左右子节点元素大小,注意判断左右子节点是否存在
(3) 如果当前值非最小,互换当前索引与最小索引,并向下继续递归探索比当前点 smaller 点更小的点
(4) 直到该点小于两个子节点,退出循环
[1, 3, 6, 5, 7, 9, 8] 弹出最小元素1
def extract(self):
# 获取最小值,并更新数组
if self._count <= 0:
raise Exception('The array is Empty')
value = self.array[0]
self._count -= 1 # 更新数组的长度
self.array[0] = self.array[self._count] # 将最后一个结点放在前面
self._shift_down(0)
return value
def _shift_down(self, index):
# 此时index 是根结点
if index < self._count:
left = 2 * index + 1
right = 2 * index + 2
# 左右节点都存在
if left < self._count and right < self._count:
left_val = self.array[left]
right_val = self.array[right]
index_val = self.array[index]
# left 最小
if min(left_val, right_val, index_val) == left_val:
self.array[index], self.array[left] = self.array[left], self.array[index]
self._shift_down(left)
# right 最小
if min(left_val, right_val, index_val) == right_val:
self.array[index], self.array[right] = self.array[right], self.array[index]
self._shift_down(right)
# 左叶子结点存在
if left < self._count < right and self.array[left] < self.array[index]:
self.array[left], self.array[index] = self.array[index], self.array[left]
self._shift_down(left)
[1, 3, 7, 6, 5, 9, 8]
3
5
6
7
9
8
这里需要注意有两种情况,一种是左右节点都存在,则需要 left,right,index 三个点同时比大小,也有可能只有左节点(完全二叉树不存在只有右节点情况),此时比较 left,index两个点的大小
4.测试
分别添加元素并遍历取出最小值
minHeap = MinHeap(10)
nums = [3, 5, 9, 6, 1, 7, 8]
# 添加元素
for num in nums:
minHeap.add(num)
# 弹出最大值
while not minHeap.isEmpty():
print(minHeap.extract())
[1, 3, 7, 6, 5, 9, 8]
1
3
5
6
7
9
8