堆的定义
- 必须是一个完全二叉树(除了最后一层, 每个节点都有两个子节点, 最后一层只能缺少若干个右节点)
- 堆中每一个节点的值都必须
>=(大顶堆)或<=(小顶堆)
左右子树节点的值
堆中节点的关系
- 下标为i节点的父节点序号是
i/2
- 下标为i节点的左子树节点时
2*i
, 右子树节点是2*i+1
完全二叉树特点:
如果一个完全二叉树有n个节点, 那么从n/2+1个节点开始到n都是叶子节点
构造最小二叉堆
# coding:utf-8
"""
空间复杂度: O(1) 原地排序
时间复杂度: O(nlogn)
不稳定, 因为最后一个节点跟堆顶节点互换可能导致相同元素的顺序互换
"""
class MinHeap(object):
"""
最小堆
"""
def __init__(self, nums):
self.heap_list = [0] # 填充0位置
self.size = 0
self.init_heap(nums)
def insert(self, data):
"""
插入元素, 放到最尾部, 上浮
:param num:
:return:
"""
self.heap_list.append(data)
self.size += 1
self._go_up(self.size)
def _go_up(self, i: int):
"""
末尾元素上浮
:param i:
:return:
"""
while int(i / 2) > 0:
parent = int(i / 2)
if self.heap_list[i] < self.heap_list[parent]:
self.heap_list[i], self.heap_list[parent] = self.heap_list[parent], self.heap_list[i]
i = parent
def pop_top(self):
"""
删除堆顶元素, 使用最后一个值移动到顶部, 再进行下浮
:return:
"""
if self.size >= 1:
top_value = self.heap_list[1]
self.heap_list[1] = self.heap_list[self.size]
self.heap_list.pop()
self.size -= 1
self._go_down(1)
return top_value
else:
raise Exception("Heap Empty")
def _go_down(self, i: int):
while (2 * i) <= self.size:
min_child_pos = self._get_min_child(i)
if self.heap_list[i] > self.heap_list[min_child_pos]:
self.heap_list[i], self.heap_list[min_child_pos] = self.heap_list[min_child_pos], self.heap_list[i]
i = min_child_pos
def _get_min_child(self, i: int):
"""
找出i节点左右子树中较小的节点
:param i:
:return:
"""
left = 2 * i
right = 2 * i + 1
if right > self.size:
return left
elif self.heap_list[left] < self.heap_list[right]:
return left
else:
return right
def init_heap(self, nums: list):
"""
构造堆. 完全二叉树中从 n/2 开始都是叶子节点, 所以只需要让非叶子节点下沉
:param nums:
:return:
"""
start_pos = len(nums) // 2
self.size = len(nums)
self.heap_list.extend(nums)
while start_pos > 0:
self._go_down(start_pos)
start_pos -= 1
if __name__ == "__main__":
nums = [9, 4, 7, 1, 8, 20]
mh = MinHeap(nums)
mh.insert(2)
mh.insert(17)
res = [mh.pop_top() for _ in range(5)]
assert res == [1, 2, 4, 7, 8]
应用
优先级队列
- 高性能定时器
比如一个定时器中维护了很多的定时任务, 每个任务都设定了一个触发执行的时间点, 定时器每过一个很小的单位时间(比如0.1s), 就会扫描一遍任务, 如果有任务是当前时间, 就触发执行.
每过0.1s扫描全部任务效率会很低, 所以把所有任务放入一个最小堆中, 堆顶存储的是最先执行任务. 定时器可以根据堆顶任务的执行时间得到一个时间间隔T, 可以直接过T时间后再来检查 - 爬虫任务的优先队列
二叉堆常用在爬虫的优先级队列中, 把任务按照优先级放入二叉堆, 调度器可以拿堆顶元素, 保证拿到的是优先级最高的task.
利用堆求Top K
- 如何在一个包含n个元素的数组中找出前K大数据?
构建一个K大小的小顶堆, 遍历数组与堆顶元素比较, 如果比堆顶元素大就删除堆顶数据, 把该数据插入堆中, 否则就比较下一个. 最后得到的小顶堆内的K个元素就是前K大的元素.
资料
- <>
- <>
- <>
原文地址:https://www.cnblogs.com/zlone/p/11043158.html
时间: 2024-10-17 01:16:06