二分查找
Posted GGBeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找相关的知识,希望对你有一定的参考价值。
刚进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]。
整理合并一下,有:
- A[mid] >= v:新区间为[l,mid];
- 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,区间为空。
用“上下界”函数求解范围统计问题的技巧非常有用,特别要用心体会左闭右开区间的使用方法和上下界函数的实现细节。
以上是关于二分查找的主要内容,如果未能解决你的问题,请参考以下文章