惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
罗磊的独立博客
SecWiki News
SecWiki News
The Register - Security
The Register - Security
The GitHub Blog
The GitHub Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
S
Schneier on Security
IT之家
IT之家
博客园 - 聂微东
T
The Exploit Database - CXSecurity.com
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
Know Your Adversary
Know Your Adversary
Latest news
Latest news
Vercel News
Vercel News
G
GRAHAM CLULEY
D
DataBreaches.Net
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
SegmentFault 最新的问题
博客园_首页
雷峰网
雷峰网
T
Tenable Blog
Spread Privacy
Spread Privacy
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
Cisco Talos Blog
Cisco Talos Blog
V
Visual Studio Blog
J
Java Code Geeks
博客园 - Franky
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
C
CERT Recently Published Vulnerability Notes
T
Threatpost
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
P
Privacy International News Feed
T
Threat Research - Cisco Blogs
T
The Blog of Author Tim Ferriss
V
Vulnerabilities – Threatpost
Recent Announcements
Recent Announcements
Blog — PlanetScale
Blog — PlanetScale
Security Latest
Security Latest
U
Unit 42
M
MIT News - Artificial intelligence
Y
Y Combinator Blog
K
Kaspersky official blog
有赞技术团队
有赞技术团队
B
Blog
腾讯CDC

博客园 - yushih

初学Erlang,写两个程序玩玩 Django杂记: super与metaclass,locmem有害 一直没弄明白的事--为什么招聘要求会有“面向对象分析与设计”这一项 用Linux的iptables和Python模拟广域网 Python programming with goto 实用主义之过--Pragmatic Version Control using Subersion, 2nd Ed.的书评 Python把C语言打得满地找牙 信春哥!Python递归原地满状态变显式堆栈!入教即送尾递归优化! 关于文档标准之争的一点旁注 有关Ruby eval的一点编程风格 Design pattern一来,动态语言就笑了 - yushih - 博客园 完全没有领会“电子商务”的真谛 超级奇怪的F#格式错误 JAOO的魅力所在 一个Ruby idiom F#的一点糖 C++ hack:将C++编译器的类型检查转化为SLR(1)解析器 Quotes The Ruby Programming Language第一版非官方修正
STL的binary search算法正确性的初步说明
yushih · 2009-06-17 · via 博客园 - yushih

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++程序写对,真不容易。