论二分的调试--带你告别二分写挂

1前言

二分想必大家都会吧,请看如下代码**(求出单调序列中最先出现的m)**

int find(int x){
	int l = 1,r = n;
	while(l<r){
		int mid = (l+r+1)>>1;
		if(a[mid]>x){
			r = mid-1;
		}else{
			l = mid;
		}
	}
	if(a[r]==x){
		return r;
	}else{
		return -1;
	} 
}

请指出错误并改正,是不是很难一眼看出来
二分是一个“玄学”算法
思路可以想对,但是一写代码就调不对,本文将治理你的各种疑难杂症

2典型的错误

作者在调二分题的时候,出现的问题可以概括为两种
1陷入死循环
2答案错误

我们将分别解决

3为什么错误-问题的分类

首先,二分分为整数二分和浮点数二分
整数二分也有两种,区间左端的值和区间右端的值
很多错误都是混淆了这些问题而导致的

4分类解决问题

(1)整数二分

我们运行前言中的代码,发现求出的是右端值,和求左端值哪里不同呢,我们逐条语句分析
首先,mid的取值,int型变量的除法是自动向下取整的,如果l+r是偶数,再加一不会有影响
关键在于l+r是奇数的情况,究竟该向下取整还是向上取整呢?
如果l,r相差得多,mid取什么没有影响,那么我们假设此时l+1 = r
如果向下取整,mid = l,如果mid正好符合要求,那么l一定要更新到mid+1,不然l,r,mid都不变,就导致了死循环
所以如果向下取mid,则l+1,如果向上取mid,则r-1
但是我们该如何选取这两种做法呢?
我们考虑mid恰好符合题意
如果要分出左端,就要更新r,否则更新l
但是对于上述问题,如果选取mid向上取整,r-1,就跳过了这个合法情况
如果r-1不合法呢,直接就输出无解了

所以,我们总结一下
遇到二分问题,先要看合法情况,分出左端r = mid,分出右端l = mid,r = mid,mid向下取整,l = mid,mid向上取整
分出右端的二分模板上文已经给出,这里附分出左端的代码(c++)

int find(int x){
	int l = 1,r = n;
	while(l<r){
		int mid = (l+r)>>1;
		if(a[mid]>=x){
			r = mid;
		}else{
			l = mid+1;
		}
	}
	if(a[l]==x){
		return l;
	}else{
		return -1;
	} 
}

(2)浮点数二分

我们依旧采用上面的思考方式,这里可以考虑精度,把l<r改成(r-l)<=1e-6
浮点数二分好考虑多了,mid设为(l+r)/2即可,因为浮点数不用考虑取整
因为不是整数,所以没有区间问题,分的是一个点,所以l,r均不用+1,最后,相等情况直接输出就行
附代码(c++)

while((r-l)>=eps){
	double mid = (l+r)/2;
	if(check(mid)){
			l = mid;
	}else{
			r = mid;
	}
}

5后记

二分成为玄学,其实是因为二分的细节太多,把问题类型搞明白,一定有一个完整的思考过程
不要直接背模板!不要直接背模板!不要直接背模板!没人能背下来!
最后,如果熟练了,模板自然形成,不用再调试“玄学”的二分了
本文作者是蒟蒻,如有错误请各位神犇指点!
森林古猿出品,必属精品,请认准CSDN森林古猿1!

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值