二分查找
来源:互联网 发布:广州公务员待遇 知乎 编辑:程序博客网 时间:2024/06/05 07:52
二分查找
以前学的时候也没好好研究,只是大概知道思想,昨天写程序的时候发现bug了,十分惭愧,结果在网上发现了这样资料:
二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组。将数组的中间项与T进行比较,可以排除一半元素,范围缩小一半。就这样反复比较,反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。对于包含N个元素的表,整个查找过程大约要经过log(2)N次比较。 多数程序员都觉得只要理解了上面的描述,写出代码就不难了;但事实并非如此。如果你不认同这一点,最好的办法就是放下书本,自己动手写一写。试试吧。 我在贝尔实验室和IBM的时候都出过这道考题。那些专业的程序员有几个小时的时间,可以用他们选择的语言把上面的描述写出来;写出高级伪代码也可以。考试结束后,差不多所有程序员都认为自己写出了正确的程序。于是,我们花了半个钟头来看他们编写的代码经过测试用例验证的结果。几次课,一百多人的结果相差无几:90%的程序员写的程序中有bug(我并不认为没有bug的代码就正确)。 我很惊讶:在足够的时间内,只有大约10%的专业程序员可以把这个小程序写对。但写不对这个小程序的还不止这些人:高德纳在《计算机程序设计的艺术 第3卷 排序和查找》第6.2.1节的“历史与参考文献”部分指出,虽然早在1946年就有人将二分查找的方法公诸于世,但直到1962年才有人写出没有bug的二分查找程序。 – 乔恩.本特利,《编程珠玑(第1版)》第35-36页
只有10%的程序员可以写出正确的二分查找程序,看来我还不属于这10%。
当时错误的原因很简单,就是一个小小的bug陷入的死循环:
int bSearch(vector<int>a,int x) { int l=0,r=a.size()-1; int mid=(l+r)/2; while(l<r) { if(a[mid]<x) l=mid; else if(a[mid]>x) r=mid; else break; mid=(l+r)/2; } return mid; }
乍一看是正确的,但是会陷入死循环,比如一个vector<int>a
,从大到小存了9个数,设为1到9,现在要找到9的位置,那么执行上述代码时,mid依次为4,6,7,7,7…,陷入死循环。
正确的方法是将第7,8句修改,变成l=mid+1,r=mid-1,当然,这是保证x在a里的时候,如果x有可能不在a中,那么正确而且完整的代码如下(参考《编程珠玑(第二版)》第87页):
int bSearch(vector<int>a,int x) { int l=0,r=a.size()-1; int mid; for(;;) { if(l>r) return -1; mid=(l+r)/2; if(a[mid]<x) l=mid+1; else if(a[mid]==x) return mid; else if(a[mid]>x) r=mid-1; } return mid; }
然后我看到了一道课后题:用二分法返回数组a中出现的第一个x的位置,我是这样想的:
在上面的基础上进行改进,如果已经搜到x的一个位置,那么如果x的前一个位置比x小,则意味着第一个x已经找到,如果前一个位置也是x,那么将r修改为mid-1;需要注意的是,如果mid==0,则直接判断mid是否是x即可,代码如下:
int bSearch2(vector<int>a,int x) { int l=0,r=a.size()-1; int mid; for(;;) { if(l>r) return -1; mid=(l+r)/2; if(mid==0) { if(a[mid]==x) return mid; else return -1; } if(a[mid]<x) l=mid+1; else if(a[mid]==x) { if(a[mid-1]<x) return mid; else r=mid-1; } else if(a[mid]>x) r=mid-1; } return mid; }
但是看了《编程珠玑(第二版)》的答案后,发现答案的方法比我的思路还有效率都清晰多了:
初始的循环不变式是:a[l]<x && a[r]>=x && l<r
,代码如下:
int bSearch3(vector<int>a,int x) { int l=-1,r=a.size(); int mid; while(l+1!=r) { mid=(l+r)/2; if(a[mid]<x) l=mid; else r=mid; } if(r>=a.size() || a[r]!=x) return -1; return r; }
一般的程序用这个就已经很好了,因为相比于bSearch2,bSearch3每次迭代只比较一次。
当然,书中还提到了进一步的优化,最终将结合搜索数组的大小,将循环展开,这样可以借助于现在计算机的流水线处理技术来增加指令集的并行,从而进一步的优化,不过一般情况下bSearch3的表现就已经相当出色,而且更容易编写和实现。
int bSearch4(vector<int>a,int x) { int l=-1; if(a[512]<x) l=1000-512; if(a[l+256]<x) l+=256; if(a[l+128]<x) l+=128; if(a[l+64]<x) l+=64; if(a[l+32]<x) l+=32; if(a[l+16]<x) l+=16; if(a[l+8]<x) l+=8; if(a[l+4]<x) l+=4; if(a[l+2]<x) l+=2; if(a[l+1]<x) l+=1; int p=l+1; if(p>1000 || a[p]!=x) p=-1; return p; }
这是一个1000个数据的二分查找的优化。
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- 二分查找
- maven打包依赖程序和配置文件
- 字典NSDictionary
- CentOS下安装配置 Ngnix_tomcat_PHP_Mysql
- linux进程状态D和Z的处理
- 集合与数组NSSet
- 二分查找
- 维护兼容性
- 集成Tomcat插件到Eclipse
- NSNumber与NSValue
- Android Binder知识简介
- 如何查看goldengate版本
- 日期与时间戳NSDate
- 我要发布
- 开箱即用!Android四款系统架构工具