当前位置:网站首页>模板系列-二分
模板系列-二分
2022-08-02 14:10:00 【老顽固也可爱】
二分模板
1、整数上的二分搜索
整数上的二分搜索,因为缩小搜索范围时,有可能 r = m i d − 1 r=mid-1 r=mid−1或 l = m i d + 1 l=mid+1 l=mid+1,因此可以用 a n s ans ans 记录可行解。是否需要减 1 或加 1 ,要根据具体问题分析。
l=a;
r=b; //初始搜索范围
while(l<=r) {
int mid=(l+r)/2;
if(judge(mid)) {
ans=mid; //记录可行解
r=mid-1;
}
else
l=mid+1;
}
2、实数上的二分搜索
实数上的二分搜索不可以直接比较大小,可以采用 r − l > e p s r-l>eps r−l>eps 作为循环条件, e p s eps eps 为一个较小的数,如 1 e − 7 1e-7 1e−7 等。为避免丢失可能解,缩小范围时 r = m i d r=mid r=mid 或 l = m i d l=mid l=mid ,循环结束时返回最后一个可行解。
l=a; r=b; //初始搜索范围
while(r-l>eps){
//判断差值
double mid=(l+r)/2;
if(judge(mid))
l=mid; //l记录了可行解,循环结束后返回答案l
else
r=mid;
}
// l 是答案
还可以运行固定的次数,如运行100次,可达 1 0 − 30 10^{-30} 10−30 精度,一般情况都可以解决问题。
l=a;
r=b;
for(int i=0; i<100; i++) {
//运行100次
double mid=(l+r)/2;
if(judge(mid))
l=mid;
else
r=mid;
}
// l 是答案
3、二分搜索详解
例如,给定n个元素序列,这些元素是有序的(假定为升序),从序列中查找元素x。
用一维数组S[]存储该有序序列,设变量low和high表示查找范围的下界和上界,middle表示查找范围的中间位置,x为特定的查找元素。
3.1 算法步骤
(1)初始化。令low=0,即指向有序数组S[]的第一个元素;high=n−1,即指向有序数组S[]的最后一个元素。
(2)判定low≤high是否成立,如果成立,转向第3步,否则,算法结束。
(3)middle=(low+high)/2,即指向查找范围的中间元素。如果数量较大,为避免low+high溢出,可以采用middle=low+(high-low)/2。
(4)判断x与S[middle]的关系。如果x=S[middle],则搜索成功,算法结束;如果x>S[middle],则令low=middle+1;否则令high=middle−1,转向第2步。
3.2 算法实现
用 BinarySearch(int n, int s[], int x)
函数实现折半查找算法,其中 n n n 为元素个数, s [ ] s[] s[] 为有序数组, x x x 为待查找元素。 l o w low low 指向数组的第一个元素, h i g h high high 指向数组的最后一个元素。如果 l o w ≤ h i g h low≤high low≤high, m i d d l e = ( l o w + h i g h ) / 2 middle=(low+high)/2 middle=(low+high)/2,即指向查找范围的中间元素。如果 x = S [ m i d d l e ] x=S[middle] x=S[middle] ,搜索成功,算法结束;如果 x > S [ m i d d l e ] x>S[middle] x>S[middle],则令 l o w = m i d d l e + 1 low=middle+1 low=middle+1 ,去后半部分搜索;否则令 h i g h = m i d d l e − 1 high=middle−1 high=middle−1 ,去前半部分搜索。
3.2.1 非递归算法
int BinarySearch(int s[],int n,int x) {
//二分查找非递归算法
int low=0,high=n-1; //low指向数组的第一个元素,high指向数组的最后一个元素
while(low<=high) {
int middle=(low+high)/2; //middle为查找范围的中间值
if(x==s[middle]) //x等于查找范围的中间值,算法结束
return middle;
else if(x>s[middle]) //x大于查找范围的中间元素,则从左半部分查找
low=middle+1;
else //x小于查找范围的中间元素,则从右半部分查找
high=middle-1;
}
return -1;
}
3.2.2 递归算法
递归有自调用问题,增加两个参数 low 和 high来标记搜索范围的开始和结束。
int recursionBS(int s[],int x,int low,int high) {
//二分查找递归算法
//low指向搜索区间的第一个元素,high指向搜索区间的最后一个元素
if(low>high) //递归结束条件
return -1;
int middle=(low+high)/2; //计算middle值(查找范围的中间值)
if(x==s[middle]) //x等于s[middle],查找成功,算法结束
return middle;
else if(x<s[middle]) //x小于s[middle],则从前半部分查找
return recursionBS(s,x,low,middle-1);
else //x大于s[middle],则从后半部分查找
return recursionBS(s,x,middle+1,high);
}
3.2.3 算法分析
3.2.3.1 时间复杂度
二分查找算法,时间复杂度怎么计算呢?如果用T(n)来表示n个有序元素的二分查找算法的时间复杂度,那么:
- 当n=1时,需要一次比较,T(n)=O(1)。
- 当n>1时,待查找元素和中间位置元素比较,需要O(1)时间,如果比较不成功,那么需要在前半部分或后半部分搜索,问题的规模缩小了一半,时间复杂度变为 T ( n 2 ) T(\frac{n}{2}) T(2n) 。
二分查找的非递归算法和递归算法查找的方法是一样的,时间复杂度相同,均为 O ( l o g n ) O(logn) O(logn)。
3.2.3.2 空间复杂度
二分查找的非递归算法中,变量占用了一些辅助空间,这些辅助空间都是常数阶的,因此空间复杂度为 O ( 1 ) O(1) O(1)。
二分查找的递归算法,除了使用一些变量外,递归调用还需要使用栈来实现。在递归算法中,每一次递归调用都需要一个栈空间存储,那么我们只需要看看有多少次调用。假设原问题的规模为n,那么第一次递归就分为两个规模为 n 2 \frac{n}{2} 2n 的子问题,这两个子问题并不是每个都执行,只会执行其中之一。因为和中间值比较后,要么去前半部分查找,要么去后半部分查找;然后再把规模为 n 2 \frac{n}{2} 2n 的子问题继续划分为两个规模为 n 4 \frac{n}{4} 4n 的子问题,选择其一;继续分治下去,最坏的情况会分治到只剩下一个数值,那么算法执行的节点数就是从树根到叶子所经过的节点,每一层执行一个,直到最后一层,如图所示。
递归调用最终的规模为1,即 n 2 x = 1 \frac{n}{2x}=1 2xn=1,则 x = l o g n x=logn x=logn 。假设阴影部分是搜索经过的路径,一共经过了 l o g n logn logn 个节点,也就是说递归调用了 l o g n logn logn 次。递归算法使用的栈空间为递归树的深度,因此二分查找递归算法的空间复杂度为 O ( l o g n ) O(logn) O(logn) 。
4、二分搜索需要注意的几个问题
- 必须满足有序性。
- 搜索范围。初始时,需要指定搜索范围,如果不知道具体范围,正数可以采用范围 [ 0 , i n f ] [0,inf] [0,inf] ,有可能为负数可以采用范围[-inf,inf],inf为无穷大,通常设定为 0 x 3 f 3 f 3 f 3 f 0x3f3f3f3f 0x3f3f3f3f。
- 二分搜索。一般情况下, m i d = ( l + r ) / 2 mid=(l+r)/2 mid=(l+r)/2 或 m i d = ( l + r ) > > 1 mid=(l+r)>>1 mid=(l+r)>>1 。如果l和r特别大,为避免 l + r l+r l+r 溢出,可以采用 m i d = l + ( r − l ) / 2 mid=l+(r-l)/2 mid=l+(r−l)/2 。判断二分搜索结束的条件,以及当判断 m i d mid mid 可行时到前半部分搜索,还是到后半部分搜索,需要具体问题具体分析。
- 答案是什么。特别小心搜索范围减少时,是否丢失在mid点上的答案。
边栏推荐
猜你喜欢
FP6296锂电池升压 5V9V12V内置 MOS 大功率方案原理图
Win11 system cannot find dll file how to fix
Open the door to electricity "Circuit" (3): Talk about different resistance and conductance
Binder ServiceManager解析
win10任务栏不合并图标如何设置
Mysql connection error solution
FP6195耐压60V电流降压3.3V5V模块供电方案
推开机电的大门《电路》(二):功率计算与判断
ECP2459耐压60V降压BUCK电路用于WIFI模块供电方案原理图
yolov5官方代码解读——前向传播
随机推荐
vscode镜像
SQL的通用语法和使用说明(图文)
jest test, component test
Daily - Notes
实战美团Nuxt +Vue全家桶,服务端渲染,邮箱验证,passport鉴权服务,地图API引用,mongodb,redis等技术点
Win10无法连接打印机怎么办?不能使用打印机的解决方法
为vscode配置clangd
CS4398音频解码替代芯片DP4398完全兼容DAC解码
将SSE指令转换为ARM NEON指令
FP5207电池升压 5V9V12V24V36V42V大功率方案
Summarize computer network super comprehensive test questions
FP6296锂电池升压 5V9V12V内置 MOS 大功率方案原理图
【系统设计与实现】基于flink的分心驾驶预测与数据分析系统
pygame绘制弧线
[STM32 Learning 1] Basic knowledge and concepts are clear
flink+sklearn——使用jpmml实现flink上的机器学习模型部署
arm push/pop/b/bl汇编指令
Mysql lock
FP6195耐压60V电流降压3.3V5V模块供电方案
Win11没有本地用户和组怎么解决