heap并不归属于STL容器组件,作为priority queue的底层机制。
priority queue允许用户以任何次序将任何元素推入容器内,但取出时一定是从优先权最高(也就是数值最高)的元素开始取。
以array表述tree:将array的0号元素保留(或设为无限大值或无限小值),那么当完全二叉树中的某个节点位于array的i处时,其左子节点必位于array的2i处,其右子节点必位于2i+1处,其父节点必位于int(i/2)处,通过简单的位置规则,array可以轻易实现完全二叉树。这种表述方法称为隐式表述法。
这样构成一个priority queue需要的工具就很简单:一个vector(因可动态改变大小)和一组heap算法(用来插入元素、删除元素、取极值,将一整组数据排列成一个heap)。
heap算法
push_heap算法
template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first,
RandomAccessIterator last) {
//此函数被调用时,新元素已置于底部容器的最尾端
__push_heap_aux(first, last, distance_type(first), value_type(first));
}
template<class RandomAccessIterator,class Distance,class T>
inline void __push_heap_aux(RandomAccessIterator first,
RandomAccessIterator last, Distance*, T*) {
__push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1)));
//根据隐式表述heap的结构特性,新值必置于底部容器的最尾端,即第一个洞号(last-first)-1
}
//以下这组push_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator,class Distance,class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
Distance topIndex, T value) {
Distance parent = (holeIndex - 1) / 2;//找出父节点
while (holeIndex > topIndex&&*(first + parent) < value) {
//尚未到达顶端,且父节点小于新值
//由于以上使用operator<,可知STL heap是一种max-heap
*(first + holeIndex) = *(first + parent);//令洞值为父值
holeIndex = parent;//调整洞号,向上提升至父节点
parent = (holeIndex - 1) / 2;//新洞的父节点
}//持续至顶端,或满足heap的次序特性为止
*(first + holeIndex) = value;//令洞值为新值,完成插入操作
}
pop_heap算法
pop操作取走根节点(其实是设至底部容器vector的尾端节点)后,为满足完全二叉树的条件,必须割舍最下层最右边的叶节点,并将其值重新安插至max-heap(因此有必要重新调整heap结构)。
为满足max-heap次序特性(每个节点的键值都大于或等于其较大子节点的键值),执行percolate down程序:将空间节点和其较大子节点对调,并持续下方,直至叶节点为止。然后将前述被割舍之元素值设给这个已到达叶层的空洞节点,再对它执行一次percolate up程序。
template<class RandomAccessIterator>
inline void pop_heap(RandomAccessIterator first,
RandomAccessIterator last) {
__pop_heap_aux(first, last, value_type(first));
}
template<class RandomAccessIterator,class T>
inline void __pop_heap_aux(RandomAccessIterator first,
RandomAccessIterator last, T*) {
__pop_heap(first, last - 1, last - 1, T(*(last - 1)),distance_type(first));
//以上根据隐式表述heap的次序特性,pop操作的结果应为底部容器的第一个元素。
//因此,首先欲调整值为尾值,然后将首值调至尾节点(所以以上将迭代器result
//设为last-1).然后重整[first,last-1),使之重新成一个合格的heap
}
//以下这组__pop_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator,class T,class Distance>
inline void __pop_heap(RandomAccessIterator first,
RandomAccessIterator last,
RandomAccessIterator result,
T value, Distance*) {
*result = *first;//设定尾值为首值,于是尾值即为欲求结果,可以底层容器之pop_back()取出尾值
__adjust_heap(first, Distance(0), Distance(last - first), value);
//以上欲重新调整heap,洞号为0(即树根处),欲调整值为value(原尾值)
}
//以下这组__adjust_heap()不允许指定“大小比较标准”
template <class RandomAccessIterator,class Distance,class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
Distance len, T value) {
Distance topIndex = holeIndex;
Distance secondChild = 2 * holeIndex + 2;//洞节点之右子节点
while (secondChild < len) {
//比较洞节点之左右两个子值,然后以secondChild代表较大子节点
if (*(first + secondChild) < *(first + (secondChild - 1))
secondChild--;
//percolate down:令较大子值为洞值,再令洞号下移至较大子节点处
*(first + holeIndex) = *(first + secondChild);
holeIndex = secondChild;
//找出新洞节点的右子节点
secondChild = 2 * (secondChild + 1);
}
if (secondChild == len)
{
//没有右子节点,只有左子节点
//percolate down:令左子值为洞值,再令洞号下移至左子节点处
*(first + holeIndex) = *(first + (secondChild - 1));
holeIndex = secondChild - 1;
}
//此时可能尚未满足percolate up操作
__push_heap(first, holeIndex, topIndex, value);
}
pop_heap之后,最大元素只是被置放在底部容器的最尾端,尚未被取出。如果要取其值,可使用底部容器vector所提供的back()操作函数。如果要移除它,可使用底部容器所提供的pop_back()操作函数。
sort_heap算法
既然每次pop_heap可获得heap中键值最大的元素,如果持续对整个heap做pop_heap操作,每次将操作范围从后向前缩减一个元素,当整个程序执行完毕,便可获得一个递增序列。
sort_heap()接受两个迭代器,用来表现一个heap底部容器的头尾。如果不符合这个条件,sort_heap执行结果未知。排序后,原来的heap就不是一个合法的heap了。
//以下这个sort_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator>
void sort_heap(RandomAccessIterator first,
RandomAccessIterator last) {
//以下,每执行一次pop_heap(),极值即被放在尾端
//扣除尾端再执行一次pop_heap(),次极值又被放在新尾端,一直下去,最后得排序结果
while (last - first > 1)
pop_heap(first, last--);
}
make_heap算法
这个算法用来将一段现有的数据转化为一个heap,主要依据完全二叉树的隐式表述。
//将[first,last)排列为一个heap
template<class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first,
RandomAccessIterator last) {
__make_heap(first, last, value_type(first), distance_type(first));
}
//以下这组make_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator,class T,class distace>
void __make_heap(RandomAccessIterator first,
RandomAccessIterator last, T*, Distance*) {
if (last - first < 2)return;//如果长度为0或1,不必重新排列
Distance len = last - first;
//找出第一个需要重排的子树头部,以parent标示出,由于任何叶节点都不需执行
//percolate down,所以有以下计算,parent命名不佳,名为holeIndex更好
Distance parent = (len - 2) / 2;
while (true) {
//重排parent为首的子树,len是为了让__adjust_heap()判断操作范围
__adjust_heap(first, parent, len, T(*(first + parent)));
if (parent == 0)return;//走完根节点就结束
parent--;//(已重排之子树的)头部向前一个节点
}
}
迭代器
heap的所有元素都必须遵循特别的(完全二叉树)排列规则,所以heap不提供遍历功能,也不提供迭代器。