C++STL之最大/小堆heap简记

优先队列(priority queue)是由二叉堆(binary heap)实现的,它是一种完全二叉树(complete binary tree)。也就是说,整棵二叉树binary tree除了最底层的叶节点之外,是填满的,而最底层的叶节点由左至右又不得有空隙。

完全二叉树的这个特点,给我们实现二叉堆带来了极大的便利:

  • 使用一个vector来实现这个二叉堆,寻找父节点就是该节点在数组位置/2,寻找子节点就是该节点在数组位置*2以及*2+1(呵呵,想起了最早自己用pascal实现二叉树那会)

以最大堆max-heap为例,介绍下插入与出堆操作

插入操作过程大概描述就是:

  1. 先将要插入的元素的位置(注意我们算法每次只定位位置)定在vector的末尾,
  2. 每次将预定要插入的元素与该位置的父节点比较,如果比父节点大,将父节点移至该位置,该位置改为父节点,然后继续重复如上操作;如果比父节点小结束操作
  3. 将要插入的元素填至要定位的位置

插入操作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;    // 令洞值为新值,完成插入操作
}

出堆操作:

  1. 将根节点与最下层最右边的叶节点对调
  2. 将新的根节点持续下放,直到叶节点
  3. 再对这个节点执行一次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实现的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值