Chinaunix首页 | 论坛 | 博客
  • 博客访问: 133765
  • 博文数量: 13
  • 博客积分: 431
  • 博客等级: 下士
  • 技术积分: 166
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-18 14:59
文章分类

全部博文(13)

文章存档

2012年(9)

2011年(4)

分类: C/C++

2012-01-26 22:58:50

stl提供了四个heap的算法:push_heap, pop_heap, make_heap, sort_heap。sgi的stl还提供了一个扩展的is_heap用来判断两个随机iterator形成的区间的是否为heap。

本文分析其中的pop_heap算法和侯捷先生的《STL源码剖析》中关于该算法的一个错误。

代码取自sgi stl 3.3,为了阅读方便,对函数顺序重新进行了排列:

  1. // pop_heap删除__first(实际是将__first与__last-1交换),然后调用__adjust_heap调整堆。
  2. // [first, last)必须是合法的heap。
  3. template <class _RandomAccessIterator>
  4. inline void pop_heap(_RandomAccessIterator __first,
  5.                      _RandomAccessIterator __last)
  6. {
  7.   // 因为需要通过iterator改变元素的值,所以需要符合mutable random access iterator.
  8.   __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
  9.   // 要求iterator指向的元素类型是可比较的(不仅仅是可以判断小于这一种情况)。
  10.   __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type,
  11.                  _LessThanComparable);
  12.   // 辅助函数
  13.   __pop_heap_aux(__first, __last, __VALUE_TYPE(__first));
  14. }
  15. // 一个鸡肋的辅助函数

  template <class _RandomAccessIterator, class _Tp>

  1. inline void
  2. __pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last,
  3.                _Tp*)
  4. {
  5.   __pop_heap(__first, __last - 1, __last - 1,
  6.              _Tp(*(__last - 1)), __DISTANCE_TYPE(__first));
  7. }
  1. template <class _RandomAccessIterator, class _Tp, class _Distance>
  2. inline void
  3. __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
  4.            _RandomAccessIterator __result, _Tp __value, _Distance*)
  5. {
  6.   // result(也就是last-1)保存first指向的元素。
  7.   *__result = *__first;
  8.   // 将value(原last-1位置的元素)放到合适的位置
  9.   __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
  10. }

  11. template <class _RandomAccessIterator, class _Distance, class _Tp>
    void
    __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
                  _Distance __len, _Tp __value)
    {
  12.   // 自顶向下寻找__value合适的位置。
      _Distance __topIndex = __holeIndex;
  13.   // 设置右孩子索引的位置(因为index是从0计数)
      _Distance __secondChild = 2 * __holeIndex 2;
      while (__secondChild < __len) {
  14.     // 如果右孩子小于左孩子,就调整__secondChild
        if (*(__first __secondChild) < *(__first (__secondChild - 1)))
          __secondChild--;
  15.     // 将较大的那个赋给双亲
        *(__first __holeIndex) = *(__first __secondChild);
  16.     // 调整子树索引
        __holeIndex = __secondChild;
  17.     // 指向新子树根节点的右孩子,这种写法真是disgusting,为什么不和上面保持一致呢?
        __secondChild = 2 * (__secondChild 1);
      }

  18.   // 只有左孩子,没有右孩子
      if (__secondChild == __len) {
  19.     // 直接将左子树元素赋给根节点
        *(__first __holeIndex) = *(__first (__secondChild - 1));
  20.     // 调整子树索引
  21.     __holeIndex = __secondChild - 1;
      }

  22.   // 将__value赋给__holeIndex所属位置,并调整所在子树。
  23.   // 在《STL源码剖析》中,侯先生认为下面的语句可以改为*(__first __holeIndex) = value
  24.   // 但是这样一来,并不能保证value所在子树符合heap的约束。
      __push_heap(__first, __holeIndex, __topIndex, __value);
    }

  25. template <class _RandomAccessIterator, class _Distance, class _Tp>
  26. void
  27. __push_heap(_RandomAccessIterator __first,
  28.             _Distance __holeIndex, _Distance __topIndex, _Tp __value)
  29. {
  30.   // 可以看出,依然需要根据value和*(__first __parent)的关系来决定value最终的位置。
  31.   _Distance __parent = (__holeIndex - 1) / 2;
  32.   while (__holeIndex > __topIndex && *(__first __parent) < __value) {
  33.     *(__first __holeIndex) = *(__first __parent);
  34.     __holeIndex = __parent;
  35.     __parent = (__holeIndex - 1) / 2;
  36.   }
  37.   *(__first __holeIndex) = __value;
  38. }

举个简单的例子,依次将10、9、8、1、2、3、7 push到vector中,它显然是一个heap,如图(家里的电脑没有visio,列位将就着看吧):

                               10
                            /      \
                           9         8
                         /   \     /   \
                        1     2   3     7

当调用pop_heap,并在__adjust_heap处使用直接赋值的话,是这样的结果:

                               9
                            /      \
                           2         8
                         /   \     /
                        1     7   3

显然,将(__last-1)处的元素赋给其他的位置时,并不能保证该元素一定小于其双亲节点。

sgi的heap算法实现调用层次过多,代码也不够紧凑,改日补充一个个人整理的简略的heap文件,方便大家理解和调试。

整理后的heap实现(没有重载less than运算符)
  1. namespace simple
  2. {

  3. template <class random_iterator>
  4. void push_heap(random_iterator first, random_iterator last)
  5. {
  6.     typedef typename iterator_traits<random_iterator>::value_type value_type;
  7.     typedef typename iterator_traits<random_iterator>::difference_type difference_type;

  8.     // less than 1 element.
  9.     if ((last - 1) <= first)
  10.     {
  11.         return ;
  12.     }

  13.     // value of (last -1).
  14.     value_type value = *(last - 1);
  15.     // index of (last -1).
  16.     difference_type i = (last - 1) - first;
  17.     // parent of (last - 1).
  18.     difference_type parent = (i - 1) / 2;

  19.     while (i > 0 && first[parent] < value)
  20.     {
  21.         first[i] = first[parent];
  22.         i = parent;
  23.         parent = (i - 1) / 2;
  24.     }

  25.     first[i] = value;

  26.     return ;
  27. }

  28. template <class random_iterator>
  29. void pop_heap(random_iterator first, random_iterator last)
  30. {
  31.     typedef typename iterator_traits<random_iterator>::value_type value_type;
  32.     typedef typename iterator_traits<random_iterator>::difference_type difference_type;

  33.     // before value of first.
  34.     value_type value = *first;
  35.     // index of first.
  36.     difference_type i = 0;
  37.     // left child index of first.
  38.     difference_type left = 1;
  39.     // right child index of first.
  40.     difference_type right = left + 1;
  41.     difference_type n = last - first;

  42.     // ajust the heap
  43.     while (right < n)
  44.     {
  45.         if (first[left] > first[right])
  46.         {
  47.             first[i] = first[left];
  48.             i = left;
  49.         }
  50.         else
  51.         {
  52.             first[i] = first[right];
  53.             i = right;
  54.         }
  55.         left = i * 2 + 1;
  56.         right = left + 1;
  57.     }

  58.     if (left < n)
  59.     {
  60.         first[i] = first[left];
  61.         i = left;
  62.     }

  63.     // find a hole for (last - 1).
  64.     first[i] = *(last - 1);

  65.     // insert (last - 1) into range [first, first + i + 1).
  66.     simple::push_heap(first, first + i + 1);

  67.     // save the before value of first.
  68.     *(last - 1) = value;

  69.     return ;
  70. }

  71. template <class random_iterator>
  72. void make_heap(random_iterator first, random_iterator last)
  73. {
  74.     if (last - first <= 1)
  75.     {
  76.         return ;
  77.     }

  78.     // insert range [i, last) into heap [first, i).
  79.     // insert (last - 1) when i equal last.
  80.     // i begin with first + 2 since [first, first + 1) is a valid heap.
  81.     for (random_iterator i = first + 2; i <= last; ++i)
  82.     {
  83.         simple::push_heap(first, i);
  84.     }

  85.     return ;
  86. }

  87. template <class random_iterator>
  88. void sort_heap(random_iterator first, random_iterator last)
  89. {
  90.     if (last - first <= 1)
  91.     {
  92.         return ;
  93.     }

  94.     // pop first into (i - 1).
  95.     // i end with first + 2 since [first, first + 1) is a sorted range.
  96.     for (random_iterator i = last; i >= first + 2; --i)
  97.     {
  98.         simple::pop_heap(first, i);
  99.     }

  100.     return ;
  101. }

  102. template <class random_iterator>
  103. bool is_heap(random_iterator first, random_iterator last)
  104. {
  105.     typedef typename iterator_traits<random_iterator>::difference_type difference_type;

  106.     difference_type n = last - first;
  107.     difference_type i = 0;
  108.     difference_type left = 1;

  109.     while (left < n)
  110.     {
  111.         if (first[i] < first[left] || first[i] < first[left + 1])
  112.         {
  113.             return false;
  114.         }
  115.         ++i;
  116.         left += 2;
  117.     }

  118.     return true;
  119. }

  120. }; // namespace simple

阅读(7088) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~