STL源码笔记(14)—堆和优先级队列(一)
来源:互联网 发布:剑三男捏脸数据 编辑:程序博客网 时间:2024/06/07 17:17
STL源码笔记(14)—堆和优先级队列
priority_queue是拥有权值观念的queue,跟queue类似,其只能在一端push,一端pop,不同的是,每次push元素之后再容器内部元素将按照一定次序排列,使得pop得到的元素始终是当前权值的极大值。
很显然,满足这个条件就需要某些机制了,缺省情况下使用max-heap大顶堆来实现,联想堆排序的实现,使用大顶完成序列从小到大的排序,过程大概是:
- 把堆的根元素(堆中极大值)交换到最后
- 堆的长度减1
这样每次取出堆中的极大值完成排序,刚好与优先级队列要求的权值最高类似,因此可以利用这个特性完成优先级队列。
对于一个优先级队列来说,他使用vector<T>
作为其底层默认容器,与stack,queue类似,priority_queue也是一个adapter(配接器),被归类为container adapter
注:在SGI STL源码中priority_queue的源码位于文件stl_queue.h中,heap算法的源码位于stl_heap.h中。
heap算法
heap算法是实现优先级队列的核心。主要有:
1.push_heap()
2.pop_heap()
3.__adjust_heap()
4.sort_heap()
5.make_heap()
除了__adjust_heap()外,其余四个都是可以在外部使用的函数,如果要实现堆排序的话可以直接使用上述4,5函数。
push_heap函数
书中给了一个很形象的表述叫做percolate up(上溯)操作,这里假设:
在容器vector中,前n个元素(索引0~n-1)为已经排好的max-heap,现在待插入元素的索引为n,这里,程序会在这里挖个坑,称之为holeindex
,可以理解为我现在要插入一个元素,但是我并不知道要插在哪里,所以我先挖一个坑,然后经过一系列的算法,这个holeindex
,要开始慢慢往上爬,以满足大顶堆条件,其实这里的上爬做了两件事:
- 将比value小的parent节点往下拉
- 将原来的holeindex往上拉,占据原来parent所在的位置
持续操作,直到holeindex爬到顶端(根节点),以及满足大顶堆的条件了,此时holeindex已经上溯到正确的位置,只需要将value值填进去即可。
template <class _RandomAccessIterator, class _Distance, class _Tp, class _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)) {//__comp默认是小于:父节点小于子节点 //调整hole的位置,如果满足条件hole将一直上溯 *(__first + __holeIndex) = *(__first + __parent); __holeIndex = __parent; __parent = (__holeIndex - 1) / 2; } *(__first + __holeIndex) = __value;//把value放到新的位置}template <class _RandomAccessIterator, class _Compare, class _Distance, class _Tp>inline void __push_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, _Distance*, _Tp*) { //直接调用__push_heap __push_heap(__first, _Distance((__last - __first) - 1), _Distance(0), _Tp(*(__last - 1)), __comp);}template <class _RandomAccessIterator, class _Compare>inline void push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp){ __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __push_heap_aux(__first, __last, __comp, __DISTANCE_TYPE(__first), __VALUE_TYPE(__first));}
pop_heap与__adjust_heap函数
与push_heap对应的就是“出堆”操作了,在堆排序中,我们对已经建好的堆进行“出堆”,操作是每次将堆顶根元素换到容器末尾,先对堆的长度减1,再对堆进行调堆操作,重复进行直到堆中所有元素出来,由于出堆操作每次出的是堆顶元素,也就是极大值,因此完成出堆操作后就完成排序工作。
pop_heap与push_heap对应的是percolate down(下溯)操作,这里很容易理解因此堆顶的根元素被取走以后,这里原来的根元素就是一个没有元素的空节点了,也就是所谓的holeindex,这里就是要将这个holeindex调整到合适的位置,holeindex之前的元素是一个堆,实际上做的工作是:
- 找到holeindex对应两个孩子中较大的将其换到holeindex的位置
- 将holeindex下溯到上述较大孩子的索引的位子
- 重复执行上述操作,最终holeindex将落在某个叶子节点处。
上述第2点需要考虑没有右子树的情况,例如:
当下溯到(索引2处)原65节点时,此时没有右节点,右节点索引second==len
由于此时并没有考虑从容器末尾换出来的value值,所以要对堆的索引为 0~holeindex(模拟一下过程可以很容易得到此时的holeindex位于叶子节点处) 的元素进行一次push_heap操作,使得value插入到合适的位置。
所以说,pop_heap的工作实际上很简单,主要任务是极大值出堆后要对剩余的元素进行重新调整,使得其成一个堆。
//4.template <class _RandomAccessIterator, class _Distance, class _Tp>void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __len, _Tp __value){ _Distance __topIndex = __holeIndex;//holeindex在顶端 _Distance __secondChild = 2 * __holeIndex + 2;//从右孩子开始 while (__secondChild < __len) {//表示holeindex还没有到叶子节点,要不停下溯 //取左右孩子的最大值 if (*(__first + __secondChild) < *(__first + (__secondChild - 1))) __secondChild--; *(__first + __holeIndex) = *(__first + __secondChild);//较大的孩子上调 __holeIndex = __secondChild;//holeindex下溯 __secondChild = 2 * (__secondChild + 1); } if (__secondChild == __len) {//当前holeindex只有左孩子 *(__first + __holeIndex) = *(__first + (__secondChild - 1)); __holeIndex = __secondChild - 1; } __push_heap(__first, __holeIndex, __topIndex, __value);//重新插入}//3.template <class _RandomAccessIterator, class _Tp, class _Distance>inline void __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __result, _Tp __value, _Distance*){ *__result = *__first;//首先将堆中极大值赋给堆末尾位置 //这里的last已经完成减1操作 //调堆操作 __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);}//2.template <class _RandomAccessIterator, class _Tp>inline void __pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp*){//直接调用,这里已经获取堆中最后一个元素的值,堆中最后一个元素的迭代器变量 __pop_heap(__first, __last - 1, __last - 1, _Tp(*(__last - 1)), __DISTANCE_TYPE(__first));}//1.template <class _RandomAccessIterator>inline void pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last){ __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, _LessThanComparable); __pop_heap_aux(__first, __last, __VALUE_TYPE(__first));//直接调用}
make_heap和heap_sort
这两个函数实现了堆排序。
make_heap是将一段现有的数据转化成一个heap,操作就是,从第一个非叶子节点向前进行调堆操作,为什么要选择第最后一个非叶子节点呢?因为,调堆操作是对holeindex进行下溯,现在你都是叶子节点了还怎么下?
至于最后一个非叶子节点的计算公式:
其中len是数组的长度。
heap_sort函数就简单了,不断的执行pop_heap操作即可:
template <class _RandomAccessIterator, class _Tp, class _Distance>void __make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp*, _Distance*){ if (__last - __first < 2) return; _Distance __len = __last - __first; //找出第一个需要重排的子树头部(因为需要执行perlocate down操作,而叶子节点不需要执行该操作) _Distance __parent = (__len - 2)/2; while (true) { __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent))); if (__parent == 0) return; __parent--; }}//将一段现有的数据转化成一个heap...在堆排序中就是build_heaptemplate <class _RandomAccessIterator>inline void make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last){ __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, _LessThanComparable); __make_heap(__first, __last, __VALUE_TYPE(__first), __DISTANCE_TYPE(__first));}
//每次将一个极大值放到尾端template <class _RandomAccessIterator>void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last){ __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, _LessThanComparable); while (__last - __first > 1) pop_heap(__first, __last--);}
- STL源码笔记(14)—堆和优先级队列(一)
- STL源码笔记(15)—堆和优先级队列(二)
- 数据结构学习笔记(六)优先级队列、堆和左高树
- 优先队列(堆)优先级设置笔记
- 堆(优先级队列)
- 优先级队列(二叉堆)
- 堆和优先级队列
- 堆和优先级队列
- STL源码笔记(13)—序列式容器之栈和队列
- 数据结构应用标准模版库STL——优先级队列(优先级队列排序)
- 经典算法和数据结构(一) 优先级队列与堆排序
- C++ STL优先队列(STL堆)
- 堆排序和优先级队列
- 堆排序和优先级队列
- 数据结构——优先级队列(主要是堆)
- 数据结构—堆排序及其应用(优先级队列)
- 堆与优先级队列研究(C++)
- 优先级队列(可用堆实现)
- Linux入门操作小技巧(持续更新)
- Spring基础 Quartz的配置
- 如何用Eclipse把第三方Jar打到Jar里,做成FatJar(包含Eclipse插件安装失败的解决办法)
- java的位运算符(>>,<<,>>>,&,|)
- 设计模式之建造者模式(Builder)
- STL源码笔记(14)—堆和优先级队列(一)
- 第三十四课 二维数组的存储 【项目1-3】
- date获取年月日
- 设计模式之装饰模式
- mysql数据库---批处理与大文本/图片类型
- 服务器基础 tomcat端口被占用问题的解决
- 10815 - Andy's First Dictionary
- HDU2059 多段决策
- Eclipse将引用了第三方jar包的Java项目打包成jar文件的两种方法