目录
1 概述
heap并不是归属于STL容器组件,它是个幕后英雄,扮演priority queue
的助手。顾名思义,priority queue
允许用户以任何次序将任何元素推入容器内,但取出时一定是从优先权最高(也就是数值最高)的元素开始取。binary max heap
正是具有这样的特性,适合作为priority queue
的底层机制。
1.1 priority queue底层机制的选择分析
- 若以list作为
priority queue
的底层机制,元素插入操作可享常数时间;但是要找到list中的极值,却需要对整个list进行线性扫描。如果在插入元素之前先经过排序这一关,使得list的元素值总是由小到大(或由大到小),此时取得极值以及元素删除操作虽然能达到最高效率,可元素的插入却只有线性表现; - 若以
binary serach tree
作为priority queue
的底层机制,则元素的插入和极值的取得就有O(logN)的表现。但binary serach tree
的输入需要足够的随机性并且binary serach tree
也不容易实现。 binary heap
应运而生,使得priority queue
的复杂度介于queue
和binary search tree
之间。
1.2 binary heap
binary heap
是一种complete binary tree
(完全二叉树)。整颗binary tree
除了最底层的叶节点之外,是填满的,而且最底层的叶节点由左至右又不得有空隙。
隐式表述法:利用array 来储存所有节点。假设动用一个小技巧,将array的#0元素保留(或设为无限大值或无限小值),那么当complete binary tree
中的某个节点位于array的i
处时,其左子节点必位于array的2i
处,其右子节点必位于array的2i+1
处,其父节点必位于i/2
处(此处的“/”权且代表高斯符号,取其整数)。通过这么简单的位置规则,array可以轻易实现出complete binary tree
。
由于array无法动态改变大小,而heap却需要这项功能,因此,以vector代替array是更好的选择!
1.3 heap的分类
根据元素排列方式,heap可分为max-heap和min-heap两种,前者每个节点的键值(key)都大于或等于其子节点键值,后者的每个节点键值(key)都小于或等于其子节点键值。因此,max-heap的最大值在根节点,并总是位于底层array或vector的起头处;min-heap的最小值在根节点,亦总是位于底层array或vector的起头处。STL供应的是max-heap。
2 heap算法
2.1 push_heap
为了满足complete binary tree
的条件,新加入的元素一定要放在最下一层作为叶子节点,并填补从左至右的第一个空格,也就是把新元素插入在底层vector的end()处。
新元素是否适合于其现有位置呢?
为满足max-heap的条件(每个节点的键值都大于或等于其子节点键值),需要执行一个所谓的 percolate up
(上溯)程序:
将新节点拿来与其父节点比较,如果其键值(key)比父节点大,就父子对换位置。如此一直上溯,直到不需对换或直到根节点为止。
push_heap算法最终调用__push_heap函数:
template<class RandomAccessIterator, class Distance, class T>
void __puah_heap(RandomAccessIterator first, Distance holeIndex, Distance topIndex, T value) { //holeIndex:容器的最尾端
Distance parent = (holeIndex - 1) / 2; //找出父节点
while(holeIndex > topIndex && *(first + parent) < value) {
//当尚未到达顶端,且父节点小于新值(此时不满足heap的次序特性)
*(first + holeIndex) = *(first + parent); //令洞值为父值
holeIndex = parent; //percolate up(上溯):调整洞号,向上提升至父节点
parent = (holeIndex - 1) / 2; //新洞的父节点
} //持续至顶端,或满足heap的次序特性为止
*(first + holeIndex) = value; //令洞为新值,完成插入操作
}
2.2 pop_heap
作为max-heap,最大值必然在根节点。pop操作取走根节点(其实是将原根节点设至底部容器vector的尾端节点)后,为了满足complete binary tree
的条件,必须割舍最下层最右边的叶节点,并将其值重新安插至max-heap(因此有必要重新调整heap结构)
为满足max-heap的条件(每个节点的键值都大于或等于其子节点键值),需要执行一个所谓的 percolate down
(下溯)程序:将空间节点和其较大子结点“对调”,并持续下放,指至叶节点为止。然后将前述割舍节点设给这个“已到达叶层的空洞节点”,再对它执行一次上溯程序。
需要注意的是,在经过pop_heap后,最大元素只是被放置于底部容器的最尾端,尚未被取走。如果要取其值,可使用底部容器(vector)所提供的back()操作函数。如果要移除它,可使用底部容器(vector)所提供的pop_back()操作函数。
2.3 sort_heap
通过pop_heap将heap中键值最大的元素取出并放置在底层容器的最尾端。因此不断对heap进行pop操作,便可达到排序效果。循环的终止条件为last-first > 1
。
2.4 make_heap
基于complete binary tree
的隐式表述,将一段现有的vector数据转化为heap。