STL算法与数据结构---堆有关操作以及优先队列

STL算法与数据结构—堆有关操作以及优先队列

本文将STL中的堆有关算法,做了些整理,以及扒出了它的源代码(去除了许多不必要的版本控制代码),并简单的注释了他们的大概运行过程。除示例代码以外,其余代码,均可以在GNU7.2.0中找到。

堆操作汇总

Heap operations,Defined in header <algorithm>
is_heap(C++11) checks if the given range is a max heap (function template)
is_heap_until(C++11) finds the largest subrange that is a max heap (function template)
make_heap creates a max heap out of a range of elements (function template)
push_heap adds an element to a max heap (function template)
pop_heap removes the largest element from a max heap (function template)
sort_heap turns a max heap into a range of elements sorted in ascending order (function template)

is_heap_until()(C++11)

先介绍这个函数,因为之后的函数有调用它。如前表所述,这个函数返回满足堆性质的最大子范围(从传入的first开始的范围),并且返回一个迭代器(first到这个迭代器是一个堆,左闭右开区间)。

主要有两个函数原型,一个是不带比较仿函数的,另一个是需要自定义比较仿函数的。另外他们都需要传入随机访问迭代器。并且他们的输出是随机访问迭代器。

template<typename _RandomAccessIterator>
_RandomAccessIterator is_heap_until(_RandomAccessIterator __first, _RandomAccessIterator __last);

 template<typename _RandomAccessIterator, typename _Compare>
_RandomAccessIterator is_heap_until(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare __comp)

具体实现:

//定义于stl_heap.h
//为了方便理解,把concept requirements那部分代码删去

//返回类型是随机访问迭代器
template<typename _RandomAccessIterator> 
inline _RandomAccessIterator is_heap_until(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    //这里在定义一个仿函数,就是一个比较函数,访问迭代器元素并比较大小,其实现见之后代码
    __gnu_cxx::__ops::_Iter_less_iter __comp;
    //返回的是_first+最大堆长度,调用了__is_heap_until来返回最大堆长度
    return __first + 
        std::__is_heap_until(__first, std::distance(__first, __last), __comp);
}


template<typename _RandomAccessIterator, typename _Compare>
inline _RandomAccessIterator is_heap_until(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare __comp)
{
    //重命名_Compare,__decltype(__comp)返回了_comp的类型
    typedef __decltype(__comp) _Cmp;
    //定义比较仿函数
    __gnu_cxx::__ops::_Iter_comp_iter<_Cmp> __cmp(_GLIBCXX_MOVE(__comp));
    //返回的是_first+最大堆长度,调用了__is_heap_until来返回最大堆长度
    //distance是STL中计算两个迭代器的长度函数(左闭右开)。
    return __first + std::__is_heap_until(__first, std::distance(__first, __last), __cmp);
}

//_it1的元素如若小于_it2的元素,则返回true,结合__is_heap_until的实现,可知默认在判断是否为大根堆
struct _Iter_less_iter
{
    template<typename _Iterator1, typename _Iterator2>
    bool operator()(_Iterator1 __it1, _Iterator2 __it2) const
    { return *__it1 < *__it2; }
};

//来看看__is_heap_until的实现
template<typename _RandomAccessIterator, typename _Distance,typename _Compare>
_Distance __is_heap_until(_RandomAccessIterator __first, _Distance __n,_Compare& __comp)
{
    //从第一个元素开始验证
    _Distance __parent = 0;
    for (_Distance __child = 1; __child < __n; ++__child)
    {
        //判断父节点与子节点是否满足堆的性质,不满足则返回当前长度
        if (__comp(__first + __parent, __first + __child))
            return __child;
        //如为偶数,__parent往后遍历,为偶数时,验证完两个孩子了,所以该往后遍历了
        if ((__child & 1) == 0)
            ++__parent;
    }
    return __n;
}

is_heap()(C++11)

这个函数判断给定序列是否为堆。其函数原型有:

template<typename _RandomAccessIterator>
bool is_heap(_RandomAccessIterator __first, _RandomAccessIterator __last);

template<typename _RandomAccessIterator, typename _Compare>
bool is_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare __comp)

具体实现;

//它的实现交给了is_heap_until()来实现
template<typename _RandomAccessIterator>
inline bool is_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{ 
    //判断最大长度堆的后一个元素是否与__last相等
    return std::is_heap_until(__first, __last) == __last; 
}

//它的实现交给了__is_heap_until()来实现
template<typename _RandomAccessIterator, typename _Compare>
inline bool is_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare __comp)
{
    //获取当前序列长度
    const auto __dist = std::distance(__first, __last);
    //重命名仿函数类型
    typedef __decltype(__comp) _Cmp;
    //_GLIBCXX_MOVE:  #define _GLIBCXX_MOVE(__val) std::move(__val)
    //右值引用,构造仿函数
    __gnu_cxx::__ops::_Iter_comp_iter<_Cmp> __cmp(_GLIBCXX_MOVE(__comp));
    //判断最大堆长度是否与当前序列相等
    return std::__is_heap_until(__first, __dist, __cmp) == _dist;
}

push_heap()

看看它的函数声明:

template< class RandomIt >
void push_heap( RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
void push_heap( RandomIt first, RandomIt last,Compare comp);

它的功能,是在已有堆[first,last-1),插入元素*(last-1)。如若[first,last-1)不是堆,不会得到正确的结果。其中Compare是自定义的仿函数类型。

其实现如下(为方便起见,只说明一个例子):

template<typename _RandomAccessIterator>
void push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    //萃取类型
    typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;
    typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType;
	//定义比较仿函数
    __gnu_cxx::__ops::_Iter_less_val __comp;
   	
    _ValueType __value = _GLIBCXX_MOVE(*(__last - 1));
    std::__push_heap(__first, _DistanceType((__last - __first) - 1),_DistanceType(0), _GLIBCXX_MOVE(__value), __comp);
}

template<typename _RandomAccessIterator, typename _Distance, typename _Tp,typename _Compare>
void __push_heap(_RandomAccessIterator __first,_Distance __holeIndex, _Distance __topIndex, _Tp __value, _Compare& __comp)
{
    //这是可能含有叶节点的最后一个位置,之前的节点全是包含叶节点
    _Distance __parent = (__holeIndex - 1) / 2; 
    //做两个判断,一是__holeIndexh还在已知堆里,以及不满足堆性质在,则再向上寻找__value正确的位置,知道找到为止
    while (__holeIndex > __topIndex && __comp(__first + __parent, __value))
    {
        //将不满足性质的父节点往子节点放
        *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent));
        //到父节点上去寻找是否满足堆性质
        __holeIndex = __parent;
        //父节点的父节点
        __parent = (__holeIndex - 1) / 2;
    }
    //找到正确的位置后
    *(__first + __holeIndex) = _GLIBCXX_MOVE(__value);
}

make_heap()

建堆,其过程,在我的这篇博客有描述,是一个不断维护堆的过程。在STL实现中也是类似的。

template<typename _RandomAccessIterator>
inline void make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    __gnu_cxx::__ops::_Iter_less_iter __comp;
    std::__make_heap(__first, __last, __comp);
}

template<typename _RandomAccessIterator, typename _Compare>
inline void make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare __comp)
{
    typedef __decltype(__comp) _Cmp;
    __gnu_cxx::__ops::_Iter_comp_iter<_Cmp> __cmp(_GLIBCXX_MOVE(__comp));
    std::__make_heap(__first, __last, __cmp);
}

template<typename _RandomAccessIterator, typename _Compare>
void __make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare& __comp)
{
    //萃取类型
    typedef typename iterator_traits<_RandomAccessIterator>::value_type  _ValueType;
    typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType;
	
    //当序列长度不足2时,直接返回
    if (__last - __first < 2)
        return;
    //计算所建堆长堆
    const _DistanceType __len = __last - __first;
    //这是可能含有叶节点的最后一个位置,之前的节点全都包含叶节点
    _DistanceType __parent = (__len - 2) / 2;
    
    //从含有叶节点的第一个节点开始建堆
    while (true)
    {
        //_GLIBCXX_MOVE:  #define _GLIBCXX_MOVE(__val) std::move(__val)
        //提出父节点,从父节点开始调整以它为根节点的树成为堆
        _ValueType __value = _GLIBCXX_MOVE(*(__first + __parent));
        std::__adjust_heap(__first, __parent, __len, _GLIBCXX_MOVE(__value),__comp);
        if (__parent == 0)
            return;
        __parent--;
    }
}
//调整堆,以__holeIndex为根节点的树,除去以__holeIndex节点为堆,
template<typename _RandomAccessIterator, typename _Distance,typename _Tp, typename _Compare>
void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,_Distance __len, _Tp __value, _Compare __comp)
{
    //调整堆的过程可以看做是,把除__holeIndex的所有节点调整为一个堆,然后把这个值push进堆中
    //这是根节点
    const _Distance __topIndex = __holeIndex;
    //孩子节点,还未计算,保存的是根节点
    _Distance __secondChild = __holeIndex;
    
    //拥有两个叶节点的处理方式
    while (__secondChild < (__len - 1) / 2)
    {
        //计算第二个孩子节点
        __secondChild = 2 * (__secondChild + 1);
        //在子节点上挑一个最小/大的节点,作为跟节点
        if (__comp(__first + __secondChild,__first + (__secondChild - 1)))
            __secondChild--;
        //将__holeIndex下放,直到为叶节点为止
        *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));
        __holeIndex = __secondChild;
    }
    
    //如果只有一个叶节点
    if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2)
    {
        __secondChild = 2 * (__secondChild + 1);
        *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + (__secondChild - 1)));
        __holeIndex = __secondChild - 1;
    }
    //最终除__value以外,已经变成堆了,再将__value压入堆中
    __decltype(__gnu_cxx::__ops::__iter_comp_val(_GLIBCXX_MOVE(__comp))) __cmp(_GLIBCXX_MOVE(__comp));
    std::__push_heap(__first, __holeIndex, __topIndex,_GLIBCXX_MOVE(__value), __cmp);
}

pop_heap()

其函数声明:

template<typename _RandomAccessIterator>
inline void pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last);

template<typename _RandomAccessIterator, typename _Compare>
inline void pop_heap(_RandomAccessIterator __first,_RandomAccessIterator __last, _Compare __comp);

其功能就是将堆顶元素放到堆尾,并使[_first, _last-1)仍然是堆。下面是具体实现代码,只拿第一个作为例子。

template<typename _RandomAccessIterator>
inline void pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    if (__last - __first > 1)
    {
        --__last;
        __gnu_cxx::__ops::_Iter_less_iter __comp;
        //注意,如若长度大于1,__last指向后一个元素,__last-__first表示原来的长度减一
        std::__pop_heap(__first, __last, __last, __comp);
    }
}

template<typename _RandomAccessIterator, typename _Compare>
inline void __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,_RandomAccessIterator __result, _Compare& __comp)
{
    typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;
    typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType;
	//获取最后一个元素值,用它参与建堆
    _ValueType __value = _GLIBCXX_MOVE(*__result);
    //将顶部元素,放入最后一个位置
    *__result = _GLIBCXX_MOVE(*__first);
    //这里的长度比传入时少1。这里表示把除最后一个元素外,调整堆,并且最后一个元素,已经被顶部元素赋值。
    std::__adjust_heap(__first, _DistanceType(0),_DistanceType(__last - __first),_GLIBCXX_MOVE(__value), __comp);
}

sort_heap()

其函数声明:

template<typename _RandomAccessIterator>
inline void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last);


template<typename _RandomAccessIterator, typename _Compare>
inline void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare __comp);

它的功能是:对于传入的堆,按照指定的比较函数(默认的比较函数在为大根堆,从小到大排序)将其排序

template<typename _RandomAccessIterator>
inline void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    __gnu_cxx::__ops::_Iter_less_iter __comp;
    std::__sort_heap(__first, __last, __comp);
}

//不断将堆顶元素置于尾部 
template<typename _RandomAccessIterator, typename _Compare>
void __sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,_Compare& __comp)
{
    while (__last - __first > 1)
    {
        --__last;
        std::__pop_heap(__first, __last, __last, __comp);
    }
}

优先队列

STL中的优先队列,需要指定一个容器(支持随机访问),以及比较函数。它成员函数的具体实现,都是由之前介绍的堆操作完成的。

其定义;

template<
    class T,//数据类型
    class Container = std::vector<T>,//容器类型
    class Compare = std::less<typename Container::value_type>//比较函数
> class priority_queue;

其成员函数有:

public成员函数
top 访问堆顶元素。函数声明:const_reference top() const;
empty 检查容器是否为空。函数声明:bool empty() const;
size 返回容器元素。函数声明:size_type size() const;
push 压入新元素进堆。
void push( const value_type& value );
void push( value_type&& value );
emplace(C++11) 压入新元素进堆。
可以通过这些参数直接构造新元素
emplate< class… Args >
void emplace( Args&&… args );
pop 将栈顶元素弹出,先放到尾部,在pop_back()。
void pop();
swap 交换内容。
void swap(priority_queue& __pq);

其实现代码:删除了许多版本相关的代码以及不常用构造函数,留下这些相对来说比较常用的函数。

template<typename _Tp, typename _Sequence = vector<_Tp>,typename _Compare  = less<typename _Sequence::value_type> >
class priority_queue
{
    
    public:
    	//类型定义
        typedef typename	_Sequence::value_type		value_type;
        typedef typename	_Sequence::reference		 reference;
        typedef typename	_Sequence::const_reference	   const_reference;
        typedef typename	_Sequence::size_type		 size_type;
        typedef				_Sequence			    container_type;
        // _GLIBCXX_RESOLVE_LIB_DEFECTS
        // DR 2684. priority_queue lacking comparator typedef
        typedef	       _Compare				    value_compare;

    protected:
        //  数据成员
        _Sequence  c;
        _Compare   comp;

    public:
    	//可以从下面的代码中获悉,无论是构造函数,还是一般操作,都通过序列的操作,或者算法中堆操作实现。
        explicit priority_queue(const _Compare& __x, const _Sequence& __s): c(__s), comp(__x)
            { std::make_heap(c.begin(), c.end(), comp); }

        explicit priority_queue(const _Compare& __x, _Sequence&& __s = _Sequence()): c(std::move(__s)), comp(__x)
            { std::make_heap(c.begin(), c.end(), comp); }


        template<typename _InputIterator>
    	priority_queue(_InputIterator __first, _InputIterator __last,const _Compare& __x,const _Sequence& __s)
        : c(__s), comp(__x)
        {
            __glibcxx_requires_valid_range(__first, __last);
            c.insert(c.end(), __first, __last);
            std::make_heap(c.begin(), c.end(), comp);
        }

        template<typename _InputIterator>
        priority_queue(_InputIterator __first, _InputIterator __last,const _Compare& __x = _Compare(),
                       _Sequence&& __s = _Sequence())
        : c(std::move(__s)), comp(__x)
        {
            __glibcxx_requires_valid_range(__first, __last);
            c.insert(c.end(), __first, __last);
            std::make_heap(c.begin(), c.end(), comp);
        }
	
        /**
         *  Returns true if the %queue is empty.
         */
        bool empty() const
        { return c.empty(); }
        /**  Returns the number of elements in the %queue.  */
        size_type size() const
        { return c.size(); }

        /**
        *  Returns a read-only (constant) reference to the data at the first
        *  element of the %queue.
        */
        const_reference top() const
        {
            __glibcxx_requires_nonempty();
            return c.front();
        }

        void push(const value_type& __x)
        {
            c.push_back(__x);
            std::push_heap(c.begin(), c.end(), comp);
        }

        void pop()
        {
            __glibcxx_requires_nonempty();
            std::pop_heap(c.begin(), c.end(), comp);
            c.pop_back();
    	}
    	
    	template<typename... _Args>
        void emplace(_Args&&... __args)
        {
          c.emplace_back(std::forward<_Args>(__args)...);
          std::push_heap(c.begin(), c.end(), comp);
        }
		
    	void swap(priority_queue& __pq)
        {
            using std::swap;
            swap(c, __pq.c);
            swap(comp, __pq.comp);
        }
};

使用示例

/*
STL中的堆操作以及优先队列的使用
*/
#include <algorithm>
#include <vector>
#include <queue>
#include <iostream>
#define _GLIBCXX_DEBUG
using namespace std;

template<typename T>
inline void showContainer(T a){
    for(auto i : a){
        cout<<i<<",";
    }
    cout<<endl;
}
int main(){
    vector<int> v1 = {21,42,36,4,57,6,7,77,99};

    cout<<"v1是否为堆:"<<is_heap(v1.begin(),v1.end())<<endl;
    cout<<"v1堆最大长度:"<<distance(v1.begin(),is_heap_until(v1.begin(),v1.end()))<<endl;
    //默认为大根堆
    //[v1.begin(),v1.end()-1)之间建堆
    make_heap(v1.begin(),v1.end()-1);
    showContainer(v1);
    //压入尾元素
    push_heap(v1.begin(),v1.end());
    showContainer(v1);
    //将栈顶元素放入最后一个位置
    pop_heap(v1.begin(),v1.end());
    showContainer(v1);
    //排序,默认大根堆,从小到大排序
    sort_heap(v1.begin(),v1.end()-1);
    showContainer(v1);

    v1 = {21,42,36,4,57,6,7,77,99};
    //小根堆,指定比较函数
    make_heap(v1.begin(),v1.end(),greater<int>());
    showContainer(v1);

    //排序,从大到小排序
    sort_heap(v1.begin(),v1.end(),greater<int>());
    showContainer(v1);

    //优先队列,默认最大堆,大根堆,最大优先
    //v1 = {21,42,36,4,57,6,7,77,99};
    v1 = {21,42,36,4,57,6,7,77,99};
    priority_queue<int> v2(v1.begin(),v1.end());
    cout<<v2.top()<<endl;
    while(!v2.empty()){
        cout<<v2.top()<<",";
        v2.pop();   
    }
    
    return 0;
}
/**输出如下
v1是否为堆:0
v1堆最大长度:1
77,57,36,42,21,6,7,4,99,
99,77,36,57,21,6,7,4,42,
77,57,36,42,21,6,7,4,99,
4,6,7,21,36,42,57,77,99,
4,21,6,42,57,36,7,77,99,
99,77,57,42,36,21,7,6,4,
99
99,77,57,42,36,21,7,6,4,
**/

Reference

1.https://en.cppreference.com/w/cpp/algorithm

2.GNU7.2.0C++标准库源代码

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值