STL源码剖析——P177关于__adjust_heap

阅读__adjust_heap源码前,我对于这个函数的一些最初实现想法:

1、相关描述:

     **** Heap的底层实现是Vector类型

     **** Heap里面存放的元素是从索引0开始存放,所以如果父结点为i,则其左子节点为(2 * i + 1)

             而右子节点为 (2 * i + 2)

     **** 调用__adjust_heap函数的前提是:调用__adjust_heap之前 , 先对heap的最前端数据和最尾部数据进行交换

2、the __adjust_heap interface

     **** template< class RandomAccessIterator, class Distance, class T > 

             void __adjust_heap( RandomAccessIterator first,  Distance holeIndex, Distance len, T value );

idea 1 : 在 [first + holeIndex , first + len)范围内执行操作,从holeIndex开始,获取secondChild,

            循环下溯,直到condition为 !(secondChild < len),退出循环。每次循环都将holeIndex的两个子节

            点中最大的那个(设为maxVal)与value比较,若 (maxVal > value) 则将maxVal放入holeIndex,

            然后将holeIndex的值重置为maxVal所在的index,重新计算secondChild。否则直接return。

            退出循环,但还未return,则获取holeIndex的firstChild, 判断 (firstChild < len),是则执行与循环体相似的操

            作。最后将holeIndex位置的值置为value。于是得到代码如下:

 1 template< class RandomAccessIterator, class Distance, class T >
 2 void __adjust_heap( RandomAccessIterator first,
 3                               Distance holeIndex, Distance len, T value )
 4 {
 5     for( Distance secondChild = 2 * holeIndex + 2 ;
 6            secondChild < len ; secondChild = 2 * ( holeIndex + 1 ) )
 7     {
 8          if( *( first + secondChild - 1 ) < *( first + secondChild ) )
 9                  secondChild-- ;
10 
11          if( *( first + secondChild ) > value )
12                 *( first + holeIndex ) = *( first + secondChild ) ;
13          else
14          {
15                 *( first + holeIndex ) = value ;
16                 return ;
17           }
18 
19           holeIndex = secondChild ;
20     }
21 
22     Distance firstChild = 2 * holeIndex + 1 ;
23 
24     if( firstChild < len && *( first + firstChild ) > *( first + holeIndex ) ) 
25     {
26           *( first + holeIndex ) = *( first + firstChild ) ;
27           holeIndex = firstChild ;
28     }
29 
30     *( first + holeIndex ) = value ;
31 }

idea 2 : 考虑到secondChild的使用也可用firstChild替代,故修正代码如下:

 1 template< class RandomAccessIterator, class Distance, class T >
 2 void __adjust_heap( RandomAccessIterator first,  Distance holeIndex, Distance len, T value ) 
 3 {
 4     for( Distance firstChild = 2 * holeIndex + 1 ;
 5           firstChild < len ; firstChild = 2 * holeIndex + 1 )
 6     {
 7         if( firstChild + 1 < len && *( first + firstChild ) < *( first + firstChild + 1 ) )
 8             firstChild++ ;
 9 
10         if( *( first + firstChild ) > value )
11             *( first + holeIndex ) = *( first + firstChild ) ;
12         else
13         {
14             *( first + holeIndex ) = value ;
15             return ;
16         }
17 
18         holeIndex = firstChild ;
19     }
20 
21     *( first + holeIndex ) = value ;
22 }

再次调整修改之后,如下:

 1 template< class RandomAccessIterator, class Distance, class T >
 2 void __adjust_heap( RandomAccessIterator first,
 3                                Distance holeIndex, Distance len, T value )
 4 {
 5     for( Distance firstChild = 2 * holeIndex + 1 ;
 6           firstChild < len ; firstChild = 2 * holeIndex + 1 )
 7     {
 8         if( firstChild + 1 < len
 9             && *( first + firstChild ) < *( first + firstChild + 1 ) )
10             firstChild++ ;
11 
12         if( *( first + firstChild ) > value )
13         {
14             *( first + holeIndex ) = *( first + firstChild ) ;
15             holeIndex = firstChild ;
16         }
17         else break ;
18     }
19 
20     *( first + holeIndex ) = value ;
21 }

idea 3 : 但看了STL的源码后,发现它的循环条件为 (secondChild < len) ,然后退出循环后的最后一次判

            断是 (secondChild == len) ,虽然这显而易见,也很平常,但感觉还是挺巧妙的,因为我最初并没有考虑

            到这一层,而基于STL源码,我猜想,它这样设定“临界条件”的想法应该是这样的:

                heap是以complete binary tree 实现的(底层实现是vector没错,但concept上它是一棵complete binary

                tree,请不要纠结于个别字眼),数据是从index 0的位置开始存放的,故secondChild = 2*holeIndex + 2,

                则当secondChild在heap的长度范围内,即[ 0 , len )时,进行处理,当secondChild越界时,则firstChild不

                一定会越界,但是否因此需要进行 ( firstChild < len ) 检测,如果进行检测则想法与我前面所实现的代码类似,

                然而由于secondChild已经越界,而firstChild = secondChild - 1 ,故要么firstChild也越界,要么firstChild

                只能刚好为最后一个元素而此时的secondChild正好等于len,所以只需检测(secondChild == len)。其实检测

                (firstChild < len) 和检测 (secondChild == len) 我感觉应该没什么差别,但是从代码含义上

                (secondChild == len) 更说明了此时firstChild的值只有两种情况:越界或者刚好为最后一个元素的下标,不过

                它显然没有 (firstChild == len - 1)更能说明问题,但是这样的检测多了一些运算,所以显然检测

                (secondChild == len)是较佳的选择。

基于此,修改代码如下:

 1 template< class RandomAccessIterator, class Distance, class T >
 2 void __adjust_heap( RandomAccessIterator first,
 3                                Distance holeIndex, Distance len, T value )
 4 {
 5     Distance secondChild = 2 * holeIndex + 2 ;
 6 
 7     for( ; secondChild < len ; secondChild = 2 * ( holeIndex + 1 ) )
 8     {
 9         if( *( first + secondChild - 1 ) < *( first + firstChild + 1 ) )
10             secondChild-- ;
11 
12         if( *( first + secondChild ) > value )
13             *( first + holeIndex ) = *( first + secondChild ) ;
14         else
15         {
16             *( first + holeIndex ) = value ;
17             return ;
18          }
19 
20         holeIndex = secondChild ;
21     }
22 
23     if( secondChild == len
24         && *( first + secondChild - 1 ) > *( first + holeIndex ) )
25     {
26         *( first + holeIndex ) = *( first + secondChild - 1 ) ;
27         holeIndex = secondChild - 1 ;
28     }
29 
30     *( first + holeIndex ) = value ;
31 }

stl本来的源码如下

 1 template <class _RandomAccessIterator, class _Distance, class _Tp>
 2 void __adjust_heap( _RandomAccessIterator __first,
 3                              _Distance __holeIndex, _Distance __len, _Tp __value )
 4 {
 5     _Distance __topIndex = __holeIndex ;
 6     _Distance __secondChild = 2 * __holeIndex + 2 ;
 7 
 8     while ( __secondChild < __len )
 9     {
10         if ( *( __first + __secondChild )
11              < * (__first + ( __secondChild - 1 ) ) )
12             __secondChild-- ;
13 
14         *( __first + __holeIndex ) = *( __first + __secondChild ) ;
15         __holeIndex = __secondChild ;
16         __secondChild = 2 * ( __secondChild + 1 ) ;
17     }
18 
19     if ( __secondChild == __len )
20     {
21         *( __first + __holeIndex ) = *( __first + ( __secondChild - 1 ) ) ;
22         __holeIndex = __secondChild - 1 ;
23     }
24     __push_heap( __first, __holeIndex, __topIndex, __value );
25 }

分析可知,stl源码中的实现是先将first的值取出并置于末元素的位置上,而末元素的值保存在__value中,然后再调用__adjust_heap,在__adjust_heap中,对__topIndex以下的元素进行调整到以__topIndex为起点的位置上,最后会得到本身是叶子节点的洞结点,并且洞结点前面的结点是满足max heap条件的,故可对[__topIndex , __holeIndex]执行__push_heap操作,插入的值为__value,故最终__value会存入正确的位置上。

对于__push_heap(__first, __holeIndex, __topIndex, __value)的调用必不可少,此处不可替换为

*( __first + __holeIndex ) = __value ;因为前面对于heap的调整并没有考虑__value,所以还需基于__value进行再次调整。然而如果基于我前面实现的方案,即将__value放到__holeIndex的位置上,然后基于__value,进行向下调整,使__value存入正确的位置上,则末尾一步的操作,就是*( __first + __holeIndex ) = __value ;

不过这也正是我所疑惑的地方,如果将__value放到__holeIndex的位置上,然后基于__value,进行向下调整,一步到位,最终使__value存入正确的位置上,感觉不是更高效一些吗,作为追求效率的STL库,却进行了一次下溯和一次上溯。难道说,这样会更高效一些?

不解中,待以后查明……

posted @ 2012-12-17 19:10  crazylhf  阅读(1288)  评论(2编辑  收藏  举报