STL算法_heap算法篇

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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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源码

你可能感兴趣的:(STL源码剖析学习,STL源码阅读总结)