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

推荐订阅源

量子位
S
Securelist
MyScale Blog
MyScale Blog
Jina AI
Jina AI
罗磊的独立博客
The Cloudflare Blog
美团技术团队
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
雷峰网
雷峰网
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
博客园 - 聂微东
Y
Y Combinator Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MongoDB | Blog
MongoDB | Blog
T
Tailwind CSS Blog
Attack and Defense Labs
Attack and Defense Labs
博客园_首页
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Hacker News
The Hacker News
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
U
Unit 42
D
Docker
Webroot Blog
Webroot Blog
N
Netflix TechBlog - Medium
T
Tor Project blog
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
B
Blog
Recent Announcements
Recent Announcements
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
Security Latest
Security Latest
V2EX - 技术
V2EX - 技术
N
News | PayPal Newsroom
Microsoft Security Blog
Microsoft Security Blog

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