二分查找 -- 代码之美

二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

大多数程序员认为,有了上面的描述,写出代码是很简单的事情。他们错了。能使你相信这一点的惟一方法是现在就合上书,去亲手写写代码试试看。

二分查找是一个非常好的例子,因为它是如此简单,却又如此容易被写错。在《Programming Pearls》一书中,Jon Bentley记述了他是怎样在多年的时间里先后让上百个专业程序员实现二分查找的,而且每次都是在他给出一个算法的基本描述之后。他很慷慨,每次给他们两个小时的时间来实现它,而且允许他们使用他们自己选择的高级语言(包括伪代码)。令人惊讶的是,大约只有10%的专业程序员正确地实现了二分查找。

更让人惊讶的是,Donald Knuth在他的《Sorting and Searching》[1]一书中指出,尽管第一个二分查找算法早在1946年就被发表,但第一个没有bug的二分查找算法却是在12年后才被发表出来。

在Java语言中,这个bug导致一个ArrayIndexOutOfBoundsException异常被抛出,而在C语言中,你会得到一个无法预测的越界的数组下标。

以下就是带有这个著名的bug的Java实现:

public static int binarySearch(int[] srcArray, int des) {
	int low = 0;
	int high = srcArray.length - 1;
	while (low <= high) {
		int middle = (low + high) / 2;
		int currentValue = srcArray[middle];
		if (des == currentValue) {
			return middle;
		} else if (des < currentValue) {
			high = middle - 1 ;
		} else {
			low = middle + 1;
		}
	}
	return -1;
}
Bug位于这一行:
int mid = (low + high) / 2;

如果low和high的和大于Integer.MAX_VALUE(在Java中是231 -1),计算就会发生溢出,使它成为一个负数,然后被2除时结果当然仍是负数。

推荐的解决方案是修改计算中间值的方法来防止整数溢出。方法之一是用减法——而不是加法——来实现:
int mid = low + ((high - low) / 2);

想一下,二分查找算法的思想是多么的简单,而这么多年又有多少人的多少智力花在它上面,这就充分说明了即使是最简单的代码也需要测试,而且需要很多。Joshua Bloch在它的blog中对这个bug作了非常漂亮的陈述:
这个bug给我上的最重要的一课就是要懂得谦逊:哪怕是最简单的一段代码,要写正确也并非易事,更别提我们现实世界中的系统:它们跑在大段大段的复杂代码上。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值