分治算法:二分查找!昨天刚说不写算法了,但是突然想起来没写过分治算法的博客,所以强迫症的我……

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函数库的应用第五弹——二分函数的更多相关文章

  1. C++STL标准库学习笔记(二)二分查找

    二.STL中的二分查找算法 1.binary_search 2.lower_bound 3.upper_bound 记得#include<algorithm>! 前言: 在这个笔记中,我把 ...

  2. STL函数库的应用第四弹——全排列(+浅谈骗分策略)

    因为基础算法快学完了,图论又太难(我太蒻了),想慢慢学. 所以暂时不写关于算法的博客了,但又因为更新博客的需要,会多写写关于STL的博客. (毕竟STL函数库还是很香的(手动滑稽)) 请出今天主角:S ...

  3. STL函数库的应用第三弹——数据结构(栈)

    Part 1:栈是什么 栈(stack)又名堆栈,它是一种运算受限的线性表.限定仅在表尾进行插入和删除操作的线性表. 这一端被称为栈顶,相对地,把另一端称为栈底. 向一个栈插入新元素又称作进栈.入栈或 ...

  4. cURL函数库错误码说明之PHP curl_errno函数

    背景概述:游戏接口是使用PHP cURL扩展进行请求操作.但是,被请求的服务器经常会无故的不响应或者超时.总之,就是请求之后收不到响应回来的数据.这时候可不能说对方API接口有问题,或者,服务器有故障 ...

  5. C++STL标准库学习笔记(五)set

    前言: 在这个笔记中,我把大多数代码都加了注释,我的一些想法和注解用蓝色字体标记了出来,重点和需要关注的地方用红色字体标记了出来,这一篇后面主要都是我的记录了,为了防止大片蓝色字体出现,后面就不改蓝色 ...

  6. 重操JS旧业第五弹:函数

    函数在任何编程语言中起着非常重要的位置,因为他是功能的最小单元,在js中函数是一种类型 Function 1 申明与定义 显示声明:function cc(){};函数名其实是函数的一个指针,函数名某 ...

  7. STL二分查找函数的应用

    应用二分查找的条件必须是数组有序! 其中二分查找函数有三个binary_serch,upper_bound,lower_bound 测试数组 int n1[]={1,2,2,3,3,4,5}; int ...

  8. STL中的二分查找

    本文转载于https://blog.csdn.net/riba2534/article/details/69240450 使用的时候注意:必须用在非递减的区间中 二分查找的原理非常简单,但写出的代码中 ...

  9. Java实现的二分查找算法

    二分查找又称折半查找,它是一种效率较高的查找方法. 折半查找的算法思想是将数列按有序化(递增或递减)排列,查找过程中采用跳跃式方式查找,即先以有序数列的中点位置为比较对象,如果要找的元素值小 于该中点 ...

随机推荐

  1. JELLY技术周刊 Vol.15 云游戏会是 5G 杀手级应用么?

    蒲公英 · JELLY技术周刊 Vol.15 听到"云游戏",或许我们的第一反应会是"云玩家"而不是那些上云的"游戏",在这个 5G 已来的 ...

  2. 写verilog程序需要注意的地方

    1.在always块语句中一定要注意if-else if-else if-else的判断条件的顺序. 2.同一个寄存器信号只能在同一个always or initial 块中进行赋值. 3.在控制一个 ...

  3. 【题解】cf1381c Mastermind

    序 (一道很考验思维质量的构造好题,而且需要注意的细节也很多.) 本题解主体使用的是简洁且小常数的\(O(nlogn)\)时间复杂度代码,并且包含其他方法的分析留给读者自行实现(其实是自己不会写或者写 ...

  4. Spring的SchedulingConfigurer实现定时任务

    前提:在做业务平台的时候我们经常会遇到,某些跟时间打交道的需要修改状态,比如说在时间区间之前,属于未生效状态,区间之内属于有效期,区间之后,属于过期,或者需要每天 每周 每月,甚至是年为单位的做一些固 ...

  5. Python os.lseek() 方法

    概述 os.lseek() 方法用于设置文件描述符 fd 当前位置为 pos, how 方式修改.高佣联盟 www.cgewang.com 在Unix,Windows中有效. 语法 lseek()方法 ...

  6. PHP is_dir() 函数

    定义和用法 is_dir() 函数检查指定的文件是否是一个目录. 如果目录存在,该函数返回 TRUE. 语法 is_dir(file) 参数 描述 file 必需.规定要检查的文件. 提示和注释 注释 ...

  7. 7.12 NOI模拟赛 积性函数求和 数论基础变换 莫比乌斯反演

    神题! 一眼powerful number 复习了一下+推半天. 可以发现G函数只能为\(\sum_{d}[d|x]d\) 不断的推 可以发现最后需要求很多块G函数的前缀和 发现只有\(\sqrt(n ...

  8. 海华大赛第一名团队聊比赛经验和心得:AI在垃圾分类中的应用

    摘要:为了探究垃圾的智能分类等问题,由中关村海华信息研究院.清华大学交叉信息研究院以及Biendata举办的2020海华AI垃圾分类大赛吸引了大量工程师以及高校学生的参与 01赛题介绍 随着我国经济的 ...

  9. Java线程知识:二、锁的简单使用

    锁的初步认识 说到锁,相信大家都不陌生,这是我们生活中非常常见的一种东西,它的形状也各式各样.在生活中,我们通常用锁来锁住房子的大门.装宠物的笼子.装衣服的衣柜.以及装着我们一些小秘密的小抽屉.... ...

  10. 70道Spring面试题

    1. 什么是spring? Spring 是个java企业级应用的开源开发框架.Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用.Spring 框架目标是简化Jav ...