Phil的课堂笔记——二分

二分

今天是黄金周的第二天,我们又开始上课了QWQ
今天讲的是二分
那我们先来看一下目录ba~

  1. 二分是什么
  2. 二分的两种板子
  3. 例题(openjudge 07:和为给定数
  4. 二分流程总结
  5. 实数上的二分

0.0 二分是什么

二分二分,顾名思义,就是将查找的区间分成两半,找中间的部分,然后判断查找左半边还是右半边。
很显然,二分有一个非常重要的条件:查找元素必须有序
不然就难以判断它到底往左找还是往右找
那么,二分到底优秀在哪呢?

我们知道,评价一个算法,我们可以从时间复杂度和空间复杂度来判断
二分查找的空间复杂度:
二分查找的时间复杂度:
————————————————分割线

举个栗子
假设小明去图书馆借书,(小明:每次都叫我背锅qwq)
假设小明的书包里有100本书,但他不知道书包里的书是不是他的,他现在需要从书包里找出那本不是他的书,该怎么办呢?
(小明:吼吼吼!!!!??你太坏了,我有那么笨吗??)

我们当然可以1本1本的找,但是那样实在是太慢了!!毕竟浪费时间就是谋财害命呢!
所以工作人员想了个好办法:

  1. 先取出50本书,过安检机,如果没有响,那说明书在另一堆中;
  2. 如果安检机响了,那说明书在这一堆书当中
  3. 那么我们继续这两步直到最后就可以找出来那本书的位置了

那么爱思考的小朋友们不难发现,二分的好处就是把困难的求解转化成了判断,从而大大缩短了算法的时间复杂度
小明欢呼:终于可以不用被这个可恶的作者恶搞了!!

—————————————————————————手动分割线

在这里插入图片描述

在这里插入图片描述
课堂截图(捂脸)+(逃跑~~~~~~~~~~~

1.0 二分板子

!剩下两个数的时候

我在二分过程中遇到一个问题,就是剩下两个数的时候我们应该如何处理最后的两个数呢
1号位 2号位

这里我们出两个例题,请大家做完以后再看以下内容;

  1. 查找有序序列中第一个>=x的数,如果没有返回None
  2. 查找有序序列中距离x最近的<=x的数,如果没有返回None

这两个虽然看起来很像,但其实是两个不同的模板;

这里我们就应该分两种情况来讨论了,这里给大家两个板子
·

!板子1——单调递增区间查找第一个>=x的值
mid=(l+r)/2;

mid就是第一位的数

  1. 如果查找数x > mid,说明x是右面的数
    至少也要从mid的下一位开始查找,查找区间为[mid+1,n]
    所以l = mid +1;
  2. 如果查找数=mid,说明x是左面的数,即x=l;
    这时候我们就需要就把右边的记号移到左边来
    r = mid;
  3. 如果查找数<mid,不存在,因为不在这一段序列中

核心代码如下

	while(l<r){
		mid = ( l + r ) / 2;
	 	if(x <= a[mid])	 r = mid;
  		else 			 l = mid + 1;
	}
!板子2——单调递增区间查找第一个<=x的值

第二种情况,我们要找第一个比x小的数
我们来分析一下这个问题在这里插入图片描述
(请大家原谅我拙劣的画工,毕竟没有触控!!)
我们假设一个数列L到R,mid是中间值
我们查找的数是x

  1. 如果x>mid

在这里插入图片描述
,那么mid肯定不是答案,
查找范围最少也应该是mid+1到最后
但是加上下面这种等于的情况
在这里插入图片描述查找范围就应该是mid到最后,l = mid;
在这里插入图片描述
2、如果x<mid
在这里插入图片描述

那么此时的mid是不可能成为答案的,所以右边的部分他们更没戏!
所以查找范围就变成了
在这里插入图片描述
核心代码

	while(l<r){
		mid= (l + r + 1) / 2;
	 	if (x >= a[mid])	 l = mid;
  		else 			 	 r = mid - 1;
	}

!这里注意

mid= (l + r + 1) / 2;``

想想为什么呢?

——————————————————————————分割线
看到这里,大家应该去自己去刷一点水题了
比如这个 07:和为给定数
在这里插入图片描述
思路:
先举个例子
1 2 4 5四个数
然后需要和为6

那么我们从1开始枚举,因为和是6,所以当枚举到每个数的时候,只需要看另一个加数在不在序列中就可以了;
比如说,1;
我们只需用二分查找,看看5在不在序列中;

放ac代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
int a[100005];
int main (){
	scanf("%d",&n);
	for(int i =1; i<= n;i++)
		scanf("%d",&a[i]);
	scanf("%d",&m);
	
	sort(a+1,a+n+1);
	for(int i = 1 ; i <= n; i++){
		int l = i+1,r = n,b = m-a[i];
		while(l<r){
			int mid = ( l + r ) / 2;
			if(b <= a[mid])	r = mid;
			else l = mid + 1;	
		}
		if(b == a[l]){
			printf("%d %d\n",a[i],b);
			return 0;
		}
	}
	printf("No");
	return 0;
}

P.S. 打标记法(似乎要爆空间)
先开一个数组,输入的时候数组++;
这样我们就知道每个数在数列中有几个

循环过程中:每个数–;代表取走这个数,
然后判断另一个加数是否为0个,如果不是就能输出了

这种方法就可以用O(n)的时间过
但可惜这个题用这种方法似乎爆空间了

1.X 二分流程总结

  1. 分析问题,确定左右半段哪个是符合题意的区间,以及mid归属于哪一段
  2. 分析结果,选择两种板子
	mid = (l + r) / 2;
	r = mid, l = mid + 1; 

或者

	mid = ( l + r + 1) / 2;
	l = mid; r = mid - 1;
  1. 二分终止条件是l == r;
    也是答案所在的位置

实数上的二分

  • 实数上的二分其实非常简单
  • 首先要考虑精度的问题,实数的相等不能直接用==来判断,
    二者差在一个很小很小的范围内我们认为就相等了;
    l + eps < r 为循环条件
    一般需要保留k位小数时,精度eps=10^-(k-2)
  • 有时不容易确定精度,我们也可以用循环固定次数的二分方法
	for(int i = 0; i <= 100; i++){
		double mid =(l + r)/2;
		if(jud(mid))	r = mid;
		else 			l = mid;
	} 
  • 因为实数上不需要考虑相等的情况下,区间归属的问题
    所以直接划分就好啦!
    是不是很nice
while(l + 1e-6 < r){
	double mid = (l + r)/2.0;
	if(jud(  ))     l = mid;             //jud里面是判断函数
	else 			r = mid;
}

几道实数二分的题

详情请见下一篇博客——二分答案
Luogu P1024 一元三次方程求解
Luogu P1577 切绳子

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值