为什么STL priority_queue默认是大根堆?

下面为priority_queue 的模板参数,第一个为数据类型,第二个为容器类型默认vector,第三个为仿函数默认为less。

template <class _Ty, class _Container = vector<_Ty>, class _Pr = less<typename _Container::value_type>>

这里有一点疑惑,传入的仿函数默认为less,为什么priority_queue 默认形成大根堆?

源码剖析

less源码如下,若__Left < __Right,则返回true:
到这里我们就可以知道为什么要通过重载小于号运算符来实现自定义数据类型的优先级队列,而重载大于号不可以,可以推测,STL中像sort也是类似原理。

template <class _Ty = void>
struct less {
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool _RESULT_TYPE_NAME;

    constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const {
        return _Left < _Right;
    }
};

我们来探究一下仿函数最终用在了哪里?首先从构造函数开始,仿函数被赋值给了 comp

priority_queue(const _Pr& _Pred, _Container&& _Cont) : c(_STD move(_Cont)), comp(_Pred) {
    _STD make_heap(c.begin(), c.end(), comp);
}

然后push函数,先在尾部插入一个元素,在向上调整堆,继续看 push_heap 函数:

void push(const value_type& _Val) {
    c.push_back(_Val);
    _STD push_heap(c.begin(), c.end(), comp);
}

一堆乱七八糟看不懂的,直接继续 _Push_heap_by_index

template <class _RanIt, class _Pr>
_CONSTEXPR20 void push_heap(_RanIt _First, _RanIt _Last, _Pr _Pred) {
    // push *(_Last - 1) onto heap at [_First, _Last - 1), using _Pred
    _Adl_verify_range(_First, _Last);
    const auto _UFirst = _Get_unwrapped(_First);
    auto _ULast        = _Get_unwrapped(_Last);
    using _Diff        = _Iter_diff_t<_RanIt>;
    _Diff _Count       = _ULast - _UFirst;
    if (2 <= _Count) {
        _Iter_value_t<_RanIt> _Val = _STD move(*--_ULast);
        _Push_heap_by_index(_UFirst, --_Count, _Diff(0), _STD move(_Val), _Pass_fn(_Pred));
    }
}

最后找到了我们要的东西,代码很抽象,但依稀能辨认出是向上调整的代码

template <class _RanIt, class _Ty, class _Pr>
_CONSTEXPR20 void _Push_heap_by_index(
    _RanIt _First, _Iter_diff_t<_RanIt> _Hole, _Iter_diff_t<_RanIt> _Top, _Ty&& _Val, _Pr _Pred) {
    // percolate _Hole to _Top or where _Val belongs, using _Pred
    using _Diff = _Iter_diff_t<_RanIt>;
    for (_Diff _Idx = (_Hole - 1) >> 1; // shift for codegen
         _Top < _Hole && _DEBUG_LT_PRED(_Pred, *(_First + _Idx), _Val); //
         _Idx = (_Hole - 1) >> 1) { // shift for codegen
        // move _Hole up to parent
        *(_First + _Hole) = _STD move(*(_First + _Idx));
        _Hole             = _Idx;
    }

    *(_First + _Hole) = _STD move(_Val); // drop _Val into final hole
}

关键是这一句, _DEBUG_LT_PRED(_Pred, *(_First + _Idx), _Val),传入的仿函数_Pred 用在了这里,当 *(_First + _Idx) < _Val 时返回 true ,即当父节点的值小于新插入的值时继续调整,less函数返回true,父节点被插入结点换了下去,所以我们才可以得出结论 less函数,返回true时,左边的优先级低于右边的优先级,也就清楚了为什么默认传入less反而形成的是大根堆。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值