一.heap概述
STL中的heap并不是一种容器,而更像是一个算法集。在STL中为priority_queue容器提供了算法基础,该heap算法集通过迭代器操作priority_queue中的底层容器,如vector。
二.heap算法
heap算法的总体思想是:将容器中的元素调整为一个大顶堆或小顶堆,实际上就是一个满足特殊条件的完全二叉树。由于是从下标0开始的,因此第i个节点的子节点为 2 * i + 1 和 2 * i + 2。
1.make_heap()构建堆(包括adujst_heap和push_heap)
假设在一个包含数个元素的vector上调用make_heap算法,会改变其中元素的顺序,使其满足堆的构造。比如:
int main()
{
vector<int> nums = {0,1,2,3,4,5,6,7};
make_heap(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
cout << nums.at(i)<<" "; // 7 4 6 3 0 5 2 1
}
return 0;
}
其构造方式如下:先从最后的子树的根节点开始,对其进行下溯,在进行上溯。下溯过程即将子节点中中较大(或较小)者的值赋予洞节点,并将洞节点指向原先较大子节点的位置,直至最底层。上溯过程是将洞节点与其父节点进行比较,若父节点元素较小(或较大),则将洞节点与父节点进行交换,并将洞节点指向原先父节点的位置,直至当前子树的根节点。以上述程序为例:
heap算法中的__adjust_heap便是进行以上的上溯,下溯操作的。
其代码如下:
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 _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));
}
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);
}
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; // 将值放到洞节点位置
}
2.sort_heap堆排序
sort_heap函数用于将容器中的指定区间进行堆排序,其排序过程是调用pop_heap函数将堆顶元素移至末尾(last - 1处),在向前缩短一个区间[first, last - 1),在调用__adujst_heap方法进行下溯,上溯来调整[first, last - 1)区间的元素为堆,再将此次的堆顶元素放至末尾(last - 2处),以此类推。
3.堆相关算法的时间复杂度计算
1) 堆排序的时间复杂度
排序算法以比较次数作为计算基准,一个深度为k的堆其比较次数至多为2*(k - 1)次:至多比较k-1层,每层需要比较两次,第一次是找出较大(小)的孩子节点,第二次是将这个孩子节点与待放入值进行比较。我们可以想像一下,起初堆中有n个元素,以后每次取出堆顶元素再进行调整,则时间复杂度为: 。所以时间复杂度最坏的情况下为O(nlogn)。
2) 建立堆的时间复杂度
建立堆是从最后一个父节点(处于第h - 1层)处开始的,最多比较H1次,再上一次有2^(h - 2)个节点,每个节点至多调整2次,依次类推可得:1 * 2^(h - 1) + 2*2^(h - 2) + ... + (h - 2)*2^1 + (h - 1)*2^0 ;带入节点数n,可得:2n - 2 - log2(n);即世间复杂度为O(n)
3) 调整堆的时间复杂度
没太多好说的,为