一.引言

前面写到了 最大堆的实现应用,趁热打铁把最小堆的也搞定,最小堆一定意思上和最大堆的实现相同,这不过“大”的概念变成了“小”,不同认知下,可以理解为两个是同一个事情,最小堆常用于大规模数据下寻找 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]

python 中小根堆_添加元素

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

 

python 中小根堆_弹出堆顶_02

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())

python 中小根堆_最小堆_03

[1, 3, 7, 6, 5, 9, 8]
1
3
5
6
7
9
8