前言
堆数据结构可以有两种表示方法
-
用指针表示
-
用一维数组表示
堆是完全二叉树,索引从0开始,满足下述条件
若父节点索引为 i ,则它的左孩子节点索引为 2i+1 ,右孩子节点索引为 2i + 2
若孩子节点(包括左右孩子)索引为 i ,则其父节点为 (i-1)/2 向下取整
push_heap
template<class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first,
RandomAccessIterator last)
{
// 此函数调用时,新元素已置于底部容器的最尾端
__push_heap_aux(first, last, distance_type(first), value_type(first));
}
template<class RandomAccessIterator, 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
}
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){
// 当尚未到达顶端,且父节点小于新值,STL heap是一种max-heap
*(first + holeIndex) = *(first + parent); //令洞值为父值
holeIndex = parent; // percolate up:调整洞号,向上提升至父节点
parent = (holeIndex - 1) / 2; //新洞的父节点
} //持续至顶端,或满足heap的次序特性
*(first + holeIndex) = value; //令子节点值为新值,完成插入操作
}
pop_heap
将heap中最后一个元素暂存到value中,将堆顶元素“弹出”到最后,此时[first, last-1)为没有堆顶的堆,通过__adjust_heap将堆[first, last-1)调整,最后剩下个holeIndex被放入value,然后[top, holeIndex]就变成了除了holeIndex元素外其它的满足堆特性,所以用__push_heap调整之,至此,pop_heap完成
//将堆顶元素弹出(放入容器末位)
template<class RandomAccessIterator>
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));
}
template<class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first,
RandomAccessIterator last,
RandomAccessIterator result,
T value, Distance*)
{
// 容器的最后一个元素的值被覆盖,它的值现存在value里
*result = *first;
__adjust_heap(first, Distance(0), Distance(last - first), value);
}
template<class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
Distance len, T value)
{
// value 存储容器中最后一个元素值(改写之前)
Distance topIndex = holeIndex;
Distance secondChild = 2 * holeIndex + 2; // 右孩子,最大为last-first-1
// 右孩子小于区间长度[first, last) [0,... ,last-first-1]
// 在堆中
while (secondChild < len){
//比较洞(父)节点左右两个子值,然后以secondChild代表较大子节点
if (*(first+secondChild) < *(first+secondChild-1)){
--secondChild;
}
// 令较大的子节点值为洞值,再令洞号下移至较大子结点处
*(first + holeIndex) = *(first + secondChild);
holeIndex = secondChild;
// 找出新洞的右孩子
secondChild = 2 * secondChild + 2;
}
// index 最大值为len-1
if (secondChild == len){ //没有右子节点,只有左子节点
// percolate down
// 此时holeIndex已经等于secondChild
*(first + holeIndex) = *(first + (secondChild - 1));
holeIndex = secondChild - 1;
}
// 下面的侯捷之见不对,不能改,必须用__push_heap,
//
//将想要调整值填入目前的洞号内,此时肯定满足次序特性?
// 依侯捷之见 *(first + holeIndex) = value; 应该也可
__push_heap(first, holeIndex, topIndex, value);
}
make_heap
// make_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, class T, class Distance>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last,
T*, Distance*)
{
if (last - first < 2) return ; //如果长度为0,1不必排列
Distance len = last - first;
// 找出第一个需要重排的子树头部,以parent标示出。由于任何叶子节点都不需执行
// perlocate down,所以有以下计算。parent命名不佳,名为holeIndex更好
// len-1最大索引值, (i-1)/2得到父节点
Distance parent = ( len - 1 -1 ) / 2;
while (true){
// 重排以parent为首得子树。len是为了让__adjust_heap()判断操作范围
__adjust_heap(first, parent, len, T(*(first + parent)));
if (parent == 0){
return ; // 走完根节点就结束
}
--parent; // 即将重排之子树得头部向前一个节点
}
}
sort_heap
// sort_heap算法,前提必须符合heap条件
template<class RandomAccessIterator>
void sort_heap(RandomAccessIterator first,
RandomAccessIterator last)
{
// 每执行一次pop_heap(),极值(在STL heap中为极大)即被放在尾端
// 扣除尾端再执行一次pop_heap(),次极值又被放在尾端,如此排序
while (last - first > 1){
pop_heap(first, last--);
}
}
总结
- 堆是完全二叉树,可以用指针或者数组表示。
- push_heap的前提是最后一个元素之前满足堆条件,push_heap将最有一个元素插入堆中,插入后整个数组又都满足堆条件。
- pop_heap的前提是整个数组必须满足堆条件,pop_heap将堆顶元素即数组第一个元素弄到数组最后一个元素的位置(最后一个元素暂存起来),然后将剩余元素调整成堆。
- heap可用于解决 Top N 问题。