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

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. Alink漫谈(十四) :多层感知机 之 总体架构

    Alink漫谈(十四) :多层感知机 之 总体架构 目录 Alink漫谈(十四) :多层感知机 之 总体架构 0x00 摘要 0x01 背景概念 1.1 前馈神经网络 1.2 反向传播 1.3 代价函 ...

  2. Letex中表格问题

    最近在学习使用Letex,在学习过程中碰到很多小问题,故记之. 以下是一个参数表的实例(绘成三线表的形式). \begin{table}[hp] %%参数: h:放在此处 t:放在顶端 b:放在底端 ...

  3. 第十二章 类加载器&反射

    12.1.类加载器 12.1.1.类加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载.类的连接.类的初始化这三个步骤来对类进行初始化.如果不出现意外情况,JVM将会连续完成 ...

  4. SQL Server 枚举异或运算后值存入数据库,读取符合条件的值

    有枚举如下: [Flags] public enum Color { Red = , Green = , Blue = , White = } 定义三个枚举变量,并将值存入数据库: Color col ...

  5. PHP mkdir() 函数

    定义和用法 mkdir() 函数创建目录. 如果成功该函数返回 TRUE,如果失败则返回 FALSE. 语法 mkdir(path,mode,recursive,context) 参数 描述 path ...

  6. Android中Activity启动模式探索

    Android中启动模式(launchMode)分为standard, singleTop, singleTask, singleInstance四种,可通过AndroidManifest.xml文件 ...

  7. 我靠!Semaphore里面居然有这么一个大坑!

    这是why的第 59 篇原创文章 荒腔走板 大家好,我是why哥 ,欢迎来到我连续周更优质原创文章的第 59 篇. 上周写了一篇文章,一不小心戳到了大家的爽点,其中一个转载我文章的大号,阅读量居然突破 ...

  8. 银弹谷零代码开发V百科|使用技巧:你已经是个成熟的系统了,该学会无网络升级了

    银弹谷零代码开发V百科|使用技巧:你已经是个成熟的系统了,该学会无网络升级了 伴随网络时代的发展,当今越来越多用户家庭的日常生活已经离不开网络.它就像是一张巨大的蛛网,连接起我们每一户人家.虽然网络不 ...

  9. SAFe必备——提高团队敏捷性

    规模化敏捷之于项目群,就像Scrum之于敏捷团队.为了创建高质量业务解决方案,企业需要提高自身能力,提升团队和技术敏捷性,实现真正的规模化敏捷. 敏捷发布火车 实现团队和技术敏捷性,首先需要敏捷团队围 ...

  10. tp3.2 新增邮件类

    1.新建方法   调用发送邮件,我的目录在/admin下 2.新增邮件方法 类的发送配置功能 文件地址: 网站根目录\项目目录\Admin\Common\ 文件 名   :function.php   ...