一.引言

前面提到了 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]

python 大顶堆函数 python最大堆_python

# 向堆中添加元素
    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

python 大顶堆函数 python最大堆_python 大顶堆函数_02

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