码迷,mamicode.com
首页 > 其他好文 > 详细

二分查找

时间:2017-08-19 00:40:24      阅读:282      评论:0      收藏:0      [点我收藏+]

标签:基于   思维   排序   nbsp   遍历   sea   返回值   算法   acm   

刚进ACM集训队,天天刷题,然后好多题解都有二分两个字,哎呀我天!!我那个无助。还好,今天碰到了,不把你吃透,算我输!!!

学习二分不在于表面的题目,而在于理解二分的思想。

二分查找往往用在一些抽象的场合,没有数组A,也没有要查找的v,但是二分的思想仍然适用。

 

我们来一个实际的问题:现有一个由106个数组成的数组A,现在给你一个数,比如12345,让你判断它是否在数组A中。

解法一:遍历数组A,逐个比较。

弊端:此解法对于“单次询问”来说运行得很好,但如果需要找105个数,即进行105次询问,那么就需要把整个数组A遍历105次。

解法二:先将数组A排序,再逐个比较。

优点:???此刻的我还真看不出。。。

 

在有序表中查找元素常常使用二分查找。

二分查找的基础是:数组有序。

二分查找的基本思路:每次将范围缩小一半,直到找到目标。

举例:饭桌上常玩的“猜数字游戏”,你在心里想一个不超过1000的正整数,我可以保证在10次之内找到它——只要你每次告诉我猜的数比你想的大一些、小一些,或者正好猜中。猜的方法就是“二分”。首先我猜500,除了运气特别好正好猜中之外, 不管你说“太大”还是“太小”,我都能把可行范围缩小一半:如果“太大”,那么答案在1~499之间;如果“太小”,那么答案在501~1000之间。只要每次选择可行区间的中点去猜,每次都可以把范围缩小一半。由于log21000<10,10次一定能猜到。

逐步缩小范围法是一种常见的思维方法。二分查找便是基于这种思路,它遵循分治三步法,把原序列划分成元素个数尽量接近的两个子序列,然后递归查找。

二分查找只适用于有序序列,时间复杂度为O(logn)。

二分查找一般写成非递归形式。

代码:二分查找(迭代实现)

int bsearch(int *A, int l, int r, int v)
{
	int mid;
	while(l < r){
		mid = l+(r-l)/2;
		if(A[mid] == v)	return mid;
		else if(A[mid] > v)	r = mid;
		else	l = mid+1;
	}
	return -1;
}

 

二分查找往往用在一些抽象的场合,没有数组A,也没有要查找的v,但是二分的思想仍然适用。

 

算法学了有些天了,若是就这么结束二分,我觉得这就不是一个算法了。

 

我们在提一个问题:如果数组A中有多个元素都是v,上面的函数返回的是哪一个的下标呢?第一个?最后一个?都不是。不难看出,如果所有元素都是要找的,它返回的是中间那一个。有时,这样的结果并不是很理想,能不能求出值等于v的完整区间呢(由于已经排好序,相等的值会排在一起)?

下面我们编写一个这样的程序:当v存在时返回它出现的第一个位置。如果不存在,返回这样一个下标 i:在此处插入v(原来的元素A[i],A[i+1],...全部往后移动一个位置)后序列仍然有序。

int lower_bound(int *A, int l, int r, int v)
{
	int mid;
	while(l < r){
		mid = l+(r-l)/2;
		if(A[mid] >= v)	r = mid;
		else	l = mid+1;
	}
	return l;
}

最后的返回值不仅可能是l,l+1,l+2,...,r-1,还可能是r——如果v大于A[r-1](这时只能插入这里了)。这样,尽管查找区间是左闭右开区间[l,r),返回值的候选区间却是闭区间[l,r]。

A[mid]和v的3种关系带来的影响如下:

  • A[mid] = v:至少已经找到一个,而左边可能还有,因此区间变为[l,mid];
  • A[mid] > v:所求位置不可能在后面,但有可能是mid,因此区间变为[l,mid];
  • A[mid] < v:m和前面都不可行,因此区间变为[m+1,r]。

整理合并一下,有:

  1. A[mid] >= v:新区间为[l,mid];
  2. A[mid] < v:新区间为[mid+1,r] 。

注意:这里有一个潜在危险,如果[l,mid]或者[mid+1,r]和原区间[l,r]相同,将发生死循环!幸运的是,这样的情况并不会发生!!!(思考原因)

 

类似地,我们也可以写一个upper_bound程序,当v存在时返回它出现的最后一个位置的后面一个位置。如果不存在,返回这样一个下标 i:在此处插入v(原来的元素A[i],A[i+1],...全部往后移动一个位置)后序列仍然有序。不难得出,只需把 “if(A[mid]>=v) r=mid; else l=mid+1;” 改成 “if(A[mid]<=v) l=mid+1; else r=mid;” 即可。

这样,对二分查找的讨论就比较完整了:设lower_bound和upper_bound的返回值分别为L和R,则v出现的子序列为[L,R) 。这个结论当v不在时也成立:此时L=R,区间为空。

 

用“上下界”函数求解范围统计问题的技巧非常有用,特别要用心体会左闭右开区间的使用方法和上下界函数的实现细节。

二分查找

标签:基于   思维   排序   nbsp   遍历   sea   返回值   算法   acm   

原文地址:http://www.cnblogs.com/xzxl/p/7392583.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!