堆
堆的基础知识
堆(heap)并不归属于STL容器,但是C++中priority_queue的底层机制正是由heap实现的。
-
思考: 为什么不用list作为priority_queue的底层机制
解答: list插入可享常数时间,但是对于查找list中的最大最小值需要遍历整个list,其复杂度为O(n), 当然也可以改变做法,先让元素插入的时候先经过排序这一关,使得list的元素值中时有小到大(或由大到小),但是这样一来,插入就只有线性的效率。
-
思考:为什么不用binary search tree作为priority_queue的底层机制
解答:比较麻烦的做法是以binary search tree(如:RB-tree)作为priority_queue的底层机制,这样以来元素的插入和极值的取得就有O(logN)的表现(当然这里指的是最好的情况,比如一般的binary search tree当严重不平衡的时候可能为O(N))。但是杀鸡用牛刀,未免小题大作,一来binary search tree的输入需要有足够的随机性,保证平衡;二来binary search tree并不容易实现。priority_queue的复杂度最好介于queue和binary search tree之间。binary heap便是完美契合。
一般我们所说的堆是二叉堆(binary heap),binary heap就是一种完全二叉树(complete binary tree)。除了最底层叶子节点外,是填满的,同时叶子节点从左到右不能有空隙。
堆是和链表和二叉树一样也是一种数据结构,前面说过堆实际上就是一种完全二叉树,其区别在于其内部是实现了节点之间的比较,也就是可以看作含内部比较器的完全二叉树。
最大堆:节点的值大于其左右节点,用作升序排列
最小堆:节点的值小于其左右节点,用做降序排列
堆排序主要经过两部分:初始建堆,堆调整(堆排序)
建堆过程(自底向上)
建堆主要步骤如下:
- 将长度为n的数组对应到完全二叉树上
- 找到一个非叶子节点,对它进行操作,比较当前值和其左右孩子节点的值,是否满足最大/最小堆的要求,不满足则调整
- 依次查找下一个非叶子几点,重复步骤2,直到最后一个父节点。
如下所示,以最大堆建堆为例子
堆调整(堆排序)
在建堆的基础上对堆进行排序,需要记住的是有序区是从后往前插入的,也就是前面所讲的最大堆对应的是升序排列,反之对应的是降序排列。
堆排序的过程是将堆顶元素插入到有序区,将最后一个元素放入到堆顶,然后再采用上浮和下沉操作调整堆,使其满足要求,直到只剩一个元素为止。
堆的应用
解决topK问题,其复杂度为O(Nlogk), 建立一个大小为k的最小堆,然后将剩余的数依次和堆顶元素比较和进行堆调整。
拓展:topK问题可以用快排达到O(N)的时间复杂度,将在后面进行分析。
参考文献
[1]STL源码剖析
[2]博客:https://blog.csdn.net/qq_36711757/article/details/80444638