堆的定义:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)
堆是一种重要的线性数据结构,通常被看作是一棵树的数组对象。(堆总是一个完全二叉树)
由于二叉树良好的形态已经包含了父节点和孩子节点的关系信息,因此就可以不使用链表而简单的使用数组来存储堆。
Python没有独立的堆类型,而只有一个包含一些堆操作函数的模块: heapq模块
heapq模块提供了堆队列算法的实现,也称为优先队列算法。
堆是完全二叉树,且每个父节点的值都小于或等于它的任何子节点,heapq模块的堆算法算法实现的数组heap中,对于所有的索引值(0~n-1)都满足 heap[k] <= heap[2*k+1] 且 heap[k] <= heap[2*k+2] 。 为了便于比较,不存在的元素被认为是无限的。
这个模块有关的API与教科书中描述的堆算法有两个不同之处:
(a)使用基于零的索引。这使得节点的索引与其子节点的索引之间的关系稍微不那么明显,但是更适合,因为Python使用基于零的索引。
(b)pop方法只返回最小的项;它最小的元素总是根元素heap[0](min heap)
1. 函数heappush用于在堆中添加一个元素。
注意,不能将它用于普通列表,而只能用于使用各种堆函数创建的列表。原因是元素的顺序很重要
如果使用普通列表,不报错但是结果不对的。
2. 函数heappop弹出最小的元素(总是位于索引0处),并确保剩余元素中最小的那个位于索引0处(保持堆特征)。虽然弹出列表中第一个元素的效率通常不是很高,但这不是问题,因为heappop会在幕后做些巧妙的移位操作。
3.函数heapify通过执行尽可能少的移位操作将列表变成合法的堆(具备堆特征)
4. 函数heapreplace 它从堆中弹出最小的元素,再压入一个新元素。相比于依次执行函数heappop和heappush,这个函数的效率更高。
5. nlargest(n, iterable, key=None)和nsmallest(n, iterable, key=None):分别用于找出可迭代对象iterable中最大和最小的n个元素
注意: 如果需要重复使用这些函数,可以考虑将iterable转换为实际的堆。
查找最大或最小的 N 个元素?
当要查找的元素个数相对比较小的时候,函数 nlargest() 和 nsmallest() 是很合适的。
如果你仅仅想查找唯一的最小或最大(N=1)的元素的话,那么使用 min() 和max() 函数会更快些。
如果 N 的大小和集合大小接近的时候,通常先排序这个集合然后再使用切片操作会更快点(sorted(items)[:N] 或者是 sorted(items)[-N:]
堆的应用:海量实数中(一亿级别以上)找到TopK(一万级别以下)的数集合。
A 排序:通常遇到找一个集合中的TopK问题,想到的便是排序,因为常见的排序算法例如快排算是比较快了,然后再取出K个TopK数,时间复杂度为O(nlogn),当n很大的时候这个时间复杂度还是很大的; 对于一亿数据来说,A方案大约是26.575424*n;
B 堆队列:由于我们只需要TopK,因此不需要对所有数据进行排序,可以利用堆的思想,维护一个大小为K的小顶堆,然后依次遍历每个元素e, 若元素e大于堆顶元素root,则删除root,将e放在堆顶,然后调整为小顶堆,时间复杂度为logK;这样遍历一遍后,最小堆里面保留的数就是我们要找的topK,整体时间复杂度为O(k+n*logk)约等于O(n*logk),大约是13.287712*n(由于k与n数量级差太多),这样时间复杂度下降了约一半。在python的nlargest(n, iterable, key=None)的实现中实现了这种算法:
if key is None:
it = iter(iterable)
# put the range(n) first so that zip() doesn't
# consume one too many elements from the iterator
result = [(elem, i) for i, elem in zip(range(n), it)]
if not result:
return result
_heapify_max(result)
top = result[0][0]
order = n
_heapreplace = _heapreplace_max
for elem in it:
if elem < top:
_heapreplace(result, (elem, order))
top = result[0][0]
order += 1
result.sort()
return [r[0] for r in result]
A、B、两个方案中,当K和n的数量级相差越大,B方式越有效。
https://www.itcodemonkey.com/article/2082.html《堆和堆的应用》