8堆(heap)

1、heap概述

heap不属于STL容器组件,它以算法形式呈现,通过heap很方便实现priority queue。priority queue允许用户以任何次序将任何元素推入容器中,但取出时一定是从优先级最高也就是数值最高的元素开始取出,heap正是具有这样的特性,适合作为priority queue的底层实现。heap不提供遍历,也不提供迭代器。

为什么使用heap作为priority queue的底层实现?如果使用list作为priority queue的底层实现,那么元素插入可以享受常数时间,但是要找到list中的极值就需要O(n)时间;如果以二叉搜索树作为priority queue的底层实现,那么元素的插入和取极值都是O(logn)时间,但杀鸡用牛刀小题大做了,一来二叉搜索树的输入需要足够的随机性,二来二叉搜索树并不容易实现。priority queue的复杂度最好介于queue和二叉搜索树之间才适合,这样binary heap便是最合适的候选人。

heap就是一种完全二叉树,整棵二叉树除了最底层的叶节点之外,是填满的,而最底层的叶节点由左至右不得有空隙。这样就可以使用array或者vector存储所有节点,因为array无法动态改变大小,所以选择vector。当完全二叉树某个节点位于vector的i处,那么其左节点必位于2i+1,其右节点必位于2i+2,其父节点位于必位于(i-1)/2处。heap可分为大顶堆和小顶堆,大顶堆的父节点大于其子节点,因此大顶堆的最大值在根节点,小顶堆的父节点小于其子节点,因此小顶堆最小值在根节点。

2、heap算法

(1)push_heap算法

为了满足完全二叉树的条件,新加入的元素一定要放在最下一层作为叶节点,并填补在由左至右的第一个空位,也就是新元素插入在底层vector的end()处,然后为了满足heap大顶堆的条件,需要做出一些调整执行上溯程序:将新节点拿来与其父节点比较,如果其值比父节点大,就父子交换位置,如此一致上溯,直到不需要对换或直到根节点为止。

void __push_heap(RandomAccessIterator first, Distance holeIndex,Distance topIndex, T value),first为根节点地址,holeIndex为新节点位置下标,topIndex为根节点位置下标,value为新节点值:当尚未到达顶端且父节点小于当前节点值,那么将当前节点位置值设为父节点值,并且更新当前节点为父节点,一直上溯直至循环结束,最后令当前节点值为value,这样就做到了push_heap并且新heap仍然满足大顶堆的条件。

template <class RandomAccessIterator, class Distance, class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
                 Distance topIndex, T value) {
  Distance parent = (holeIndex - 1) / 2;
  while (holeIndex > topIndex && *(first + parent) < value) {
    *(first + holeIndex) = *(first + parent);
    holeIndex = parent;
    parent = (holeIndex - 1) / 2;
  }
  *(first + holeIndex) = value;
}

template <class RandomAccessIterator, class Distance, class T>
inline void __push_heap_aux(RandomAccessIterator first,
                            RandomAccessIterator last, Distance*, T*) {
  __push_heap(first, Distance((last - first) - 1), Distance(0),
              T(*(last - 1)));
}

template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __push_heap_aux(first, last, distance_type(first), value_type(first));
}

(2)pop_heap算法

pop操作是取走根节点,其实是放到了底部容器vector的尾端节点,然后将最下层最右边的叶节点也就是底部容器vector的尾端节点放入根节点,然后为了满足heap大顶堆的条件,需要做出一些调整,此时调整的范围是除了最后存放根节点的底部容器vector尾端的所有节点,执行下溯程序:将根位置节点和较大子节点对调,并持续下放,直至叶子节点为止,之后可能尚未满足次序特性,还需要执行一次上溯操作。

void __adjust_heap(RandomAccessIterator first, Distance holeIndex,Distance len, T value),first为根节点地址,holeIndex为根节点位置下标,len为调整的节点个数,value为当前根节点值原尾值:比较当前节点也就是根节点的左右子节点值,选择较大的一个出来,令当前节点值为较大的值,并且更新当前节点为较大子节点,一直下溯直至循环结束,最后如果是当前节点只有左子节点没有右子节点的情况,那么令当前节点值为左子节点值,当前节点更新为左子节点,并令当前节点值为value,这样就做到了pop_heap最大元素被放于底部容器的最尾端并且新heap仍然满足大顶堆的条件。

template <class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
                   Distance len, T value) {
  Distance topIndex = holeIndex;
  Distance secondChild = 2 * holeIndex + 2;
  while (secondChild < len) {
    if (*(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);
}

template <class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                       RandomAccessIterator result, T value, Distance*) {
  *result = *first;
  __adjust_heap(first, Distance(0), Distance(last - first), value);
}

template <class RandomAccessIterator, class T>
inline void __pop_heap_aux(RandomAccessIterator first,
                           RandomAccessIterator last, T*) {
  __pop_heap(first, last - 1, last - 1, T(*(last - 1)), distance_type(first));
}

template <class RandomAccessIterator>
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __pop_heap_aux(first, last, value_type(first));
}

(3)sort_heap算法

因为每次pop_heap可获得heap中键值最大的元素,对于大顶堆,如果持续对整个heap做pop_heap操作,每次将操作范围从后向前缩减一个元素,因为pop_heap会把键值最大的元素放在底部容器的最尾端,这样整个程序结束就可以得到一个从小到大的递增序列;同样对于小顶堆,如果持续对整个heap做pop_heap操作,每次将操作范围从后向前缩减一个元素,因为pop_heap会把键值最小的元素放在底部容器的最尾端,这样整个程序结束就可以得到一个从大到小的递减序列

template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
  while (last - first > 1) pop_heap(first, last--);
}

(4)make_heap算法

将一段无序的数据转化为一个heap,从下向上不断调用__adjust_heap(RandomAccessIterator first, Distance holeIndex,Distance len, T value)函数,使得以当前节点holeIndex为根节点的树满足heap,也就是holeIndex位置处元素值最大,然后不断向上调整每个节点以当前节点为根节点的树满足heap,那么最终达到heap头端整个heap满足大顶堆条件。

template <class RandomAccessIterator, class T, class Distance>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last, T*,
                 Distance*) {
  if (last - first < 2) return;
  Distance len = last - first;
  Distance parent = (len - 2)/2;

  while (true) {
    __adjust_heap(first, parent, len, T(*(first + parent)));
    if (parent == 0) return;
    parent--;
  }
}

template <class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __make_heap(first, last, value_type(first), distance_type(first));
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值