无处不在的二分查找

来源:互联网 发布:win8无法连接到此网络 编辑:程序博客网 时间:2024/04/27 17:07

引入

什么是二分查找?

先来一个简单的游戏,我选定了1-100之间的某个数,你来猜。50?太大了。25?太小了。如此,游戏进行下去,直到你猜中为止。这样,对区间为1-n之间的整数,你可以在log(n)次之内猜中。

在某一给定范围内,查找某个对象,每次查找可以判断该对象与相应位置值的关系。二分查找通过查找当前范围的中间位置来定位,这样每次查找之后,查找的范围都会变为原来的一半。二分查找其实是一种分而治之的思想,将查找的任务分解为两个子空间,每次比较都判断下一个查询的子空间,依次迭代之后,最终达到查询的目的。

 

性能

对比用顺序查找的方法,如从1开始往n逐个猜测,平均需猜测n/2次。

如果n为100万,使用二分查找,只需20次,而顺序查找平均需100万次,可以看出二分查找的强大性能。

关于这种方法快速的实质,在《数学之美番外篇》中,有很生动的探讨,基本idea是,每次查找得到的分支,都是等概率的。

 

用处

二分查找应用范围很广,最常见的当然是有序数组中的元素查找。数学上,求解方程的近似解时,也会用到二分法查找。

在《编程珠玑》”2、9章都有提及。而《编程之美》3.11节“程序改错”中,提到的第一个改错就是二分查找。

 

用法

1.最简单的查找是在有序数组中,查找某个数,返回任意一个满足的索引。

如数组1,5,19,20,31,31,401 中查找31,满足的有2个,则查找返回任意一个位置都可以。

算法设计很简单,使用三个标记:l,u,m;  l代表当前搜索的范围的下界,u代表上届,m为l和u的中值(向下取整)。

迭代过程只需比较m位置的值与x(所查找的值)关系,若 a[m]>x,则x只可能出现在左边的区间,此时只需修改u为m-1;若a[m]<x,则x只可能出现在右边的区间,此时只需修改l的值为m+1;迭代终止条件为l值大于u。

int bisearch_v1(int*a,int n,int x){int l=0,u=n-1,m;while (l<=u){m=l+(u-l)/2;//注意 没有使用m=(l+u)/2;防止越界if(a[m]==x) return m;if (a[m]>x)u=m-1;elsel=m+1;}return -1;}


2.上述算法有两个问题:

a.每次循环迭代,需要比较两次,有没有更高效的算法?

b. 有多个符号条件的位置时,不能返回特定位置要求。

重新定义问题:在有序数组中,查找某个数,返回一个满足的索引,如有多个位置满足要求则返回第一个满足的索引

关键思路: 仍然使用三个标记:l,u,m有关系式a[l]<x<=a[u],并假设 a[-1]<x,a[n]>=x其中l 代表检索范围内,已检索的小于x的最大索引,初始值为-1u代表检索范围内,已检索的大于或等于x的最小索引。迭代终止条件l+1==u,此时l 指向的是有序数组中,小于x的最大索引,而u为大于或等于x的最小索引。只需检测u,a[u]与x的关系即可。

int bisearch_v2(int *a,int n,int x){//注意对比1中 l,u的初始值,迭代终止条件,以及l,u的修改方法int l=-1,u=n,m;while (l+1!=u){m=l+(u-l)/2;if (a[m]<x)l=m;elseu=m;}if(u!=n&&a[u]==x)return u;elsereturn -1;}

3.类似2中描述,如《编程之美》所提出的 ,找出一个有序(字典序)字符串数组中,值等于字符串v的元素的序号,如果有多个元素满足这个条件,则返回其中最大的序号。

可用2中分析的方法来解。

同时该书中提到多个二分查找类似的问题,摘录如下:

a.给定一个有序(不降序)数组arr,求任意一个i使得arr[i] 等于v,不存在则返回-1

b.给定一个有序(不降序)数组arr,求最小的i使得arr[i] 等于v,不存在则返回-1

c.给定一个有序(不降序)数组arr,求最大的i使得arr[i] 等于v,不存在则返回-1

d.给定一个有序(不降序)数组arr,求最大的i使得arr[i] 小于v,不存在则返回-1

e.给定一个有序(不降序)数组arr,求最小的i使得arr[i] 大于v,不存在则返回-1

有兴趣的朋友可以思考下。

原创粉丝点击