STL heap

一.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个元素,以后每次取出堆顶元素再进行调整,则时间复杂度为: 2 * (log_{2}^{n - 1} + log_{2}^{n - 2}+ ... + log_{2}^{3} + log_{2}^{2}) < 2*n*log_{2}^{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) 调整堆的时间复杂度

     没太多好说的,为log_{2}^{n}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值