STL算法_heap算法篇
堆简介
二叉堆是一种完全二叉树,即整棵二叉树中除了最底层的叶子节点之外,其余节点是填满的,而最底层的叶子节点由左到右也是填满的不能存在空隙。
堆主要包括两类:大顶堆和小顶堆。大顶堆指每个节点的键值(key)都大于或等于其叶子节点的键值,而小顶堆指每个节点键值都小于等于其节点的键值。STL中堆主要使用vector/array实现的。大顶堆的最大值在根节点,故其总是位于底层的vector/array的起头处;小顶堆的最小值在根节点,故其总是位于vector/array的起头处。
此算法只要存在于stl_heap.h中,主要有make_heap(),push_heap(),pop_heap(),sort_heap()。
make_heap()算法
此算法用于将一段现有的数据转化为一个heap,即构建二叉堆的过程。
// 以下这组make_heap()不允许指定“大小比较标准” template <class _RandomAccessIterator, class _Tp, class _Distance> void __make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp*, _Distance*) { if (__last - __first < 2) return; // 如果长度为0或1,不必重新排列 _Distance __len = __last - __first; // 找出第一个需要重排的子树头部,以parent标示出。由于任何叶节点都不需执行percolate down,所以 // 有以下计算。parent命名不佳,名为holeIndex更好 _Distance __parent = (__len - 2)/2; while (true) { // 重排以parent为首的子树。len是为了让__adjust_heap()判断操作范围 __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent))); if (__parent == 0) return; // 走完根节点,就结束 __parent--; // (已重排之子树的)头部向前一个节点 } } // 利用所给的数组建立堆,其主要涉及调整底部vector中元素使其满足堆的条件 // 将[first, last)排列为一个heap // 版本1 template <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, class _Compare, class _Tp, class _Distance> void __make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, _Tp*, _Distance*) { if (__last - __first < 2) return; _Distance __len = __last - __first; // 计算堆的长度 _Distance __parent = (__len - 2)/2; // 计算根节点位置 while (true) { // 调整排列元素,使其满足堆 __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent)), __comp); if (__parent == 0) return; __parent--; } } // 将[first, last)排列为一个heap // 版本2 template <class _RandomAccessIterator, class _Compare> inline void make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __make_heap(__first, __last, __comp, __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); // 以下,每执行一次pop_heap(),极值(在STL heap中为极大值)即被放在尾端。扣除尾端再执行一次pop_heap(),次极值 // 又被放在新尾端。一直下去,最后即得到排序结果 while (__last - __first > 1) pop_heap(__first, __last--); // 每执行pop_heap()一次,操作范围即退缩一格 }
push_heap()算法
在堆中添加一个元素。为了满足完全二叉树的条件,新加入的元素一定要放在最下一层作为叶子节点,并填补在从左到右的第一个空格出,即将新元素插入在底层vector/array的end()处。插入后,为了满足堆(以大顶堆为例)的条件,还需要执行上溯操作:即将新节点与其父节点比较,如果其键值大于父节点,就将父子对换位置。如此一直上溯,直到不需对换或直到根节点为止。
// 以下这组push_back()不允许指定“大小比较标准” template <class _RandomAccessIterator, class _Distance, class _Tp> void __push_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __topIndex, _Tp __value) { _Distance __parent = (__holeIndex - 1) / 2; while (__holeIndex > __topIndex && *(__first + __parent) < __value) { // 当尚未达到顶端,且父节点小于新值(于是不符合heap的次序特性) // 由于以上使用operator<,可知STL heap是一种max-heap(大者为父) *(__first + __holeIndex) = *(__first + __parent); // 令洞者为父节点 __holeIndex = __parent; // percolate up:调整洞号,向上提升至父节点 __parent = (__holeIndex - 1) / 2; // 新洞的父节点 } *(__first + __holeIndex) = __value; // 令洞值为新值,完成插入操作 } template <class _RandomAccessIterator, class _Distance, class _Tp> inline void __push_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Distance*, _Tp*) { // 根据implicit represention heap的结构特性:新值必置于底部容器的最尾端,此即第一个洞号:(last-first)-1 __push_heap(__first, _Distance((__last - __first) - 1), _Distance(0), _Tp(*(__last - 1))); } // push_heap函数,在堆中插入一个新元素 // 版本1 template <class _RandomAccessIterator> inline void push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last) { __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, _LessThanComparable); // 注意:此函数被调用时,新元素应已置于底部容器的最尾端 __push_heap_aux(__first, __last, __DISTANCE_TYPE(__first), __VALUE_TYPE(__first)); } 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)) { *(__first + __holeIndex) = *(__first + __parent); __holeIndex = __parent; __parent = (__holeIndex - 1) / 2; } *(__first + __holeIndex) = __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(__first, _Distance((__last - __first) - 1), _Distance(0), _Tp(*(__last - 1)), __comp); } // 版本2 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()算法
在堆中删除根元素。下面以大顶堆为例,取走根节点(实质是设置底部容器vector/array的尾端节点)后,为了满足完全二叉树的条件,必须割舍最下层最右边的叶节点,并将其值重新安插到大顶堆的顶点(即vector/array的首端)。取走根元素后,为了满足大顶堆的条件,还需要执行下溯操作:即将此时的根节点值与其较大的自己子节点比较,如果其子节点更大,则交换,然后持续下方,直到不需对换或达到叶节点为止。
// __adjust_heap(调整堆)算法:主要用于调整排列中的数据,使其满足堆的性质。此算法主要用在pop_heap()算法中。 // 以下这个__adjust_heap()不允许指定“大小比较标准” // 版本1 template <class _RandomAccessIterator, class _Distance, class _Tp> void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __len, _Tp __value) { _Distance __topIndex = __holeIndex; _Distance __secondChild = 2 * __holeIndex + 2; // 洞节点之右子节点 while (__secondChild < __len) { // 比较洞节点之左右两个子值,然后以secondChild代表较大的子节点 if (*(__first + __secondChild) < *(__first + (__secondChild - 1))) __secondChild--; // percolate down:令较大值为洞值,再令洞号下移至较大子节点处 *(__first + __holeIndex) = *(__first + __secondChild); __holeIndex = __secondChild; // 找出新洞节点的右子节点 __secondChild = 2 * (__secondChild + 1); } if (__secondChild == __len) { // 没有右子节点,只有左子节点 // percolate down:令左子值为洞值,再令洞号下移至左子节点处 *(__first + __holeIndex) = *(__first + (__secondChild - 1)); __holeIndex = __secondChild - 1; } // 此时可能尚未满足次序特性。执行一次percolate up操作 __push_heap(__first, __holeIndex, __topIndex, __value); } template <class _RandomAccessIterator, class _Tp, class _Distance> inline void __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __result, _Tp __value, _Distance*) { *__result = *__first; // 设定尾指为首值,于是尾值即为欲求结果,可由客户端稍后再以底层容器之pop_back()取出尾值 // 以上欲重新调整heap,洞号为0(亦即树根处),欲调整值为value(原尾值) __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value); } template <class _RandomAccessIterator, class _Tp> inline void __pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp*) { // 根据implicit represention heap的次序特性,pop操作的结果应为底部容器的第一个元素。因此,首先 // 设定欲调整值为尾值,然后将首值调至尾节点(所以以上将迭代器result设为last-1),然后重整[first, // last-1),使之重新成一个合格的heap __pop_heap(__first, __last - 1, __last - 1, _Tp(*(__last - 1)), __DISTANCE_TYPE(__first)); } // pop_heap算法,从堆中删除元素 // 版本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)); } // 调整堆,版本2 template <class _RandomAccessIterator, class _Distance, class _Tp, class _Compare> void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __len, _Tp __value, _Compare __comp) { _Distance __topIndex = __holeIndex; _Distance __secondChild = 2 * __holeIndex + 2; while (__secondChild < __len) { if (__comp(*(__first + __secondChild), *(__first + (__secondChild - 1)))) __secondChild--; *(__first + __holeIndex) = *(__first + __secondChild); __holeIndex = __secondChild; __secondChild = 2 * (__secondChild + 1); } if (__secondChild == __len) { *(__first + __holeIndex) = *(__first + (__secondChild - 1)); __holeIndex = __secondChild - 1; } __push_heap(__first, __holeIndex, __topIndex, __value, __comp); } template <class _RandomAccessIterator, class _Tp, class _Compare, class _Distance> inline void __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __result, _Tp __value, _Compare __comp, _Distance*) { *__result = *__first; __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value, __comp); } template <class _RandomAccessIterator, class _Tp, class _Compare> inline void __pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp*, _Compare __comp) { __pop_heap(__first, __last - 1, __last - 1, _Tp(*(__last - 1)), __comp, __DISTANCE_TYPE(__first)); } // 版本2 template <class _RandomAccessIterator, class _Compare> inline void pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __pop_heap_aux(__first, __last, __VALUE_TYPE(__first), __comp); }
sort_heap()算法
因为堆中每次执行pop_heap可获得堆中(大顶堆为例)中键值的最大元素,如果持续对整个heap做pop_heap操作,每次将操作范围从后向前缩减一个元素(pop_heap会把键值最大的元素放在底部容器的最尾端),当整个程序执行完毕时,便可得到递增的序列,即实现利用堆进行排序。
// 堆排序算法,主要利用pop\_heap()算法实现 // 版本1 template <class _RandomAccessIterator> void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last) { __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, _LessThanComparable); // 以下,每执行一次pop_heap(),极值(在STL heap中为极大值)即被放在尾端。扣除尾端再执行一次pop_heap(),次极值 // 又被放在新尾端。一直下去,最后即得到排序结果 while (__last - __first > 1) pop_heap(__first, __last--); // 每执行pop_heap()一次,操作范围即退缩一格 } // 版本2 template <class _RandomAccessIterator, class _Compare> void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); while (__last - __first > 1) pop_heap(__first, __last--, __comp); }
参考文献
STL源码剖析——侯捷
STL源码