二分查找
[参考文献] https://www.bilibili.com/video/BV1d54y1q7k7/?share_source=copy_web&vd_source=586c54b04d52432e150fe6416a170629
二分的本质
设一个数组中有N个元素,前k个元素为蓝色,后N-k个元素为红色
但对于该数组,k的位置是未知的,即整个数组的颜色位置
我们需要求出蓝红边界,即k
朴素算法
设计一个蓝色指针,从左向右不断移动到边界或设计一个红色指针,从右到左不断移动到边界
时间复杂度:O(n)
因为指针移动是缓慢进行的,每次循环移动一次直到边界
二分查找
我们可以查询最中间元素颜色,若为蓝色,则该元素前全为蓝色,可以将蓝色指针直接拓展到该元素
重复操作,查询剩下区域最中间元素颜色,若为红色,则该元素后全为红色,可以将红色指针直接拓展到该元素
不断重复操作,直到最后找到蓝红边界
时间复杂度:O(logn)
因为每次循环中查找区域长度缩小一半,合计约logn次查找完毕
伪代码分析
1 | l=-1, r=N |
思路如下:
- 初始设置l,r指针分别指向蓝色和红色区域
- 循环体内进行二分,两指针快速向边界逼近,保持l,r颜色不改变
- 最后l与r正好指向蓝红边界,返回一个即可
细节分析
初始值为什么l是-1,r是N?
若整个数组为同一种颜色,则对应另一颜色的指针一开始便会出错
即如果哪边是开区间,则需要对应向外移动一位
m是否始终处于[0,N]内?
m尽可能小即l与r尽可能小,为保证进入循环体,lmin=-1,rmin=1,则mmin=0
m尽可能大即l与r尽可能大,为保证进入循环体,lmax=N-2,rmax=N,则mmax=N-1
不会溢出
更新指针时能不能写成l=m+1,r=m-1?
不能,若l或r指针在蓝色最后一位或红色第一位,这种方法可能会越过蓝色与红色区域
会不会死循环?
考虑以下情况
l+1=r:l的下一个元素是r,while循环体直接退出
l+2=r:l与r间隔了一个元素,求得m为l与r间隔的元素,将l或r设置为m,即回到第一种情况
l+3=r:会回归到第二进而第一种情况
之后的都会回归到第一种情况
故不会
即二分最后的分界线一定要唯一,不然返回值也不是唯一的
一般流程
- 建模:划分蓝红区域,确定分界线、检查方式
- 确定返回是l还是r
- 套用算法模板
- 加入后处理逻辑(opt)
算法模板
1 | int bsearch(int l, int r){ |
递归版本(查找某数位置或存在性):
1 | int bsearch(int *arr, int target, int l, int r){ |
例
对于数组 3 4 4 5 5 5 6 7 下标从1开始
找到第一个大于5的元素的位置下标:在最后一个5右边确认分界线,找线右侧第一个元素
分界线左边的数:都<=5 分界线右边的数:都>5
1
2
3
4
5
6
7
8
9
10
11
12bool isBlue(int x){
if (x<=5) return true;
else return false;
}
while(l+1!=r){
int mid = l+r>>1;
if (isBlue(q[mid])){
l=mid;
}
else r = mid;
}
return r;找到最后一个小于等于5的元素的位置下标:在最后一个5右边确认分界线1,找线左侧第一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13bool isBlue(int x){
if (x<=5) return true;
else return false;
}
while(l+1!=r){
int mid = l+r>>1;
if (isBlue(q[mid])){
l=mid;
}
else r = mid;
}
if (q[l]==5) return l; // 保证找到5
else return -1;
C++ STL
C++的stl中有两个函数可以快速实现二分查找:
头文件:#include
1. binary_search():查找元素存在性
模板:binary_search(arr[], arr[]+size, index)
- arr[]:数组首地址
- size:数组元素个数
- index:需要查找的值
原理:在数组中进行二分检索,若在数组中查到index元素则为真否则为假
注意:要求数组中元素非递减
2. lower_bound():查找第一个大于或等于某个元素的位置
模板:lower_bound(arr[], arr[]+size, index)
- arr[]:数组首地址
- size:数组元素个数
- index:需要查找的值
原理:在first与last的前闭后开区间中二分查找,返回大于等于index的第一个元素位置的地址,如果都小于,则返回last的位置
e.g.
3.upper_bound():查找第一个大于某个元素的位置
模板:upper_bound(arr[], arr[]+size, index)
- arr[]:数组首地址
- size:数组元素个数
- index:需要查找到值
原理:在前闭后开区间中查找关键字的上界,返回大于index的第一个元素位置的地址,如果插入元素大于数组中全部元素,返回的是last
浮点数二分
求一个浮点数n的三次方根
浮点数由于精度较高,无法直接暴力枚举,或者用整数二分
我们可以将while循环中的条件改为精度差小于某个浮点数(如1e-8)
一般让查找要求精度比题目要求精度精一些,则最后输出l或r都可以
优化:由于浮点数的不好控制性,我们可以直接固定二分次数
如下方固定100次,完全可以将浮点数二分到一个1e-30的程度
代码
1 | // 求一个数的立方根 |