前言
当我们在一个序列中查找一个元素时,我们首先想到的可能是暴力解决(即从前向后依次查找),这种情况在数据很小的时候可以适用,但是在数据量很大的情况下效率相当低下,所以一般情况下这种方法是不适用的特别是在解决实际问题的时候。
一、二分搜索是什么和二分搜索的条件。
1.二分搜索的条件:想要二分搜索一个序列则该序列必须是一个有序的序列(一般是从小到大),也只有这样我们才可以进行二分搜索。
2.二分搜索是什么:当我们要一个有序的序列中查找一个值是否存在,我们可以找到当前的区间的左右边界下标l,r(初始时为0和序列长度-1),然后(l+r)/2 找到中间下标,通过比较中间的值和目标值的大小来进行下一次操作:1:如果中间下标值和目标值相等则返回中间下标。2:如果中间下标值小于目标值则摒弃中间下标左边的值(这就是为什么是二分操作),反之则摒弃右边的值,并且缩小区间(改变l或r的值,这里的改变方法有多种,下面会给出模板)。
3.优点:不难发现这里的搜索方法将时间复杂度降低至Ologn级别,在数据量很大时,效率远远高于暴力的On。
二、二分搜索模板
1.模板一(基本的二分搜索)
代码如下:
int Binary_Search(int a[], int target,int size) {(传入查找序列,目标值,序列长度)
int l = 0, r = size - 1;//(这里定义二分的初始区间,不同收缩区间方法对应的初始区间不一样,大家也可以上Leetcode看看其他模板)
while (l <= r) {
int mid = l + (r - l) / 2;//(这里 l + (r - l) 的操作 == l + r , 但是这样的话可以避免 l + r 溢出)
if (a[mid] == target)
return mid;//(找到目标值直接返回下标)
else if (a[mid] < target)
l = mid + 1;
else r = mid - 1;//(收缩区间)
}
}
2.模板二(寻找左边(右侧)边界)
上面我们的基本二分搜索如果目标值在查找序列之中重复的话那么返回的下标是随机的,但是有的时候我们需要的是最左侧的下标或者最右侧的下标,上面的模板显然满足不了我们的要求,下面就给出两种情况对应的模板。
代码如下:
1.寻找左侧边界:
int Binary_Search_Left(int a[], int target, int size) {
int l = 0, r = size;//(这里注意和上面的区间不一样)
while (l < r) {
int mid = l + (r - l) / 2;
if (a[mid] < target) {
l = mid + 1;
}
else r = mid;//(这里将a[mid] > target 和 a[mid] = target 的情况合并起来了 因为这里就算找到了目标值也不能返回下标要继续缩小区间寻找左侧边界值而操作和a[mid] > target情况一样)
}
return l;
}
2.寻找右侧边界值
int Binary_Search_Right(int a[], int target, int size) {
int l = 0, r = size;//(这里注意和寻找左侧边界值的区间一样)
while (l < r) {
int mid = l + (r - l) / 2;
if (a[mid] <= target) {
l = mid + 1;
}
else r = mid;//(这里和上面的寻找左侧边界一样合并操作但是是寻找右侧边界就将<=的情况合并了)
}
return l - 1;
}
我们可以发现两种情况对应的返回值不一样,一个是l,一个是l-1。这是因为上面情况确保a[mid]=target,r = mid,a[r] = target,最后循环结束条件l = r,所以直接返回l即可。而第二种情况则不一样最后的a[mid] = target 时 l = mid + 1,所以最后l = r时返回l - 1;
最后如果要判断是否找到了目标值 则修改return语句即可:
return (a[l] == target)?l:-1;
return (a[l - 1] == target)?l - 1:-1;
总结
这就是二分搜索的基本模板希望对大家有所帮助。