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库,却进行了一次下溯和一次上溯。难道说,这样会更高效一些?
不解中,待以后查明……