优先队列(priority queue)是由二叉堆(binary heap)实现的,它是一种完全二叉树(complete binary tree)。也就是说,整棵二叉树binary tree除了最底层的叶节点之外,是填满的,而最底层的叶节点由左至右又不得有空隙。
完全二叉树的这个特点,给我们实现二叉堆带来了极大的便利:
- 使用一个vector来实现这个二叉堆,寻找父节点就是该节点在数组位置/2,寻找子节点就是该节点在数组位置*2以及*2+1(呵呵,想起了最早自己用pascal实现二叉树那会)
以最大堆max-heap为例,介绍下插入与出堆操作
插入操作过程大概描述就是:
- 先将要插入的元素的位置(注意我们算法每次只定位位置)定在vector的末尾,
- 每次将预定要插入的元素与该位置的父节点比较,如果比父节点大,将父节点移至该位置,该位置改为父节点,然后继续重复如上操作;如果比父节点小结束操作
- 将要插入的元素填至要定位的位置
插入操作STL源码如下:
template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last)
{
// 此函数被调用时,新元素应置于底部容器的最尾端
__push_heap_aux(first, last, distance_type(first), value_type(first));
}
template <class RandomAccessItertor, 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)));
// 以上系根据implicit representation heap的结构特性:新值必须置于底部
// 容器的最尾端,此即为第一个洞号:(last-first)-1
}
// 以下这组push_back()不允许指定“大小比较标准”
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)
{
// 当尚未到达顶端,且父节点小于新值(于是不符合heap的次序特性)
// 由于以上使用operator<,可知STL heap是一种max-heap(大者为父)
*(first +holeIndex) = *(first + parent); // 令洞值为父值
holeIndex = parent; // percolate up:调整洞号,向上提升至父节点
parent = (holeIndex -1) / 2; // 新洞的父节点
} // 持续至顶端,或满足heap的次序特性为止
*(first + holeIndex) = value; // 令洞值为新值,完成插入操作
}
出堆操作:
- 将根节点与最下层最右边的叶节点对调
- 将新的根节点持续下放,直到叶节点
- 再对这个节点执行一次percolate up(上溯)算法(可以认为就是添加时的操作)
出堆STL源码如下:
template <class RandomAccessIterator>
inline coid 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));
// 以上,根据 implicit representation 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);
// value为原尾值,原尾值放到根节点,并重新调整heap
}
// 以下这个__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--;
// 令较大子值为洞值,再令洞号下移至较大子节点处
*(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);
}
可以看出,在堆中的元素插入与极值的取得就有O(logN)的表现
STL中的优先队列priority queue的底层就是由heap实现的