写在前面

今天学习了二分法,整理了一下笔记,觉得是一种非常实用的方法
首先,学习历程重重困难,文章一定会难以避免的冗长,所以在前面先附上我制作的思维导图,大家可以先点进去看,思维导图是用的知犀思维导图,不过我觉得好像不用下载那个点开链接输入密码后也可以看,我觉得思维导图的表现力应该比文字更好,所以大家可以先看这个思维导图,有个大致的思路
链接:https://www.zhixi.com/view/495d55c8
密码:4292
并且一些关于二分的题目也一并放在思维导图中啦!大家可以看一下!

正文

什么是二分

二分法是一种十分常见的算法,其意很简单明了,就是挑一半,挑一半,再挑一半。。。。。。
当然这么说可能有点抽象,那就举个例子:
比如要从0——100中找一个数(当然是随机的,就类似于猜数字),人家选好了一个数,让你去猜,聪明的你当然不可能从1一直猜到100,这样未免太暴力了,于是有一种更好的办法,那就是先猜50,也就是取一半,然后让对方回答是大了还是小了,这就是把100个数分成了两半,无论是大了还是小了,我们都会知道有答案的那一边(哦,除非答案就是五十),然后重复上次操作,如果答案在左边就猜25,否则猜75.。。。。。。这样一直重复下去,总可以猜出答案。
这就是所谓的二分,那我们再来看一下这个例子。首先,是从0——100随机出一个数,而0——100是一个单调的区间,另外假如我们要猜的数是70,那么我们第一次二分后的0——50这个区间不满足有答案的这个性质(答案不在这里面),所以我们舍弃了他,在此进行二分重复上述动作直至二分出答案。
当然我们要注意的是:有单调性的题一定可以二分,能二分的题不一定有单调性!
故而我们可以得出二分的条件:
1.确定一个区间,使得目标值必然在区间内
2.找到一个性质,使得具有二段型且答案最好是二段性的分界点
那二分是什么呢?
二分:在某一区间上存在某种性质,使得整个区间一分为二,一半区间内有该性质,一半区间内不满足该性质,那么二分就可以寻找性质的边界,是一种查找方式

如何二分

二分分为两种,一种是整数二分,一种是实数二分

整数二分(不连续,是由一堆整数组成)

那接下来就要说一说如何整数二分了,我们先把一段分成两段,通常是从中间开始找中间值判断

假如说我们要二分橙色边界点,我们先找中间值mid

当然这里只是我画的像是在分界点,这只是赶巧了而已,有很多情况橙色与绿色的范围差还是很大的
然后我们检查这个点满不满足橙色的性质(或是是不是满足绿色的性质),*如果满足橙色的性质(也就是不满足绿色的性质)*那因为我们是要找橙色的边界,故其边界一定是在mid的右边,当然mid也有可能是答案(嗯。。。看来这个图画的就是这种情况)所以我们更新l=mid,新区间变成了mid至r,后再次进行二分(不断二分),一直到只剩下一个单位的时候,它就是答案。
那么如果我们刚开始的那个mid点不满足橙色的性质(或是满足绿色性质)呢?

那么说明那个mid在绿色的范围里,答案一定在mid左边,且不包括mid故而我们更新的对象就变成了 r=mid-1 , 范围区间就变成了l至mid-1

当然在这里又有一个重点,那就是如果我们只是人畜无害的将mid=l+r/2的话,在这里会出现一个问题,那就是当二分至最后只有两个数时即l=r-1时我们再次二分,此时由于除法的向下取整,我们做mid=(l+r)/2时得到的答案是mid=l,也就是我们的更新条件为l=l,它的范围区间没有得到更新,下一次取中点值还是l=l,这样会无限递归下去,也就是边界问题,为了解决这类问题,我们让每次取中点时这样取:mid=(l+r+1)/2这样就可以避免出现这种情况,这样当我们二分至l=r-1时,我们的mid=2r/2=r,故而此时二分结束,答案就是这里的r
好了,恭喜你!现在完成了橙色分界点的探索!
那假如说我们要找绿色的分界点呢?
当然也是一样了!先找到中间值看它是否满足绿色的性质
如果那个中间值满足橙色的性质

那么由于我们找的是绿色的边界点,于是我们的边界点一定在mid的右边(不包括mid),故而我们的更新条件就是l=mid+1,然后更换新的区间再次进行二分即可
同样,如果我们的中间值满足绿色的性质

显然,我们的绿色边界在mid的左半边(也有可能是在mid值上),也就是我们需要更新r值,将它更新为r=mid

在这种方式中,当我们一直二分到l=r-1时,我们再次二分mid=(l+r)/2=l(向下取整)由于此时我们的更新条件要么是l=mid+1要么是r=mid;无论是哪种情况,最后都可以得到长度为一的,也就是最后的值(比如更新状态是l=mid+1时,由于mid=l故新的l=r,又因为r=r此时长度为一二分结束,r=mid时同理)故而我们不用考虑边界问题,mid的取值还是(l+r)/2即可
故而,当我们找橙色分界点时,更新状态为l=mid或r=mid-1(看满不满足性质条件判断更新条件来缩小范围),找中间值mid=(l+r+1)/2

我们找绿色分界点时,更新状态为l=mid+1或r=mid,找中间值mid=(l+r)/2

实数二分

实数二分由于实数有无限多个,所以二分会没完没了,所以一般我们会通过题目判断它的误差小于多少后就可以近似相同了,这时我们输出答案,因为我们随便两个实数中间有无限个,故而不存在边界问题,我们不需要考虑mid是应该等于(l+r)/2还是应该等于(l+r+1)/2直接通通看成(l+r)/2即可,不断二分,当区间范围很小时(看题目)我们输出答案就可以啦。

模板

通过上述分析,我们可以得到以下模板:

整数二分

bool check(int x) {/* ... */} // 检查x是否满足某种性质// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{while (l < r){int mid = l + r >> 1;if (check(mid)) r = mid;    // check()判断mid是否满足性质else l = mid + 1;}return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{while (l < r){int mid = l + r + 1 >> 1;if (check(mid)) l = mid;else r = mid - 1;}return l;
}

###实数二分

bool check(double x) {/* ... */} // 检查x是否满足某种性质double bsearch_3(double l, double r)
{const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求while (r - l > eps){double mid = (l + r) / 2;if (check(mid)) r = mid;else l = mid;}return l;
}

如何结束二分

当l不在小于r时停止二分:

while(l<r)
{//进行二分
}

停止时判断条件,如果他就是你要找的东西,那就可以,如果他不等于你要找的东西,那就说明这里面没有

例子

是骡子是马,拉出来溜溜,我们先来看几个题目加深影响

整数二分例子:数的范围

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回 -1 -1。

输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1。

数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
题意比较好理解,就是说有这么一段数是按从小到大排列的数,后再给你一些数,对于每个数让你寻找这个数在这个数组的起始点和终点

故而我们可以发现,就约等于我们既要找左边的边界又要找右边的边界

为什么可以二分

首先的一个问题是为什么可以二分,首先就拿我们的这个图来举例,对于我们要寻找的3,在他的左边满足一个性质:全都小于3(求左边界);对于他的右边也满足一个性质:全部大于3(求右边界),ok这样我们就有了二段性,而且我们的目标值恰巧就在二段性的边缘,故而可以二分
而且该数组是从小到大升序排列 有单调性的一定可以二分

找一下左边界

假设我们要找的那一段数为x,则左端点就是大于等于x的第一个位置,我们找到一个中点mid,判断条件就是:q[mid]是否>=x

判断更新条件

如果要是q[mid]>=x,则由于我们要找的是左端点,那么我们的左端点一定在mid的左端(可能会包含mid)故而更新条件就是:r=mid
知道了这个之后我们就可以由上方的模板得到else的更新方式:l=mid+1
由这个更新方式我们就可以知道我们的mid的更新方式:mid=(l+r+1)/2
不断二分,当最后我们的l不再小于r时

判断是否输出

当最后我们的l不再小于r的那一刻,我们判断(此时l与r相同)q[l]是否==x
如果等于,那它就是边界,否则我们直接输出-1 -1即可,不需要再进行右边界的判断了

找一下右边界

由于我们此时已经找完了左边界了,故而我们一开始的搜索范围就缩减为[二分的左边界,n-1],判断条件就是:q[mid]<=x

判断更新条件

由于我们有了判断左边界的经验,故而我们根本就不需要思考了,直接套用模板的后半段就可以,也就是l=mid或者r=mid-1,mid的更新条件是mid=(l+r)/2;

判断是否输出

由于我们的左半端有输出,故而我们的右半段也一定可以输出,不需要考虑

代码实现

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int n,m,mid;
int q[N];
int main()
{scanf("%d %d",&n,&m);for(int i=0;i<n;i++){scanf("%d",&q[i]);//输入}for(int i=0;i<m;i++){int x;scanf("%d",&x);//输入要找的数//判断左边界int l=0,r=n-1;while(l<r){mid=l+r>>1;if(q[mid]<x)l=mid+1;else r=mid;}if(q[l]==x){//判断最后二分后长度为一的那个点等不等于xprintf("%d ",l);//如果等于则判断右边界r=n-1;while(l<r){mid=l+r+1>>1;if(q[mid]>x)r=mid-1;else l=mid;}printf("%d\n",l);}else{//如果左边界判断时最后的值不是要求的x,那么x不存在,就不用再求右边界了,直接输出即可printf("-1 -1\n");}}return 0;
}

实数二分的例子(数的三次方根)

给定一个浮点数 n,求它的三次方根。

输入格式
共一行,包含一个浮点数 n。

输出格式
共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 位小数。

数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000

为什么可以二分

此题有查找的性质,在题目要求的误差之下(误差在6位小数),满足在我们所求数的左边的数三次方小于n,而右边的三次方大于n
且实数是从小到大排的 有单调性的一定可以二分

判断输出

当我们二分至左右边界l-r<=10的负八次方时输出最为保险(题目误差再乘以十的负二次方)

代码实现

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{double n;scanf("%lf",&n);double l=-10000,r=10000;while(r-l>1e-8){//看题目给的误差范围再乘以10的负二次方保险double mid=(l+r)/2;if(mid*mid*mid>n)r=mid;else l=mid;}printf("%lf",l);return 0;
}

总结

好了这就是我们的二分
总结一下:
1.确定一个区间,使得目标值必然在区间内
2.找到一个性质,使得具有二段型
3.分析终点M在该判断条件下是否成立考虑更新条件
4.若更新R=mid,则不用做任何处理,若是L=mid,则需要在计算mid时加一

希望对大家有所帮助!

算法之二分法(例子:数的范围,数的三次方根)相关推荐

  1. 【算法 | 实验8】分配最小页数(数组划分和最大值最小化问题)

    文章目录 题目 问题分析与算法设计思路 思路1:类似枚举的分治(暴力) 思路2:二分法 算法实现(C++) 思路1实现 思路2实现 bug记录 1.子问题对最大值没有实现最小化 2.保存的最大值是局部 ...

  2. java蓝桥杯算法训练 求1000以内的完数(题解)

    试题 算法训练 求1000以内的完数 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 如果一个数恰好等于它的所有因子(包括1但不包括自身)之和,则称之为"完数". ...

  3. 算法训练Day6:有效的字母异位词, 两个数组的交集,快乐数,两数之和

    文章目录 有效的字母异位词 自己题解 其他参考 两个数组的交集 题解(重点在熟悉set的一些操作以及使用场景) 快乐数 两数之和 题解(这道题还是比较难的) 有效的字母异位词 Category Dif ...

  4. 领扣LintCode算法问题答案-83. 落单的数 II

    领扣LintCode算法问题答案-83. 落单的数 II 目录 83. 落单的数 II 描述 样例 1: 样例 2: 题解 鸣谢 83. 落单的数 II 描述 给出 3 * n + 1 个非负整数,除 ...

  5. 我理解的算法 - 三数之和及两数、三数之和扩展题

    我理解的算法 - 三数之和及两数.三数之和扩展题 LeetCode 15.三数之和 扩展 三数之和变种题 两数之和变种题 LeetCode 15.三数之和 这道题的题目大家自行查看:链接在这 ,题目和 ...

  6. 代码随想录算法训练营第七天|454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

    今日学习的文章和视频链接 454文章链接: link 454视频讲解链接: link 383文章链接: link 383视频暂无讲解 15文章链接: link 15视频讲解链接: link 18文章链 ...

  7. 顺序栈完成十进制数转八进制数的算法

    补充完善下面的C语言代码,实现顺序栈的基本操作,然后借助所实现的顺序栈完成十进制数转八进制数的算法(请参考课本算法3.1),最后在主函数中测试该算法(测试用例:(1348)10=(2504))8. / ...

  8. Java实现算法提高十进制数转八进制数

    算法提高 十进制数转八进制数 时间限制:1.0s 内存限制:512.0MB 编写函数,其功能为把一个十进制数转换为其对应的八进制数.程序读入一个十进制数,调用该函数实现数制转换后,输出对应的八进制数. ...

  9. 领扣LintCode算法问题答案-82. 落单的数

    领扣LintCode算法问题答案-82. 落单的数 目录 82. 落单的数 鸣谢 82. 落单的数 给出 2 * n + 1个数字,除其中一个数字之外其他每个数字均出现两次,找到这个数字. 样例 1: ...

  10. 代码随想录算法训练营第七天|454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ● 18. 四数之和

    一.454.四数相加II 力扣 思路:第一眼还没反应过来,真是缺练.在四个数组中分别寻找,可以先把前两个数组的和先存入map中,再计算后两个数组元素的和,看一下相反数在map中出现没有,出现过就res ...

最新文章

  1. npm构建脚本_NPM脚本简介
  2. Centos 76分布式lamp平台
  3. 错误删除linux分区致Win7引导失败的修复方法
  4. php 不同时区时间转换,在PHP中将DateTime字符串转换为不同的时区
  5. pythonxml库_对python 生成拼接xml报文的示例详解
  6. idea解决activiti(*.bpmn)文件乱码问题。
  7. 剑指offer 面试题59 - II. 队列的最大值
  8. Linux集中日志服务器rsyslog(亲测)
  9. 1951-2021年至今全国气象数据(逐日、逐月、逐年)
  10. NetSpeedMonitor for mac
  11. DDS通信协议与安全实践
  12. 动易 转 html5,动易dedecms数据转成dedecms的php程序
  13. 10个宝藏级编程资源
  14. 基于Unity开发的鼠标打飞碟游戏设计
  15. vsCode常用插件
  16. Android Studio 手动创建活动(Activity) 第一行代码 第二章
  17. FM收音机入门,以及Python实现FM调制解调
  18. 华为无线AC 配置内置Portal认证和Radius服务器示例
  19. c++学习:多线程;顺序容器;智能指针
  20. 微软企业文化中的“工作激情”

热门文章

  1. yolo v5 NVIDIA Jetson Xavier NX 部署刷机+安环境(2)
  2. 微课程学习平台(微课平台)-特色功能(移动学习解决方案)
  3. Cytoskeleton——SiR-肌动蛋白相关工具推荐
  4. ecshop数据结构
  5. 计算机二级只有上机考试吗,计算机等级考试二级是上机考试吗
  6. 人工智能机器人——水中机器人
  7. 文字怎么转语音?这些方法值得收藏
  8. Python!Python!
  9. 支付宝服务商第三方代发布小程序
  10. jQuery手风琴菜单的制作