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++标准库源代码