STL堆算法性能分析与优化方法(GCC4.4.2 stl_heap.h源代码分析与改进方案)

堆是常用的数据结构,经常用于实现优先队列中,huffman编码中的就用到了优先队列。

本质上就是用数组实现的完全二叉树保证父节点的关键码都大于或者等于其子节点的关键码(最大堆,反过来是最小堆),下图是一个最大堆示意。

那么在STL中有相应的堆算法,如判断一个序列是否为堆__is_heap,将一个序列建立为堆make_heap,向堆中插入元素push_heap,删除元素pop_heap,对堆排序等等sort_heap。个人认为STL的make_heap和pop_heap的实现并不是最好的,性能上也稍受影响。

就以建堆操作为例子吧,有两种方法:

  1. 就是用shift up的方法,所谓shift up就是前面的序列已经是堆了,再加入一个元素放到末尾,然后由底端向上通过和父节点比较交换到达合适的位置。这种方法也适合与push_heap也就是插入元素操作。用这种方法建堆复杂度是o(nlong(n))
  2. 更好的方案是采用shift down方法,这是假定我们已经知道要建堆区间的情况下。具体可以参考数据结构课本这方法的代价是o(n)的。

关于元素删除:

元素删除其实就是把堆顶端的元素删除,将堆末尾的元素赋值到堆顶端然后再利用shift down操作向下调整到合适的位置。

STL的算法实现中包括对一个区间建立堆结构和元素的删除都没有用shift down操作,统统用的是shift up(__push_heap),

比如说元素的删除,它会把堆顶元素删除,然后尝试着把从位置1到最后一个元素的位置都向上移动一位并且通过调整保持堆结构,最后

再次调用shift_up将尾元素向上移动到合适的位置。在网上查了下http://www.cppblog.com/guogangj/archive/2009/10/29/99729.html

其实就是下图这种算法,删除元素也是O(log(n))的代价,但是我觉得还是用shift down更直观,同时能够减少一定的比较移动的次数,虽然时间复杂度不变。

下图如果用shift down的话就是把8直接放到原来1的位置向下不断调整直到到达合适的位置。

 

shift up(__push_heap)的源代码如下:

//shift_up!  将位于first+holeIndex的值为value的元素shift up操作,最远直到
//first+topIndex位置

代码
 1  template<typename _RandomAccessIterator, typename _Distance, typename _Tp>
 2     void
 3     __push_heap(_RandomAccessIterator __first,
 4         _Distance __holeIndex, _Distance __topIndex, _Tp __value)
 5     {
 6       _Distance __parent = (__holeIndex - 1/ 2;
 7       while (__holeIndex > __topIndex && *(__first + __parent) < __value)
 8     {
 9       *(__first + __holeIndex) = *(__first + __parent);
10       __holeIndex = __parent;
11       __parent = (__holeIndex - 1/ 2;
12     }
13       *(__first + __holeIndex) = __value;
14     }
15 

//删除操作会调用 __adjust_heap而__asjust_heap并没有直接执行shift down的操作,而是将堆普遍前移一位,最后再执行shift up操作(__push_heap)

代码

而初始给定区间构造堆得过程也是如此,其实可以用shift down操作的地方的那个元素删除,后面的元素前提,保持堆形态,然后再调用

shift up.

代码
 /**
   *  @brief  Construct a heap over a range.
   *  @param  first  Start of heap.
   *  @param  last   End of heap.
   *  @ingroup heap
   *
   *  This operation makes the elements in [first,last) into a heap.
  
*/
  template
<typename _RandomAccessIterator>
    
void
    make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
    {
      typedef typename iterator_traits
<_RandomAccessIterator>::value_type
      _ValueType;
      typedef typename iterator_traits
<_RandomAccessIterator>::difference_type
      _DistanceType;

      
// concept requirements
      __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
        _RandomAccessIterator
>)
      __glibcxx_function_requires(_LessThanComparableConcept
<_ValueType>)
      __glibcxx_requires_valid_range(__first, __last);

      
if (__last - __first < 2)
    
return;

      
const _DistanceType __len = __last - __first;
      _DistanceType __parent 
= (__len - 2/ 2;
      
while (true)
    {
      
std::__adjust_heap(__first, __parent, __len,
                 _ValueType(*(__first + __parent)));
      
if (__parent == 0)
        
return;
      __parent
--;
    }
    }

我尝试着改变算法采用shift down,其实改变的工作量很小,并且做了实验验证了新

算法的正确性,和与原实现的效率对比。我仅仅测试了对于最大堆给定区间建堆,删除元素的情况。

如果需要最小堆那么你还要改写对应的带有防函数模板参数变量的函数。

实验的结果是采用shift_down之后删除操作和以前几乎完全一样,但是建堆会快一些。

allen:~/study/c++/stl$ ./makeheap_perf
[==========] Running 3 tests from 3 test cases.
[----------] Global test environment set-up.
[----------] 1 test from my_make_heap
[ RUN      ] my_make_heap.perf
15.97 s

[       OK ] my_make_heap.perf (16024 ms)
[----------] 1 test from my_make_heap (16024 ms total)

[----------] 1 test from stl_make_heap
[ RUN      ] stl_make_heap.perf
21.40 s

[       OK ] stl_make_heap.perf (21516 ms)
[----------] 1 test from stl_make_heap (21516 ms total)

[----------] 1 test from make_heap
[ RUN      ] make_heap.func
[       OK ] make_heap.func (54 ms)
[----------] 1 test from make_heap (54 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 3 test cases ran. (37595 ms total)
[  PASSED  ] 3 tests.

综上所述,由于GCC实现的STL 堆算法没有用shift down 操作,虽然负责度不变,我个人还是认为用将尾元素替换被删除的头元素然后执行shift down操作的算法可读性更强,并且能够减少一定的比较次数,实验也证明确实如此。如果你特别在意性能的话,例如堆是性能瓶颈,需要大量的元素多次建堆,可以自己改写相应算法,如果不是大量操作的话其实性能差异可以忽略不记了:)。

改写的话其实也只需要改写一点点啦,不需要造轮子的地方就不造,最后再赞一下google test,太好用了:)

实验程序如下:

代码
  1 /*
  2  *  ==============================================================================
  3  * 
  4  *          \file   makeheap_perf.cc
  5  *
  6  *        \author   chenghuige@gmail.com
  7  *
  8  *          \date   2009-11-28 12:14:41.518985
  9  *  
 10  *   Description:   测试建立最大堆(注意这里仅仅测试最大堆,最小堆类似)
 11  *                  
 12  *                  1.stl算法效率(gcc 4.4.2)
 13  *                  2.我修改后的算法效率及正确性(加入shift down函数) 
 14  *                    我对原算法做了最小的修改,注意如果用最小堆,你还需要改
 15  *                    相应的带有防函数typname __Compare 模板参数的函数。
 16  *                  g++ -O3 -o makeheap_perf makeheap_perf.cc -lgtest -I$BOOST_ROOT
 17  *
 18  *  ==============================================================================
 19  */
 20 
 21 #include <iostream>
 22 #include <algorithm>
 23 #include <vector>
 24 #include <gtest/gtest.h>      //using google test
 25 #include <boost/progress.hpp> //using boost timer
 26 #include <ctime>            // std::time
 27 //using boost random
 28 #include <boost/random/linear_congruential.hpp>
 29 #include <boost/random/uniform_int.hpp>
 30 #include <boost/random/uniform_real.hpp>
 31 #include <boost/random/variate_generator.hpp>
 32 #ifdef BOOST_NO_STDC_NAMESPACE
 33 namespace std {
 34   using ::time;
 35 }
 36 #endif
 37 typedef boost::minstd_rand base_generator_type;
 38 using namespace std;
 39 
 40 //two global vector, we will use them as heap holder
 41 vector<double> vec;
 42 vector<double> vec1;
 43 vector<double> vec2;
 44 const int VecSize  = 1024 *1024;
 45 const int LoopTimes = 400
 46 
 47 //以0-1的随机浮点数填充  
 48 void init_vec() {
 49   vec.resize(VecSize);
 50   vec1.resize(VecSize);
 51   vec2.resize(VecSize);
 52 
 53 
 54   base_generator_type generator(42u);
 55   boost::uniform_real<> uni_dist(0,1);
 56   boost::variate_generator<base_generator_type&, boost::uniform_real<> > uni(generator, uni_dist);
 57 
 58   for (int i = 0; i < VecSize; i++) {
 59     vec[i] = uni();
 60   }
 61   
 62 }
 63 //----------------------------------改写后的shift down 和建堆操作
 64 //shift down 操作,将__first + __holeIndex位置的元素
 65 //(其值为__value如果原来不是就相当于先赋值为__value)
 66 //向下shift down,最远到达__first + len - 1位置
 67 template<typename _RandomAccessIterator, typename _Distance, typename _Tp>
 68 void  shift_down(_RandomAccessIterator __first, _Distance __holeIndex,
 69                       _Distance __len, _Tp __value)
 70 {
 71   _Distance __secondChild = 2 * __holeIndex + 2//右子节点index
 72   
 73   while (__secondChild < __len) {
 74     //执行后secondChild代表两个子节点中较大的节点
 75     if ( *(__first + __secondChild) < *(__first + (__secondChild - 1)) )
 76       __secondChild--;
 77     
 78     //如果比子节点小
 79     if ( __value < *(__first + __secondChild)) {
 80       *(__first + __holeIndex) =  *(__first + __secondChild);
 81       __holeIndex = __secondChild;         //继续shift down
 82       __secondChild = 2 * __holeIndex+ 2;
 83     }
 84     else 
 85       break;  
 86   }
 87   
 88   //最后一层可能存在只有左子节点情况
 89   if (__secondChild == __len) {
 90     __secondChild--;
 91    if ( __value < *(__first + __secondChild)) {
 92       *(__first + __holeIndex) =  *(__first + __secondChild);
 93       __holeIndex = __secondChild;
 94    }
 95   }
 96   
 97   //将__value赋值到最后确定的位置
 98   *(__first + __holeIndex) = __value;   
 99 }
100 
101 
102 template<typename _RandomAccessIterator>
103 void my_make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
104 {
105   typedef typename iterator_traits<_RandomAccessIterator>::value_type
106   _ValueType;
107   typedef typename iterator_traits<_RandomAccessIterator>::difference_type
108   _DistanceType;
109 
110   if (__last - __first < 2)
111     return;
112 
113   const _DistanceType __len = __last - __first;
114   _DistanceType __parent = (__len - 2/ 2;
115   while (true)
116   {
117     shift_down(__first, __parent, __len, 
118                        _ValueType(*(__first + __parent)));
119     if (__parent == 0)
120       return;
121     __parent--;
122   }
123 }
124 
125 //-------------改写后的删除操作
126 template<typename _RandomAccessIterator, typename _Tp>
127     inline void
128     __my_pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
129            _RandomAccessIterator __result, _Tp __value)
130     {
131       typedef typename iterator_traits<_RandomAccessIterator>::difference_type
132     _Distance;
133       *__result = *__first;
134       shift_down(__first, _Distance(0), _Distance(__last - __first),
135              __value);
136     }
137 
138  template<typename _RandomAccessIterator>
139     inline void
140     my_pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
141     {
142       typedef typename iterator_traits<_RandomAccessIterator>::value_type
143     _ValueType;
144       
145       __my_pop_heap(__first, __last - 1, __last - 1,
146               _ValueType(*(__last - 1)));
147     }
148 
149 
150 //stl 自带的make_heap,以及pop_heap函数的性能测试,对vec1操作 
151 void stl_make_heap() {
152   boost::progress_timer timer;
153   for (int i = 0; i < LoopTimes; i++) {
154     copy(vec.begin(), vec.end(), vec1.begin());
155     //建堆
156     make_heap(vec1.begin(), vec1.end());
157     ////逐次删除所有的堆中元素
158     //for (int j = 0; j < VecSize; j++) {
159     //  pop_heap(vec1.begin(), vec1.end() - j); 
160     //}
161     
162   }
163 }
164 
165 //修改后的my_make_heap,以及pop_heap函数测试,对vec2操作
166 void my_make_heap() {
167   boost::progress_timer timer;
168   for (int i = 0; i < LoopTimes; i++) {
169     copy(vec.begin(), vec.end(), vec2.begin());
170     //建堆
171     my_make_heap(vec2.begin(), vec2.end());
172     ////逐次删除所有的堆中元素
173     //for (int j = 0; j < VecSize; j++) {
174     //  pop_heap(vec2.begin(), vec2.end() - j); 
175     //}
176 
177   }
178 }
179 
180 //测试my_make_heap是否正确的建堆,及删除堆元素是否正确
181 void test_equal_vec() {
182   copy(vec.begin(), vec.end(), vec2.begin());
183   my_make_heap(vec2.begin(), vec2.end());
184   EXPECT_EQ(1, std::__is_heap(vec2.begin(), vec2.end()));
185   //测试删除堆元素操作是否正确
186   //int j;
187   //for (j = 0; j < VecSize/2 + 5; j++) {
188   //  pop_heap(vec2.begin(), vec2.end() - j); 
189   //}
190   
191   //EXPECT_EQ(1, std::__is_heap(vec2.begin(), vec2.end()-j));
192 }
193 
194 TEST(stl_make_heap, perf)
195 {
196   stl_make_heap();
197 }
198 
199 TEST(my_make_heap, perf)
200 {
201   my_make_heap();
202 }
203 
204 TEST(make_heap, func)
205 {
206   test_equal_vec();
207 }
208 
209 
210 
211 int main(int argc, char *argv[])
212 {
213   init_vec();
214 
215   testing::InitGoogleTest(&argc, argv);
216   return RUN_ALL_TESTS();
217 }

 

原文地址:https://www.cnblogs.com/rocketfan/p/1612665.html