一、概述
首先,我们应该知道heap并不属于STL容器组件,但是经常充当着底层实现结构,比如最大优先队列(priority queue)等。
本章所采用list版本为 SGI STL 2.91版本。
堆是一种完全二叉树,因此我们可以用数组来存储所有节点。在这里的实现中,采用了一个技巧:将数组中索引为0的元素保留,设置为极大值或者为极小值(依据大顶堆或者小顶堆而定)。那么 当某个节点的索引是i时,其左子节点索引为2i,右子节点索引为2i+1.父节点是i/2(这里/表示高斯符号,取整) 。这种以数组表示树的方式,我们称为 隐式表述法 (implicit reprentation)。我们这里用C++ STL中的容器vector实现替代数组的功能。
堆分为 大顶堆和小顶堆 。这里介绍并实现的是大顶堆。
二、heap相关算法
push_heap算法
此操作是向堆中添加一个节点。为了满足完全二叉树的条件,新加入的元素一定放在最下面的一层作为叶节点,并填补在由左至右的第一个空格,在这里放在底层容器vector的end()处。
很显然,新元素的加入很可能使得堆不在满足大顶堆的性质(每个节点的键值都大于或等于其子节点的键值)。为了调整使得其重新满足大顶堆的特点,在这里执行一个 上溯(percolate up) 操作:将新节点与父节点比较,如果其键值比父节点大,就交换父子的位置,如此一直上溯,直到不需要交换或者到根节点为止。
下面便是实现细节:
template <class _RandomAccessIterator>
inline void push_heap(_RandomAccessIterator first, _RandomAccessIterator last)
{
//此函数被调用时,新元素已经位于底部容器的最尾端
__push_heap_aux(first, last, DISTANCE_TYPE(first), VALUE_TYPE(first));
}
下面是__push_heap_aux() 的实现
template <class RandomAccessIterator, class _Distance, class _Tp>
inline void __push_heap_aux(RandomAccessIterator first,RandomAccessIterator last, Distance*, Tp*)
{
__push_heap(first, Distance((last - first) - 1), Distance(0), Tp(*(last - 1)));
}
下面是__push_heap() 的实现,不允许指定大小比较的标准
template <class _RandomAccessIterator, class Distance, class Tp>
void
__push_heap(RandomAccessIterator first,Distance holeIndex, Distance topIndex, Tp value)
{
Distance parent = (holeIndex - 1) / 2; //找出父结点
while (holeIndex > topIndex && *(first + parent) < value) {
// 未到达顶点,并且父结点确实小于新值,将该位置设为父值,同时调整value待插入位置
*(first + holeIndex) = *(first + parent);
holeIndex = parent;
parent = (holeIndex - 1) / 2;
}
*(first + holeIndex) = value; //找到合适位置,赋值,完成插入操作
}
pop_heap算法
此操作取走根节点。 对于大顶堆,取得的是堆中值最大的节点,对于小顶堆,取得的是堆中值最小的节点。 STL实现并不是将这个节点直接删除,而是将其放在底层容器vector的尾端。 而原尾端的节点插入到前面的适当位置。
我们首先保存原vector尾端的节点值,然后将根节点值存储在此处。为了实将原尾端节点的值插入适当位置,重新构建大顶堆,我们实施如下 调整堆 的操作:
- 先执行 下溯(percolate down) 操作:从根节点开始将 空洞节点(一开始是根节点)和较大子节点交换,并持续向下进行,直到到达叶节点为止。然后将已保存的原容器vector尾端节点赋给这个已到达叶层的空洞节点。
- 还需要执行一次上溯操作。这样,便重新构建了大顶堆。
如图所示:
下面是代码:
template <class _RandomAccessIterator>
inline void pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
__pop_heap_aux(__first, __last, __VALUE_TYPE(__first));
}
template <class _RandomAccessIterator, class _Tp>
inline void __pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp*)
{
__pop_heap(__first, __last - 1, __last - 1, _Tp(*(__last - 1)), __DISTANCE_TYPE(__first));
}
template <class _RandomAccessIterator, class _Tp, class _Distance>
inline void __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __result, _Tp __value, _Distance*)
{
*__result = *__first;
__adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
}
template <class _RandomAccessIterator, class _Distance, class _Tp>
void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __len, _Tp __value)
{
_Distance __topIndex = __holeIndex;
_Distance __secondChild = 2 * __holeIndex + 2;
while (__secondChild < __len) {
if (*(__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);
}
源码中,加入太多 _ ,导致晦涩难懂。同时,源码中还有很多重载版本,包括,可以引入“大小比较标准”。
sort_heap算法
堆排序算法。执行此操作之后,容器vector中的元素按照从小到大的顺序排列。
构建大顶堆之后,不断执行pop_heap算法取出堆顶的元素,即可。因为每次取得的是最大的元素,将其放在容器vector的最尾端。所以到最后vector中的元素是从小到大排列的。
下面是源码:
template <class _RandomAccessIterator>
void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
while (__last - __first > 1)
pop_heap(__first, __last--); // 每执行完一次,操作范围退缩一些
}
make_heap算法
此操作是依据已有的各元素构建堆。其中,各元素已经存放在底层容器vector中。
构建堆实质是一个不断调整堆(即前面pop_heap算法中的调整堆的操作)的过程,即调用__adjust_heap() 。通过不断调整子树,使得子树满足堆的特性来使得整个树满足堆的性质。
叶节点显然需要调整,第一个需要执行调整操作的子树的根节点是从后往前的第一个非叶结点。从此节点往前到根节点对每个子树执行调整操作,即可构建堆。
下面是代码:
template <class _RandomAccessIterator>
inline void make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
__make_heap(__first, __last, __VALUE_TYPE(__first), __DISTANCE_TYPE(__first));
}
template <class _RandomAccessIterator, class _Tp, class _Distance>
void __make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _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)));
if (__parent == 0) return;
__parent--;
}
}
这个过程中,源码显得晦涩难懂,可以参考另一篇博客–C/C++堆排序 ,来简单了解过程。
三、heap的迭代器
由于heap有一定排序规则,所以并不提供访问的迭代器。
四、priority_queue
概述
priority_queue是一个拥有权值的queue,能够加入元素、移除元素等功能。由于它是一个queue,所以只能在底端加入元素、顶端取出元素。
而priority_queue中权值的概念是在插入元素后,进行排序的,即利用max-heap。
priority_queue完整定义
priority_queue是以底部容器为根据,辅助heap的处理规则,实现相对简单。默认情况下,是以vector作为底部容器。
由于priority_queue的性质,往往不被归类为container,而被归类为 container adapter。
template <class _Tp,
class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(vector<_Tp>),
class _Compare
__STL_DEPENDENT_DEFAULT_TMPL(less<typename _Sequence::value_type>) >
class priority_queue {
// requirements:
__STL_CLASS_REQUIRES(_Tp, _Assignable);
__STL_CLASS_REQUIRES(_Sequence, _Sequence);
__STL_CLASS_REQUIRES(_Sequence, _RandomAccessContainer);
typedef typename _Sequence::value_type _Sequence_value_type;
__STL_CLASS_REQUIRES_SAME_TYPE(_Tp, _Sequence_value_type);
__STL_CLASS_BINARY_FUNCTION_CHECK(_Compare, bool, _Tp, _Tp);
public:
typedef typename _Sequence::value_type value_type;
typedef typename _Sequence::size_type size_type;
typedef _Sequence container_type;
typedef typename _Sequence::reference reference;
typedef typename _Sequence::const_reference const_reference;
protected:
_Sequence c; //底层容器
_Compare comp;
public:
priority_queue() : c() {}
explicit priority_queue(const _Compare& __x) : c(), comp(__x) {}
// 这里make_heap、push_heap、pop_heap都是泛型算法
priority_queue(const _Compare& __x, const _Sequence& __s)
: c(__s), comp(__x)
{ make_heap(c.begin(), c.end(), comp); }
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
const_reference top() const { return c.front(); }
void push(const value_type& __x) {
__STL_TRY {
c.push_back(__x);
push_heap(c.begin(), c.end(), comp);
}
__STL_UNWIND(c.clear());
}
void pop() {
__STL_TRY {
pop_heap(c.begin(), c.end(), comp);
c.pop_back();
}
__STL_UNWIND(c.clear());
}
};
priority_queue的迭代器
同样,由于priority_queue所有元素具有一定规则,所以不提供迭代器。