堆简介
二叉堆是一种完全二叉树,即整棵二叉树中除了最底层的叶子节点之外,其余节点是填满的,而最底层的叶子节点由左到右也是填满的不能存在空隙。
堆主要包括两类:大顶堆和小顶堆。大顶堆指每个节点的键值(key)都大于或等于其叶子节点的键值,而小顶堆指每个节点键值都小于等于其节点的键值。STL中堆主要使用vector/array实现的。大顶堆的最大值在根节点,故其总是位于底层的vector/array的起头处;小顶堆的最小值在根节点,故其总是位于vector/array的起头处。
此算法只要存在于stl_heap.h中,主要有make_heap(),push_heap(),pop_heap(),sort_heap()。
make_heap()算法
此算法用于将一段现有的数据转化为一个heap,即构建二叉堆的过程。
// 以下这组make_heap()不允许指定“大小比较标准”
template
void
__make_heap(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Tp*, _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, _Tp(*(__first + __parent)));
if (__parent == 0) return; // 走完根节点,就结束
__parent--; // (已重排之子树的)头部向前一个节点
}
}
// 利用所给的数组建立堆,其主要涉及调整底部vector中元素使其满足堆的条件
// 将[first, last)排列为一个heap
// 版本1
template
inline void
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type,
_LessThanComparable);
__make_heap(__first, __last,
__VALUE_TYPE(__first), __DISTANCE_TYPE(__first));
}
template
void
__make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp, _Tp*, _Distance*)
{
if (__last - __first < 2) return;
_Distance __len = __last - __first; // 计算堆的长度
_Distance __parent = (__len - 2)/2; // 计算根节点位置
while (true) {
// 调整排列元素,使其满足堆
__adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent)),
__comp);
if (__parent == 0) return;
__parent--;
}
}
// 将[first, last)排列为一个heap
// 版本2
template
inline void
make_heap(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__make_heap(__first, __last, __comp,
__VALUE_TYPE(__first), __DISTANCE_TYPE(__first));
}
// 对排序算法
template
void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type,
_LessThanComparable);
// 以下,每执行一次pop_heap(),极值(在STL heap中为极大值)即被放在尾端。扣除尾端再执行一次pop_heap(),次极值
// 又被放在新尾端。一直下去,最后即得到排序结果
while (__last - __first > 1)
pop_heap(__first, __last--); // 每执行pop_heap()一次,操作范围即退缩一格
}
push_heap()算法
在堆中添加一个元素。为了满足完全二叉树的条件,新加入的元素一定要放在最下一层作为叶子节点,并填补在从左到右的第一个空格出,即将新元素插入在底层vector/array的end()处。插入后,为了满足堆(以大顶堆为例)的条件,还需要执行上溯操作:即将新节点与其父节点比较,如果其键值大于父节点,就将父子对换位置。如此一直上溯,直到不需对换或直到根节点为止。
// 以下这组push_back()不允许指定“大小比较标准”
template
void
__push_heap(_RandomAccessIterator __first,
_Distance __holeIndex, _Distance __topIndex, _Tp __value)
{
_Distance __parent = (__holeIndex - 1) / 2;
while (__holeIndex > __topIndex && *(__first + __parent) < __value) {
// 当尚未达到顶端,且父节点小于新值(于是不符合heap的次序特性)
// 由于以上使用operator<,可知STL heap是一种max-heap(大者为父)
*(__first + __holeIndex) = *(__first + __parent); // 令洞者为父节点
__holeIndex = __parent; // percolate up:调整洞号,向上提升至父节点
__parent = (__holeIndex - 1) / 2; // 新洞的父节点
}
*(__first + __holeIndex) = __value; // 令洞值为新值,完成插入操作
}
template
inline void
__push_heap_aux(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Distance*, _Tp*)
{
// 根据implicit represention heap的结构特性:新值必置于底部容器的最尾端,此即第一个洞号:(last-first)-1
__push_heap(__first, _Distance((__last - __first) - 1), _Distance(0),
_Tp(*(__last - 1)));
}
// push_heap函数,在堆中插入一个新元素
// 版本1
template
inline void
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type,
_LessThanComparable);
// 注意:此函数被调用时,新元素应已置于底部容器的最尾端
__push_heap_aux(__first, __last,
__DISTANCE_TYPE(__first), __VALUE_TYPE(__first));
}
template
void
__push_heap(_RandomAccessIterator __first, _Distance __holeIndex,
_Distance __topIndex, _Tp __value, _Compare __comp)
{
_Distance __parent = (__holeIndex - 1) / 2;
while (__holeIndex > __topIndex && __comp(*(__first + __parent), __value)) {
*(__first + __holeIndex) = *(__first + __parent);
__holeIndex = __parent;
__parent = (__holeIndex - 1) / 2;
}
*(__first + __holeIndex) = __value;
}
template
inline void
__push_heap_aux(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp,
_Distance*, _Tp*)
{
__push_heap(__first, _Distance((__last - __first) - 1), _Distance(0),
_Tp(*(__last - 1)), __comp);
}
// 版本2
template
inline void
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__push_heap_aux(__first, __last, __comp,
__DISTANCE_TYPE(__first), __VALUE_TYPE(__first));
}
pop_heap()算法
在堆中删除根元素。下面以大顶堆为例,取走根节点(实质是设置底部容器vector/array的尾端节点)后,为了满足完全二叉树的条件,必须割舍最下层最右边的叶节点,并将其值重新安插到大顶堆的顶点(即vector/array的首端)。取走根元素后,为了满足大顶堆的条件,还需要执行下溯操作:即将此时的根节点值与其较大的自己子节点比较,如果其子节点更大,则交换,然后持续下方,直到不需对换或达到叶节点为止。
// __adjust_heap(调整堆)算法:主要用于调整排列中的数据,使其满足堆的性质。此算法主要用在pop_heap()算法中。
// 以下这个__adjust_heap()不允许指定“大小比较标准”
// 版本1
template
void
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
_Distance __len, _Tp __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);
}
template
inline void
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
_RandomAccessIterator __result, _Tp __value, _Distance*)
{
*__result = *__first; // 设定尾指为首值,于是尾值即为欲求结果,可由客户端稍后再以底层容器之pop_back()取出尾值
// 以上欲重新调整heap,洞号为0(亦即树根处),欲调整值为value(原尾值)
__adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
}
template
inline void
__pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Tp*)
{
// 根据implicit represention heap的次序特性,pop操作的结果应为底部容器的第一个元素。因此,首先
// 设定欲调整值为尾值,然后将首值调至尾节点(所以以上将迭代器result设为last-1),然后重整[first,
// last-1),使之重新成一个合格的heap
__pop_heap(__first, __last - 1, __last - 1,
_Tp(*(__last - 1)), __DISTANCE_TYPE(__first));
}
// pop_heap算法,从堆中删除元素
// 版本1
template
inline void pop_heap(_RandomAccessIterator __first,
_RandomAccessIterator __last)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type,
_LessThanComparable);
__pop_heap_aux(__first, __last, __VALUE_TYPE(__first));
}
// 调整堆,版本2
template
void
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
_Distance __len, _Tp __value, _Compare __comp)
{
_Distance __topIndex = __holeIndex;
_Distance __secondChild = 2 * __holeIndex + 2;
while (__secondChild < __len) {
if (__comp(*(__first + __secondChild), *(__first + (__secondChild - 1))))
__secondChild--;
*(__first + __holeIndex) = *(__first + __secondChild);
__holeIndex = __secondChild;
__secondChild = 2 * (__secondChild + 1);
}
if (__secondChild == __len) {
*(__first + __holeIndex) = *(__first + (__secondChild - 1));
__holeIndex = __secondChild - 1;
}
__push_heap(__first, __holeIndex, __topIndex, __value, __comp);
}
template
inline void
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
_RandomAccessIterator __result, _Tp __value, _Compare __comp,
_Distance*)
{
*__result = *__first;
__adjust_heap(__first, _Distance(0), _Distance(__last - __first),
__value, __comp);
}
template
inline void
__pop_heap_aux(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Tp*, _Compare __comp)
{
__pop_heap(__first, __last - 1, __last - 1, _Tp(*(__last - 1)), __comp,
__DISTANCE_TYPE(__first));
}
// 版本2
template
inline void
pop_heap(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__pop_heap_aux(__first, __last, __VALUE_TYPE(__first), __comp);
}
sort_heap()算法
因为堆中每次执行pop_heap可获得堆中(大顶堆为例)中键值的最大元素,如果持续对整个heap做pop_heap操作,每次将操作范围从后向前缩减一个元素(pop_heap会把键值最大的元素放在底部容器的最尾端),当整个程序执行完毕时,便可得到递增的序列,即实现利用堆进行排序。
// 堆排序算法,主要利用pop\_heap()算法实现
// 版本1
template
void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
__STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type,
_LessThanComparable);
// 以下,每执行一次pop_heap(),极值(在STL heap中为极大值)即被放在尾端。扣除尾端再执行一次pop_heap(),次极值
// 又被放在新尾端。一直下去,最后即得到排序结果
while (__last - __first > 1)
pop_heap(__first, __last--); // 每执行pop_heap()一次,操作范围即退缩一格
}
// 版本2
template
void
sort_heap(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
while (__last - __first > 1)
pop_heap(__first, __last--, __comp);
}
参考文献
STL源码剖析——侯捷
STL源码