聊聊C++11标准库中堆(heap)算法的源码
STL中支持堆操作,对外暴露了std::make_heap
,std::push_heap
,std::pop_heap
,std::sort_heap
,std::is_heap
,std::is_heap_until
这6个函数,详细的使用方法可以参见图解STL中算法的分类、简介及其Demo。
这里我简单一下讲一下我对堆这种数据结构的理解,首先,堆是完全二叉树,这意味着堆可以很方便用数组这种数据结构来表示,看下图:
其次,以大顶堆为例子,最大的元素永远在二叉树的根,根到二叉树的叶子的每条路径都是降序的,因此,调整堆的操作就是在这些路径上做插入排序。
声明一下,以下所有源码都来自MinGW中的stl_heap.h
,我弄了下代码的格式化和写了点注释。
标准库中的代码写的很好,包括变量的命名,都是很形象的。比如holeIndex
,“洞的下标”。
push_heap
/*** @brief Push an element onto a heap.* @param __first Start of heap.* @param __last End of heap + element.* @ingroup heap_algorithms** This operation pushes the element at last-1 onto the valid heap* over the range [__first,__last-1). After completion,* [__first,__last) is a valid heap.*/
template <typename _RandomAccessIterator>
inline void
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
/*
template<typename _RandomAccessIterator, typename _Compare>
inline void
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
*/
{typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType;_ValueType __value = std::move(*(__last - 1));std::__push_heap(__first, _DistanceType((__last - __first) - 1),_DistanceType(0), std::move(__value),__gnu_cxx::__ops::__iter_less_val());// __gnu_cxx::__ops::__iter_comp_val(__comp));
}
这里把最后一个元素,也就是新加入堆的元素(最后一个叶子结点)打了个洞,利用std::move
,这样,最后一个位置是没有值的,这相当于是一个空洞;
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;while (__holeIndex > __topIndex && __comp(__first + __parent, __value)){*(__first + __holeIndex) = std::move(*(__first + __parent));__holeIndex = __parent;__parent = (__holeIndex - 1) / 2;}*(__first + __holeIndex) = std::move(__value);
}
由于出现了空洞,因此需要元素来填上空洞,而且,新加入进来的元素只会影响一条路径(叶子结点到根节点的路径),因此只要在这条路径上做插入排序就好了,把父亲结点移动到孩子结点直到这条路径上满足堆的性质,这样可以填洞,但是又会把父亲结点弄成洞,所以最后用新加入的元素填住最后的洞,其实就是个向上的插入排序。这样,就把新加入的元素融入了堆。
pop_heap
/*** @brief Pop an element off a heap.* @param __first Start of heap.* @param __last End of heap.* @pre [__first, __last) is a valid, non-empty range.* @ingroup heap_algorithms** This operation pops the top of the heap. The elements __first* and __last-1 are swapped and [__first,__last-1) is made into a* 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)
*/
{typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;if (__last - __first > 1){--__last;std::__pop_heap(__first, __last, __last,__gnu_cxx::__ops::__iter_less_iter());// __gnu_cxx::__ops::__iter_comp_iter(__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 = std::move(*__result);*__result = std::move(*__first);std::__adjust_heap(__first, _DistanceType(0),_DistanceType(__last - __first),std::move(__value), __comp);
}
也就是把第一个元素和最后一个元素互换一下,然后把第一个元素的位置打一个洞,之后就调用__adjust_heap
来填洞,其实就是个向下的插入排序。
下图是__adjust_heap
向上提升结点的操作,最后再次调用__push_heap
来做插入排序调整堆。
template <typename _RandomAccessIterator, typename _Distance, typename _Tp, typename _Compare>
void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,_Distance __len, _Tp __value, _Compare __comp)
{const _Distance __topIndex = __holeIndex;_Distance __secondChild = __holeIndex;// 1while (__secondChild < (__len - 1) / 2){__secondChild = 2 * (__secondChild + 1);if (__comp(__first + __secondChild, __first + (__secondChild - 1)))__secondChild--;*(__first + __holeIndex) = std::move(*(__first + __secondChild));__holeIndex = __secondChild;}// 2if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2){__secondChild = 2 * (__secondChild + 1);*(__first + __holeIndex) = std::move(*(__first + (__secondChild - 1)));__holeIndex = __secondChild - 1;}// 3std::__push_heap(__first, __holeIndex, __topIndex, std::move(__value), __gnu_cxx::__ops::__iter_comp_val(__comp));
}
make_heap
/*** @brief Construct a heap over a range.* @param __first Start of heap.* @param __last End of heap.* @ingroup heap_algorithms** This operation makes the elements in [__first,__last) into a heap.*/
template <typename _RandomAccessIterator>
inline void
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
/*
template<typename _RandomAccessIterator, typename _Compare>
inline void
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
*/
{std::__make_heap(__first, __last, __gnu_cxx::__ops::__iter_less_iter());// std::__make_heap(__first, __last, __gnu_cxx::__ops::__iter_comp_iter(__comp));
}
叶子结点本来就是一个合格的堆,那么,就要从最后一个非叶子结点向根节点逐渐调整堆,这样可以使得每一个子堆都是合格的堆,那么整体上也就是一个合格的堆了。
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;if (__last - __first < 2)return;const _DistanceType __len = __last - __first;_DistanceType __parent = (__len - 2) / 2;while (true){_ValueType __value = std::move(*(__first + __parent));std::__adjust_heap(__first, __parent, __len, std::move(__value), __comp);if (__parent == 0)return;__parent--;}
}
sort_heap
/*** @brief Sort a heap.* @param __first Start of heap.* @param __last End of heap.* @ingroup heap_algorithms** This operation sorts the valid heap in the range [__first,__last).*/
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)
*/
{std::__sort_heap(__first, __last,__gnu_cxx::__ops::__iter_less_iter());// __gnu_cxx::__ops::__iter_comp_iter(__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);}
}
is_heap_until
从根结点找一个最大的合格的堆,如果整个数组是个合格的堆,那么返回数组大小。
/*** @brief Search the end of a heap.* @param __first Start of range.* @param __last End of range.* @return An iterator pointing to the first element not in the heap.* @ingroup heap_algorithms** This operation returns the last iterator i in [__first, __last) for which* the range [__first, i) is a heap.*/
template <typename _RandomAccessIterator>
inline _RandomAccessIterator
is_heap_until(_RandomAccessIterator __first, _RandomAccessIterator __last)
/*
template<typename _RandomAccessIterator, typename _Compare>
inline _RandomAccessIterator
is_heap_until(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
*/
{return __first + std::__is_heap_until(__first, std::distance(__first, __last),__gnu_cxx::__ops::__iter_less_iter());// __gnu_cxx::__ops::__iter_comp_iter(__comp));
}
根据堆的性质 – "父亲结点一定大于或者小于两个孩子结点"来依次判断就好了。
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;if ((__child & 1) == 0)++__parent;}return __n;
}
is_heap
之前就说了,如果整个数组是个合格的堆,那么__is_heap_until
返回数组大小,所以is_heap
就是这儿干的。
/*** @brief Determines whether a range is a heap.* @param __first Start of range.* @param __last End of range.* @return True if range is a heap, false otherwise.* @ingroup heap_algorithms*/
template <typename _RandomAccessIterator>
inline bool
is_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{return std::is_heap_until(__first, __last) == __last;// return std::is_heap_until(__first, __last, __comp) == __last;}
聊聊C++11标准库中堆(heap)算法的源码相关推荐
- 【C++标准库】std::string用法指南源码剖析
文章目录 1.ASCII码 (1)计算机如何表达字符 2.C 语言中的字符类型 char (1)思想:char 即整数 (3)C 语言帮手函数 (4)C语言中的字符串 (4)C 语言转义符 3.C++ ...
- C++标准库(第二版).pdf与STL源码剖析.pdf下载
链接:https://pan.baidu.com/s/1KJjkz19AdFd_UHQzBwHd8A 提取码:2191 链接:https://pan.baidu.com/s/1754Oi4BdBE2s ...
- 聊聊C++标准库,准标准库中关于时间的概念和用法
概要 在实际C++业务开发中,经常需要使用系统API或者标准库去获取时间,计算时间的需求,其中,时间按概念又分时间段,时间点:按表达形式又分系统时间,本地时间:其实,获取到了时间,如何通过日志的方式把 ...
- 标准库中的智能指针shared_ptr
智能指针的出现是为了能够更加方便的解决动态内存的管理问题.注:曾经记得有本书上说可以通过vector来实现动态分配的内存的自动管理,但是经过试验,在gcc4.8.5下是不行的.这个是容易理解的,vec ...
- C++标准库中的随机数生成
C++标准库中的随机数生成 一.伪随机与真随机 数字计算机的结果可以说是固定的.必然的.都是根据现有数据的状态得出接下来的状态.除非硬件损坏,计算机不会产生真正的随机和无法预料的事.在生活中随手抛一个 ...
- C++11标准库 - array 1
C++11标准库 - array std::array是原生数组的封装,它存放于栈上且大小固定,性能也与之相同.在原生数组的基础上,它添加了范围检查,以及其它的STL的相应特性,比如复制.交换.迭代器 ...
- C++标准库中的数学函数
C++标准库中的数学函数. 这是一篇我转载的文章,里面有关于数学相关的函数讲解的很详细,供以后自己学习. http://blog.sina.com.cn/s/blog_149e9d2ec0102wxq ...
- C++新特性之五:标准库中的正则表达式扩充
总第34篇 本文主要对C++11/14标准库中的正则表达式的扩充进行详细总结说明,以方便大家在学习工作过程中参考. 1.正则表达式 正则表达式是描述一种字符串匹配的模式.一般使用正则表达式主要实现下面 ...
- C++标准库中各种排序归纳
一.简介 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.我们在编程过程中会经常接触到排序,比如游戏中的排行榜等.C++标准库中提供了各种不同的排序算法,这篇博 ...
最新文章
- linux(以ubuntu为例)下Android利用ant自动编译、修改配置文件、批量多渠道,打包生成apk文件...
- java 密钥工厂 desede_20145212 实验五《Java网络编程》
- Python生成html邮件
- oracle创建数据库步骤
- python 增删列表_python 列表的增删改查
- 云计算对于传统软件工程的影响
- vcf文件(call variants得来的)怎么看变异是纯合还是杂合的
- 学习笔记 | 传统企业互联网改革之道
- 20175323 团队项目 服务器端函数功能与业务逻辑详解
- 也说说angularJs里的evalAsync
- *第二周*数据结构实践项目一【交换】
- Linux红帽认证最全介绍
- php读取mysql单条数据_用PHP框架与原始代码读取Mysql单条数据性能比较
- 词法分析器的java代码_利用Java实现简单的词法分析器实例代码
- 超市管理系统软件测试用例图,超市管理系统用户管理模块测试用例集.doc
- 【舆情聚焦】 乐视危机舆情监测专项报告
- mac:通过鼠标右键 新建文本文档
- dimens文件生成器使用方法
- 无法使用内置管理员账户打开应用商店
- 独家专访丨刘江川:从“边缘”到“中心”,边缘计算科学家的创业之路