Modest opinions  
by a humble autodidact
STL的binary search是由lower_bound和uppper_bound两个函数实现的。lower_bound的描述为:
lower_bound() returns the position of the first element that has a value less than or equal to value. This is the first position where an element with value value could get inserted without breaking the actual sorting of the range [beg,end).
此函数的random-access iterator版的实现为(采自《STL源码剖析》):
__lower_bound
这是一个典型的binary search。为了方便分析,改写一下(以下分析均针对这段代码):
 1 template <class RandomAccessIterator, class T, class Distance>
 2 RandomAccessIterator __lower_bound(RandomAccessIterator first,
 3                                    RandomAccessIterator last, const T& value,
 4                                    Distance*, random_access_iterator_tag) {
 5   Distance len = last - first;  
 6   Distance half;
 7   RandomAccessIterator middle;
 8 
 9   while (len > 0) {
10     half = len >> 1;       
11     middle = first + half;    
12     if (*middle < value) {    
13       first1 = middle + 1;     
14       len1 = len - half - 1;     
15 
16       first = first1;
17       len =
 len1;
18     }
19     else
20       len = half;        
21   }
22   return first;
23 }

下面不严格的说明这个函数的正确性。首先确定函数的precondition。为了方便说明,假定数组[first, last)前后各有一个无穷小和无穷大的元素,最后我们将会看到这个假设不会影响结果。此函数要求输入的数组是排过序的:
-∞ = *(first-1) < *first ≤ *(first+1) ≤...≤ *(last-1) < *last = +∞
然后确定while循环的invariant:
*(first-1) < value ≤ *(frist+len)
以下说明这个条件始终成立。

首先在进入while循环前的第8行处,由precondition可得invariant“-∞ = *(first-1) < value < *last = *(first+len) = +∞”成立。

在循环的过程中根据条件(*middle < value)是否成立分两种情况改变len(和first)的值,需要分别说明两种情况下的改变都可以保持invariant成立。

首先如果条件成立,那么程序执行到15行时,*(first1-1)=*middle,而此时条件*middle < value成立,因此*(first1-1)<value。而*(firtst1+len1) = *(middle + 1 + len - half - 1) = *(first + half + 1 + len - half -1) = *(first + len)因为此时老的invariant“value ≤ *(frist+len)”依旧是成立的,所以value ≤ *(first1 + len1)。所以在15行时,条件*(first1-1) < value ≤ *(frist1+len1)成立,当16、17行分别把first1、len1赋值给新的first、len后,invariant成立。

当条件 *middle < value不成立,即*middle ≥ value时,程序执行20行“len = half”。first的值不变,因此*(first1-1)<value依旧成立。第20行执行后,*(first + len) = *(first + half) = *middle(注意11行:middle = first+half),而此时的条件是*middle ≥ value,所以*(first + len) ≥ value,20行过后invariant保持成立。

由于while循环过程中invariant保持成立,所以循环过后此条件依然成立。又由于循环的条件是len > 0,循环结束后(22行)必然len = 0,所以invariant就成了:*(first-1) < value ≤ *first,这就是函数的postcondition,后面会说明此postcondition符合函数定义。

另外还需说明循环为什么能结束。第10行“half = len >> 1;”执行后,0 ≤ half < len。第14行“len1 = len - half - 1;”,所以len1 < len。因此17、20行两处更新len的值,都可以保证len要减少。最终len会变成零或负值,循环条件将不成立,此时循环结束。

最后说明一下开始做的假设*(first-1) = -∞和*last = ∞不影响证明的正确性。首先在整个循环过程中,不会dereference这两个不存在的iterator。由于first的值是改变的,为了方便说明,用first0代表最初的,即由参数传入的first的值。需要说明middle的值始终在范围[first0, last)内。第11行,middle = first + half,第一次执行时midde = first0+half ≥ first0,且循环过程中first的值是单调递增的,又half总是大于0,所以始终有middle ≥ first0。又middle = first+len/2 < first+len,循环开始时first+len = first0+last-first0 = last,且循环中随着first和len值的改变,first+len是递减的(),所以middle始终小于last。

既然middle的始终在[first0, last)内,对*(first0-1)和*last的值做出的假设就不会对计算过程有任何影响。前面说明了第23行处的postcondition为*(first-1) < value ≤ *first。当first0+1 ≤ first ≤ last-1时,很明显first处的元素值大于等于value,而first前一个值小于value,这和定义“the position of the first element that has a value less than or equal to value”相吻合。当first = first0时,只取postcondition的右半value ≤ *first,当first = last时,只取左半*(first-1) < value,均符合lower_bound的定义。此postconditition保证23行处的first值即所需结果。

以上是一个非形式化的、不严格的、对只有短短20多行的程序的正确性说明。感慨一下,要把C/C++程序写对,真不容易。


 

 





 


 

posted on 2009-06-17 19:30  yushih  阅读(633)  评论(0编辑  收藏  举报