号外,号外 -几乎所有的binary search和mergesort都有错

转载 2006年06月04日 09:11:00

是Joshua Bloch(Effective Java的作者)在google blog上发的帖子。在说这个帖子之前,不得不强力重复Joshua Bloch的推荐:如果你还没有读过Programming Pearls (中文版叫《编程珠玑》)这本书,现在就去读吧。如果你只读了一遍,现在就去再读一遍吧。

还是说回Joshua的文章。当初Programming Pearls的作者Jon Bentley到CMU做讲座。他叫在场的计算机系博士生们写出binary search的算法,然后当场分析了其中一份。当然,那份算法以及绝大部分人写的算法都错了。Jon Bentley在Programming Pearls里也提到,虽然1946年就有人发表binary search,但直到1962第一个正确运行的算法才写出来。这个小故事的关键教训就是写程序时要仔细考虑算法的不变量(invariant)。如果我记得没错,Programming Pearls第4章讲解了怎么证明binary search的正确性。当然,每本离散数学的教科书都会教我们列出pre-condition, invariant, 和post-condition,证明循环开始前pre-condition成立,循环中invariant始终成立,而循环结束后post-condition被满足,而几乎每本教科书(至少我看过的)都会用binary search作例子。所以有兴趣的自己去看吧,俺就不罗嗦了。

JDK里的binary search代码是这样实现的(Joshua Bloch本人写的)

1:     public static int binarySearch(int[] a, int key) {
2: int low = 0;
3: int high = a.length - 1;
4:
5: while (low <= high) {
6: int mid = (low + high) / 2;
7: int midVal = a[mid];
8:
9: if (midVal < key)
10: low = mid + 1;
11: else if (midVal > key)
12: high = mid - 1;
13: else
14: return mid; // key found
15: }
16: return -(low + 1); // key not found.
17: }

错误就在第6行:
6:             int mid = (low + high) / 2;
这行的问题是当low和high的和超过2^31-1, 也就是Java里最大整数值时,整数溢出就发生了,而mid就变成负数了, 于是JVM就抓狂了,于是ArrayIndexOutOfBoundsException就发生了。

当一个数组包含多过2^30元素时,这个错误就会被发现。那么大的数组在80年代Programming Pearls第一版写就的时候难以想象,但在现在却很常见。所以说,尽管1962年正确的binary search问世,现实却是直到现在流行系统里的binary search还有错。

解决的办法不难。把第6行改写成

6:             int mid = low + ((high - low) / 2);

或者
6:             int mid = (low + high) >>> 1;

C和C++里没有这个">>>",我们可以这样做:

6:            int mid = ((unsigned) (low + high)) >> 1。

那现在binary search就完全正确了么?我们还是不知道。我们得到的深刻教训是,仅仅证明一个程序正确是不够的。我们必须仔细测试。高德纳在写给Peter van Emde Boas的信里说,“上面那段程序可能有错。我只证明了它是正确的,但还没有测过”。人们往往用这段话来彰显高德纳的一丝不苟和学究气,谁知道这句话背后是高德纳深刻的洞察力。人们常说“理论上讲实践和理论没有差别。实践上讲,两者确有差别”,可为旁证。

binary search的这个错误同样会出现在其它“分而治之”的算法里,比如说mergesort。如果你有类似的算法代码,赶快修改吧。Joshua说,他从中学到的教训是谦卑:哪怕一个简单的程序都很难写对,而整个社会却运行在庞大而复杂的代码上面。

最后的总结很有意思:我们程序员需要各种帮助,别无它法。仔细设计很好。测试很好。形式化方法很好(不过我还是觉得有教授研究用形式化电子商务需求(比如用范畴论),纯粹无事找事)。代码评审很好,静态分析很好。但他们并不能帮我们彻底消除代码错误--他们将永远存在。我们半个世纪以来竭尽全力都不能消除一个程序错误。我们必须小心翼翼,防御性地编程,并且保持警醒。    
                                                                         

 

正则表达式匹配括号外的符号

[\\?!/\\.,\\s]+(?=[^\\)]*(\\(|$)) 将括号外的?!/.,和空格(连续多个时同时)匹配 如 String string1 = "sdfsdf sdlfksd s...
  • huang_hws
  • huang_hws
  • 2012年11月07日 17:35
  • 1810

号外!号外!……

/// ///Depiction:你如果不开微博关注我, ///ObjPerson.Name永远不等于你! ///我的腾讯微博:http://t.qq.com/jialin3399 ///我...
  • shan9liang
  • shan9liang
  • 2011年12月25日 21:15
  • 1158

第6章 图像文件格式

第6章 图像文件格式   本章从大量的图像文件格式中选择了三种常用的图像文件结构和一种正在推广的图像文件格式进行介绍,目的是为读者分析图像和编程提供一个概貌。BMP位图格式是Windows上画图软件(...
  • yjyb
  • yjyb
  • 2007年09月18日 15:31
  • 1542

如何用MathType编辑带圈加号

在MathType中输入加减乘除这四则基本运算是非常简单的事情,加号和减号可以直接用键盘输入,但是叉乘号和除号需要使用模板来进行输入,直接利用键盘就没有办法输入的。但是加号除了这一种形式外,还有种比较...
  • EducationSoft
  • EducationSoft
  • 2016年08月04日 10:25
  • 556

号外 号外 博客搬家了

博客正式乔迁昨天鼓捣了一下午, 用 Hexo + Github 搭建了自己的博客, 采用Next主题, 简单了解实现而已, 有许多可div的模块, 动画 布局, 可深入研究.CSDN博客不会再更新了以...
  • xy_26207005
  • xy_26207005
  • 2017年06月24日 10:06
  • 189

Android 仿百合网超火爆社交app首页滑动效果

探探,百合网等神器的首页有一个相册加载个控件,通过左滑右滑加载新的照片,同时左滑丢弃这个照片,右滑则表明对这个照片感兴趣。这个效果是怎么实现的呢?1,Android3.0以后控件中增加了setTran...
  • xiangzhihong8
  • xiangzhihong8
  • 2016年09月03日 22:47
  • 1857

servlet和cgi

下面有关servlet和cgi的描述,说法错误的是?   servlet处于服务器进程中,它通过多线程方式运行其service方法   CGI对每个请求都产生新的进程,服务完成后就销毁   servl...
  • qq_26222859
  • qq_26222859
  • 2016年10月05日 21:02
  • 183

号外号外:谷歌可能退出中国

 CSDN综合报道 Google今天早些时候在官方博客发表了由谷歌高级副总裁、公司发展兼首席法律顾问David Drummond撰写的题为《A new approach to China(无法直接访问...
  • KerryZhu
  • KerryZhu
  • 2010年01月13日 15:39
  • 1601

号外号外--JCreator 有 v3.5 咯

    我不知道我是不是过时了,也许我想说的很多人早就知道了,我知道的 JCreator 的最新版本是 JCreator pro v3.5.0.10。    现在连我自己都不知道为什么我会如此钟爱这款...
  • liltos
  • liltos
  • 2005年04月25日 14:26
  • 1084

号外号外!RancherOS v1.2.0发布啦!

RancherOS v1.2.0版本于北京时间2月7日正式发布,从v1.1到v1.2开发周期中,我们收集到了社区用户和商业用户的Bug report和Feature request,感谢大家为此作出的...
  • RancherLabs
  • RancherLabs
  • 2018年02月08日 15:26
  • 38
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:号外,号外 -几乎所有的binary search和mergesort都有错
举报原因:
原因补充:

(最多只允许输入30个字)