您的位置:首页 > 产品设计 > UI/UE

Chapter 4: 序列式容器之 heap and priority queue

2017-01-12 10:20 260 查看

一: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();
}
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: