一:heap
1:heap 概述
1):binary max heap 表示的是用户可以以任何次序将任何元素推入容器内,但是每一次取出时取出的一定是优先权最高(也就是数值最高)的元素;同样的 binary min heap 表示的每一次取出的是优先权最低的元素;
2):binary heap 是一种 complete binary tree(完全二叉树),也就是说整棵 binary tree 除了最底层的叶节点(s)之外,是填满的,而最底层的叶节点(s)由左至右又不得有空隙。对于 max heap(最大堆) 来说,每个节点的键值(key)都大于或等于其子节点键值,min heap(最小堆) 的每个节点键值(key)都小于或等于其子节点键值;
3):Complete binary tree 是基于 vector 实现的,如果 vector 的 #0 元素保留(或设为无限大值或无限小值),则当 complete binary tree 中的某个节点位于 vector 的第 i 处时,其左节点位于 vector 的 2i 处,右节点位于 vector 的 2i+1 处,父节点位于 i/2 处(除法只取整);如果 vector 的 #0 号元素不保留的话,则左节点位于 2i+1 处,右节点位于 2i+2 处,父节点位于 (i-1)/2 处。
2:heap 算法
注明:下面呈现的是最大堆(max heap)的一些算法,并且 vector 从 i=0 处开始保存数据
1):push_heap 算法
为了满足 complete binary tree 的条件,新加入的元素一定要放在 vector 的 end()处。同时为了满足 max-heap 的条件(每个节点的键值都大于或等于其子节点键值),我们需要执行一个上溯程序,将新节点拿来与其父节点比较,如果其键值(key)比父节点大,就父子对换位置,如此上溯,直到不需要对换或直到根节点为止;
下面就是push_heap算法的代码,该函数接受两个迭代器,用来表现 heap 底部容器(vector)的头尾,并且新元素已经插入到底部容器的最尾端:
//迭代器 first 和 last 用来表示 vector 的头尾
template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last)
{
//注意,此函数被调用时,新元素已置于底部容器的最尾端
__push_heap_aux(first, last, distance_type(first), value_type(first));
//distance_type 和 value_type 均为函数,见第三章
}
template <class RandomAccessIterator, class Distance, class T>
void __push_heap_aux(RandomAccessIterator first, RandomAccessIterator last, Distance*, T*)
{
__push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1)));
//根据特性:新值必置于底部容器的最尾端
}
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) {
*(first + holeIndex) = *(first + parent); //令洞值为父值
holeIndex = parent; //调整洞号,向上提升至父节点
parent = (holeIndex - 1) / 2; //新洞的父节点
} //持续至顶端,或满足 heap 的次序特性为止
*(first + holeIndex) = value; //令洞值为新值,完成插入操作
}
2):pop_heap
算法
pop_heap
算法表示从 max heap 中取出最大值,这在 vector 中对应为第一个元素。pop_heap
算法的大概操作过程为首先将 vector 的头节点值与尾值对调,然后执行下滤过程,操作代码如下:
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));
//pop 操作的结果应为底部容器的第一个元素,因此,首先设定欲调整值为尾值,然后将首值
//调至尾节点(所以上述迭代器的result 为 last - 1)。然后重整 [first, last - 1),使之重新
//成为一个合格的 heap
}
template <class RandomAccessIterator, class Disatance, class T>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIterator result, T value, Distance*)
{
*result = *first; //设定尾值为首值,于是尾值即为欲求结果
__adjust_heap(first, Distance(0), Distance(last - first), value);
//以上欲重新调整 heap,洞号为 0(亦即树根处),欲调整值为 value(原尾值)
}
template <class RandomAccessIterator, class Distance, class T>
inline void __adjust_heap(RandomAccessIterator first, Distance holeIndex, Distance len, T value)
{
Distance child = 2 * holeIndex + 1;
const Distance last_index = len - 1;
while (child <= last_index) {
if (child != last_index && *(first + child) < *(first + child + 1))
child++;
if (value < *(first + child)) {
*(first + holeIndex) = *(first + child);
holeIndex = child;
child = 2 * holeIndex + 1;
}
else
break;
}
*(first + holeIndex) = value;
}
3):sort_heap
算法
既然每次 pop_heap
可获得 heap 中键值最大的元素,如果持续对整个 heap 做 pop_heap
操作,每次操作范围从后向前缩减一个元素(因为pop_heap
会把键值最大的元素放在底部容器的最尾端),当整个程序执行完毕时,我们便有了一个递增序列,代码如下:
template <class RanodmAccessIterator>
void sort_heap(RandomAccessIterator first, RnadomAccessIterator last)
{
//以下,每执行一次 pop_heap(),极值(在 STL heap 中为极大值)即被放在尾端。
//扣除尾端再执行一次 pop_heap(),次极值又被放在新尾端,一直下去,最后即地
//排序结果
while (last - first > 1)
pop_heap(first, last--); //每执行 pop_heap() 一次,操作范围即退缩一格
}
4):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));
}
template <class RandomAccessIterator>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last, T*, Distance*)
{
if (last - first < 2) return;
Distance len = last - first;
//找出第一个需要重排的子树头部,以 parent 标示出
//由于任何叶节点都不需要执行下滤,所以有以下计算
Distance parent = (len - 2) / 2;
while (true) {
//重排以 parent 为首的子树,len 是为了让 __adjust_heap()
//判断操作范围
__adjust_heap(first, parent, len, T(*(first + parent)));
if (parent == 0) return; //走完根节点,就结束
parent--;
}
}
二:priority_queue
1:priority_queue
概述
priority_queue
只允许在底端加入元素,并从顶端取出元素,每次取出的元素为优先级别最高的元素。默认情况下,priority_queue
利用一个 max-heap 完成,后者是一个以 vector 表现的 complete binary tree。同样的,其也没有迭代器
2:完整代码
priority_queue
默认情况下是以 vector 为底部容器的,同样的,也是 container adapter。完整代码如下:
template <class T, class Sequence = vector<T>, class Compare = less<typename Sequence::value_type>>
class priority_queue {
public:
typedef typename Sequence::value_type value_type;
typedef typename Sequence::size_type size_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()均为泛型算法
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last, const Compare& x) : c(first, last), comp(x)
{ make_heap(c.begin(), c.end(), comp); }
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last) : c(first, last)
{ 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) {
try {
//push_heap 是泛型算法,先利用底层容器的push_back()将新元素
//推入末端,再重排 heap
c.push_back(x);
push_heap(c.begin(), c.end(), comp);
}
catch(...) {
c.clear();
}
}
void pop() {
try {
//pop_heap 是泛型算法,从 heap 内取出一个元素,它并不是
//真正将元素弹出,而是重排 heap,然后再以底层容器 pop_back()取得弹出的元素
pop_heap(c.begin(), c.end(), comp);
c.pop_back();
}
catch(...) {
c.clear();
}
}
};