分治算法(二分查找)、STL函数库的应用第五弹——二分函数
分治算法:二分查找!昨天刚说不写算法了,但是突然想起来没写过分治算法的博客,所以强迫症的我……
STL函数库第五弹——二分函数lower_bound()、upper_bound()、binary_search()
由于笔者比较懒,所以把分治算法(二分查找篇)和STL第五弹放在一起。。。
Part 1:引入和导语
我们在做题的时候,经常会遇到一些需要分治的问题。(这是真的
今天的主角是——二分查找(开头提到过)。
二分查找,是针对于有序排列的数据调用而生的一种数据调用方法。
听上去很高端?我来讲个故事,大家肯定一下就会明白。
记得坐车的时候会收听电台,主持人有时会和观众玩这样一个游戏:
主持人在纸卡上写一个0-100的数字,要求听众在30秒内猜出这个数。
当听众说出一个数的时候,主持人会说出这个数相对于纸卡上的数是大了还是小了。
当然了,我相信主持人其实根本就没往纸卡上写数。
他也不会那么容易的让听众拿到奖品,所以每次猜一个数,他总会让听众尽可能少的排除错误答案。
比如你猜75,他会说“比75小”,这样你就只排除了25个错误答案。
那么怎么猜才是最聪明、最稳健的呢?
相信很多同学都已经想到了:猜50。这样就算主持人想阻挠拿到奖品,也可以一下子排除掉一半的错误答案。
那么猜完50呢?假如主持人说“比50大”,我们又该猜几呢?
对了——75,这样又能从剩下的里面排除一半错误答案。
——没错,这样每次缩小一半结果范围,不断缩小答案可能存在的区间,最终找到答案的方法,就叫做“二分法”。
如果我们使用二分法,在100个数中查找一个数,最坏的情况也只需要7次运算,而如果暴力枚举的话,可能就要几十次。
所以复杂度O(logn),而暴力的复杂度却是O(n)。
当然,使用二分法也有条件——数列必须有序,按照从小到大排序或者从大到小排序才可以。
(这里最好从小到大,笔者后面的代码也是按照从小到大来二分的)
Part 2:怎么写二分查找函数!
有一些加(丧)强(心)数(病)据(狂)的题目是这样的:
对于一个长度为n的不降序列,给出m组数据,请找出每个数在此序列中第一次出现的位置。
对于100%的数据,数据个数 n<=10^9;
如果暴力枚举的话,可爱的评测姬会非常不屑地回复你TLE三个字。(但是可以拿到部分分,唔,大概20~30分吧,不会太多的)
为了AC,我们就要用到二分,因为二分法的时间复杂度要优秀的多。
我们既然已经知道二分的原理了,剩下的就是怎么模拟二分了。
(鲁迅说过,算法来自生活实际,是模拟出来的。比如高精度算法,是模拟竖式……)
思路如下。
对于一个非常大的不降序列,注意必须是不降!!!我们有如下操作:
1、按照开头故事里的思路,我们要做出两个指针,分别指向这个序列的开头和末尾,表示二分到现在,答案是在这两个指针所指区间之内。
2、找出中间元素的值,然后比较它和所查找的那个数的值。如果比他大,那么排除比中间值小的数,把开头的指针(称为左指针)移动到中间值的位置;如果比他小,那么排除比中间值大的数,把末尾的指针(称为右指针)移动到中间值的位置。
3、重复步骤(2),直到找到该元素,返回元素下标。需要注意的是:如果左指针比右指针还要靠右(指向元素下标比右指针大),说明这个元素在此序列里不存在,这时,我们要退出循环二分查找。
具体代码如下,我已经竭尽一个蒟蒻所能的做了注释:
//这里指的左指针和右指针指向的是答案范围的最小值和最大值,初始时指向0和数组的最后一个元素
#include <cstdio>
#include <algorithm>
using namespace std;
int a[]= {,,,,,,,};//要查找的数组,二分前请确认不降序
int binarySearch(int x,int n)
{
int left =;
int right=n-;
while(left<=right)
{
int mid=(left+right)/;//int类型除法,不考虑小数(求中间的元素)
if(x==a[mid])
{
return mid;//如果这个中间值就是要找的元素,返回值
}
if(x>a[mid])//如果比他大,左指针指向中间值的下一个元素
{
left=mid+;
}
else//没有他大,右指针指向中间值的前一个元素
{
right=mid-;
}
}
return -;//未找到x
}
//二分递归实现
int recurisonBinarySearch(int left,int right,int x)
{
if(left>right)
{
return -;//左指针比右指针大,但是还没有找到答案,说明没有此元素,返回-1
}
int mid=(left+right)/;//求中间元素位置
if(x==a[mid])
{
return mid;//如果这个中间值就是要找的元素,返回值
}
if(x>a[mid])//比他大,中间值当做左指针,进行递归二分查找
{
return recurisonBinarySearch(mid+,right,x);
}
else//没他大,中间值当做右指针,进行递归二分查找
{
return recurisonBinarySearch(left,mid-,x);
}
}
int main()
{
int x;
int ans1,ans2;
scanf("%d",&x);//输入要查找的数
ans1=binarySearch(x,);//进行循环二分查找
ans2=recurisonBinarySearch(,,x);//进行递归二分查找
printf("%d %d\n",ans1,ans2);//输出元素在数组中的位置
return ;
}
这里还有一个要点,就是我这么写二分,只能对升序序列生效,如果序列中有多个相同的值,这个函数返回哪一个的下标我就不知道了。
如果题目中有要求,返回第一次出现的下标,我们需要手动在函数里加一个循环,找到该元素第一次出现的位置。
如果需要查找最接近某元素的值,二分到最后,确定该元素不在序列中,我们可以比较左指针所指元素的前一个或者后一个元素和左指针所指的元素,看看哪一个更接近即可。
这些只需要对函数做一些小小的修改,都很好写,笔者就不多BB了。
Part 3:STL二分函数
终于到了STL函数库出场的时间了!
伟大的#include<algorithm>!
为了方便我们这些蒟蒻编程和dalao们压缩代码,减少工作量,神犇们创造了这三个函数:
lower_bound()、upper_bound()、binary_search()
我们来一位一位介绍。
首先,binary_search()函数,作用:对一个不降序列进行二分查找,如果所查找的值在序列中出现了,返回true,没有出现,返回false。
然后,lower_bound()函数,作用:对一个不降序列进行二分查找,返回第一个大于等于所查找的值的元素下标,注意返回的是指针变量!!!
最后,upper_bound()函数,作用:对一个不降序列进行二分查找,返回第一个大于所查找的值的元素下标,注意返回的是指针变量!!!
这么方便的函数怎么用?
我们设置以下变量:
num[ ](需要被查找的序列);
lenth(序列长度);
x(需要查找的元素);
那么这几个函数使用的格式大概是这样的:
binary_search(num,num+lenth,x);
lower_bound(num,num+lenth,x);
upper_bound(num,num+lenth,x);
放到具体代码里是这样的:
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int a[]={,,,,,,,};
int b=binary_search(a,a+,);//查找成功,返回1
cout<<"在数组中查找元素4,结果为:"<<b<<endl;
int c=binary_search(a,a+,);//查找失败,返回0
cout<<"在数组中查找元素40,结果为:"<<c<<endl;
int d=lower_bound(a,a+,)-a;
cout<<"在数组中查找第一个大于等于10的元素位置,结果为:"<<d<<endl;
int e=lower_bound(a,a+,)-a;
cout<<"在数组中查找第一个大于等于100的元素位置,结果为:"<<e<<endl;
int f=upper_bound(a,a+,)-a;
cout<<"在数组中查找第一个大于10的元素位置,结果为:"<<f<<endl;
int g=upper_bound(a,a+,)-a;
cout<<"在数组中查找第一个大于101的元素位置,结果为:"<<g<<endl;
}
需要注意的是,因为这lower_bound()和upper_bound()这两个函数返回的值是指针变量,所以要把它变成int类型,要减去数组名。
ps:如果你所查找的数值比序列里最大的那个数还要大,lower_bound()和upper_bound()这两个函数返回的下标是lenth+1,注意这个东西有可能是越界的,所以不想RE的话就稍微处理一下吧!!!
Part 4:实战演练
由于笔者今天刚刚粗略的学了学STL大法,还不是很熟练,所以做题不多(只有一道)望谅解,有时间的话会专门发一篇二分查找的题解。
这个题,因为给出的序列已经是非降序列了,所以不需要先sort()来排序。
思路很简单——lower_bound()查找,需要注意的是比较的时候,不要越界。
先上加注释的代码,相信大家一看就懂。、
//1240
//#include<zhangtao.std>
#include<algorithm>//别忘了头文件
#include<cstdio>
using namespace std;
int n1,n2,a,b,num[];
int main()
{
scanf("%d",&n1);//n1为序列长度
for(int i=;i<n1;i++)
{
scanf("%d",&num[i]);//输入序列
}
scanf("%d",&n2);//输入询问次数
for(int i=;i<n2;i++)
{
scanf("%d",&a);//输入查询数据
b=lower_bound(num,num+n1,a)-num;//二分函数查找
if(b-<)//先特判,保证不要越界
{
//如果查找的数比数列中第一个数还要小,直接返回第一个元素即可
printf("%d\n",num[b]);
continue;
}
if(b>=n1)
{
//这里特判的是查找的数比数列中最大的数还要大的情况
//这时函数返回b的值为序列长度+1,我们返回序列的最后一个元素即可
printf("%d\n",num[b-]);
continue;
}
if(abs(num[b-]-a)<=num[b]-a)//题目要求如果一样接近,返回较小值,所以是<=
{
//元素num[b]>=a,num[b-1]<a判断哪个更接近一些,由于前面已经特判过越界的情况,可以放心比较
printf("%d\n",num[b-]);
continue;
}
else printf("%d\n",num[b]);
}
return ;//return 0好习惯
}
好了,今天的学习分享就到这里了,最后BB一句,能用STL的,我们绝对不要手写二分,不仅容易写错,而且费时间(蒟蒻思维)
记住这三个函数!!!lower_bound()、upper_bound()、binary_search(),非常方便,非常方便,非常方便!!!
老规矩——祝大家,身(少)体(掉)健(头)康(发)。
分治算法(二分查找)、STL函数库的应用第五弹——二分函数的更多相关文章
- C++STL标准库学习笔记(二)二分查找
二.STL中的二分查找算法 1.binary_search 2.lower_bound 3.upper_bound 记得#include<algorithm>! 前言: 在这个笔记中,我把 ...
- STL函数库的应用第四弹——全排列(+浅谈骗分策略)
因为基础算法快学完了,图论又太难(我太蒻了),想慢慢学. 所以暂时不写关于算法的博客了,但又因为更新博客的需要,会多写写关于STL的博客. (毕竟STL函数库还是很香的(手动滑稽)) 请出今天主角:S ...
- STL函数库的应用第三弹——数据结构(栈)
Part 1:栈是什么 栈(stack)又名堆栈,它是一种运算受限的线性表.限定仅在表尾进行插入和删除操作的线性表. 这一端被称为栈顶,相对地,把另一端称为栈底. 向一个栈插入新元素又称作进栈.入栈或 ...
- cURL函数库错误码说明之PHP curl_errno函数
背景概述:游戏接口是使用PHP cURL扩展进行请求操作.但是,被请求的服务器经常会无故的不响应或者超时.总之,就是请求之后收不到响应回来的数据.这时候可不能说对方API接口有问题,或者,服务器有故障 ...
- C++STL标准库学习笔记(五)set
前言: 在这个笔记中,我把大多数代码都加了注释,我的一些想法和注解用蓝色字体标记了出来,重点和需要关注的地方用红色字体标记了出来,这一篇后面主要都是我的记录了,为了防止大片蓝色字体出现,后面就不改蓝色 ...
- 重操JS旧业第五弹:函数
函数在任何编程语言中起着非常重要的位置,因为他是功能的最小单元,在js中函数是一种类型 Function 1 申明与定义 显示声明:function cc(){};函数名其实是函数的一个指针,函数名某 ...
- STL二分查找函数的应用
应用二分查找的条件必须是数组有序! 其中二分查找函数有三个binary_serch,upper_bound,lower_bound 测试数组 int n1[]={1,2,2,3,3,4,5}; int ...
- STL中的二分查找
本文转载于https://blog.csdn.net/riba2534/article/details/69240450 使用的时候注意:必须用在非递减的区间中 二分查找的原理非常简单,但写出的代码中 ...
- Java实现的二分查找算法
二分查找又称折半查找,它是一种效率较高的查找方法. 折半查找的算法思想是将数列按有序化(递增或递减)排列,查找过程中采用跳跃式方式查找,即先以有序数列的中点位置为比较对象,如果要找的元素值小 于该中点 ...
随机推荐
- Python虚拟环境(virtualenv)
python虚拟环境 虚拟环境:一个独立的可以运行的python执行环境,可以创建多个,且相互之间互不影响 使用virtualenv库 pip install virtualenv 用法 # 创建虚拟 ...
- 搞定 CompletableFuture,并发异步编程和编写串行程序还有什么区别?你们要的多图长文
你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...
- 脸书(Facebook)如何绑定谷歌二次验证码/谷歌身份验证/双重认证?
1.打开Facebook,找到双重验证界面 打开Facebook,点击“设置”-“安全与登陆”-“使用双重验证”-“身份验证应用”-“在其他设备上设置应用”-“输入验证码” *****想使用Fac ...
- 单例模式—Java实现
饿汉法 顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建.代码如下: 1 2 3 4 5 6 7 public class Singleton { priv ...
- 聊一聊Flutter的setState()
Flutter 里面包含两种widget 一种可变的,一种不可变的: 在可变的widget中可以使用 setstate(){} 函数. 官方也给出了例子: _onClick(){ setState() ...
- 推荐IT经理/产品经理,常用工具和网站
一. 常用必备工具 1)文档工具 石墨文档,在线协作文档工具 https://shimo.im/ 2) 表格工具 麦客,在线问卷调查工具 http://www.mikecrm.com/ 3)脑图工具 ...
- Redis服务之常用配置(一)
上一篇博客聊了下redis的简介以及redis的yum安装和源码编译安装需要注意到问题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13378138.html ...
- Day01_企业权限管理(SSM整合)
学于黑马程序员和传智播客联合做的教学项目 感谢 黑马程序员官网 传智播客官网 个人根据教程的每天的工作进度的代码和资料 密码:cti5 b站在线视频 微信搜索"艺术行者",关注并回 ...
- Django学习路19_is_delete属性,重写类方法,显性隐性属性
如果在 创建数据表时,使用了 objects = models.Model() 使隐形属性变为了 显性属性 则 必须要自己定义一个 继承了 models.Model 类的类,实现 管理功能 如果一个属 ...
- luogu P5667 拉格朗日插值2 拉格朗日插值 多项式多点求值 NTT
LINK:P5667 拉格朗日插值2 给出了n个连续的取值的自变量的点值 求 f(m+1),f(m+2),...f(m+n). 如果我们直接把f这个函数给插值出来就变成了了多项式多点求值 这个难度好像 ...