还不会二分查找?看这一篇就够了

news2024/11/17 15:57:10

目录

  • 一、整数二分
    • 1.1 二分查找模板
      • 1.1.1 寻找右边界的二分查找
      • 1.1.2 寻找左边界的二分查找
    • 1.2 应用:寻找元素的起始位置和终止位置
  • 二、浮点数二分
    • 2.1 浮点数二分模板
    • 2.2 应用:数的三次方根
  • 三、使用STL进行二分查找
    • 3.1 std::binary_search
    • 3.2 std::lower_bound
    • 3.3 std::upper_bound
    • 3.4 std::equal_range
  • References

一、整数二分

二分查找分为整数二分和浮点数二分,一般所说的二分查找都是指整数二分。

1.1 二分查找模板

满足单调性的数组一定可以使用二分查找,但可以使用二分查找的数组不一定需要满足单调性。

不妨假设我们找到了条件 C 1 C_1 C1,它和它的对立条件 C 2 C_2 C2 能够将数组 a a a 一分为二,如下图所示:

因为 C 1 C_1 C1 C 2 C_2 C2 互为对立,故总有 C 1 ∪ C 2 ≡ True ,    C 1 ∩ C 2 ≡ False C_1\cup C_2\equiv \text{True},\;C_1\cap C_2\equiv \text{False} C1C2True,C1C2False(用C++语言描述,就是 c1 || !c1 总是为真,c1 && !c1 总是为假)。换句话说, ∀   x ∈ a \forall \,x\in a xa x x x 至少满足 C 1 C_1 C1 C 2 C_2 C2 中的一个,且 x x x 不会同时满足 C 1 C_1 C1 C 2 C_2 C2

观察上图可以发现,索引 3 3 3 和索引 4 4 4 这两个位置都可以作为 C 1 C_1 C1 C 2 C_2 C2 的分界点。其中,索引 3 3 3 是红色区域的右边界,索引 4 4 4 是绿色区域的左边界。而我们接下来要讨论的二分查找模板就是用来寻找 C 1 C_1 C1 C 2 C_2 C2 的分界点的

1.1.1 寻找右边界的二分查找

前面说过, C 1 C_1 C1 C 2 C_2 C2 的分界点一共有两个,因此我们的整数二分查找模板也有两个。一个用来查找右边界(即左侧的分界点,对应索引 3 3 3),一个用来查找左边界(即右侧的分界点,对应索引 4 4 4)。这里首先介绍查找右边界的模板。

因为查找的是红色区域的右边界,所以先定义一个函数 check(i),其中参数 i i i 是索引。当 i i i 位于红色区域,即 0 ≤ i ≤ 3 0\leq i \leq 3 0i3 时,check(i) 为真;当 i i i 位于绿色区域,即 4 ≤ i ≤ 7 4\leq i \leq 7 4i7 时,check(i) 为假。

初始时设置左右两个指针分别位于数组的左右两端,每次循环时计算 m i d = l + r 2 mid=\frac{l+r}{2} mid=2l+r(至于 m i d mid mid 到底取多少稍后会说),然后判断 check(mid) 的值(为实现二分查找,我们需要确保每次缩小区间时答案都落在区间内。这样一来,当最终 l == r 时,l 就是我们需要的答案)。如果 check(mid) 为真,说明 m i d mid mid 位于红色区域,且 m i d mid mid 有可能就是右边界,因此接下来令 l = m i d l=mid l=mid 来缩小查找范围(因为我们要保证缩小后的区间仍然包含答案);如果 check(mid) 为假,说明 m i d mid mid 位于绿色区域,且 m i d mid mid 必不可能是红色区域的右边界,因为 m i d mid mid 最多是索引 4 4 4,因此令 r = m i d − 1 r=mid-1 r=mid1 来缩小查找范围。

接下来重点关注 m i d mid mid 到底该取多少。如果 m i d = l + r 2 mid=\frac{l+r}{2} mid=2l+r,其中的除法代表整除,在某一轮循环出现了 r − l = 1 r-l=1 rl=1,则 m i d = 2 l + 1 2 = l mid=\frac{2l+1}{2}=l mid=22l+1=l。若 check(mid) 为真,则更新后的区间仍然为 [ l , r ] [l,r] [l,r],这就会导致无限循环。事实上,只需要取 m i d = l + r + 1 2 mid=\frac{l+r+1}{2} mid=2l+r+1,若 check(mid) 为真,则 m i d = r mid=r mid=r,更新后的区间为 [ r , r ] [r,r] [r,r],循环结束。若 check(mid) 为假,则更新后的区间为 [ l , l ] [l,l] [l,l],循环结束。

寻找右边界的二分查找模板:

int right_bound(int l, int r) {
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

1.1.2 寻找左边界的二分查找

类似1.1.1节中的分析,因为查找的是绿色区域的左边界,所以先定义一个函数 check(i),其中参数 i i i 是索引。当 i i i 位于绿色区域,即 4 ≤ i ≤ 7 4\leq i \leq 7 4i7 时,check(i) 为真;当 i i i 位于红色区域,即 0 ≤ i ≤ 3 0\leq i \leq 3 0i3 时,check(i) 为假。

初始时设置左右两个指针分别位于数组的左右两端,每次循环时计算 m i d = l + r 2 mid=\frac{l+r}{2} mid=2l+r(至于 m i d mid mid 到底取多少稍后会说),然后判断 check(mid) 的值。如果 check(mid) 为真,说明 m i d mid mid 位于绿色区域,且 m i d mid mid 有可能就是左边界,因此接下来令 r = m i d r=mid r=mid 来缩小查找范围;如果 check(mid) 为假,说明 m i d mid mid 位于红色区域,且 m i d mid mid 必不可能是绿色区域的左边界,因为 m i d mid mid 最多是索引 3 3 3,因此令 l = m i d + 1 l=mid+1 l=mid+1 来缩小查找范围。

接下来重点关注 m i d mid mid 到底该取多少。如果 m i d = l + r 2 mid=\frac{l+r}{2} mid=2l+r,其中的除法代表整除,在某一轮循环出现了 r − l = 1 r-l=1 rl=1,则 m i d = 2 l + 1 2 = l mid=\frac{2l+1}{2}=l mid=22l+1=l。若 check(mid) 为真,则更新后的区间为 [ l , l ] [l,l] [l,l],循环结束。若 check(mid) 为假,则更新后的区间为 [ r , r ] [r,r] [r,r],循环结束。综上所述, m i d mid mid l + r 2 \frac{l+r}{2} 2l+r 即可。

寻找左边界的二分查找模板:

int left_bound(int l, int r) {
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

1.2 应用:寻找元素的起始位置和终止位置

原题链接:AcWing 789. 数的范围

a = [1, 3, 3, 3, 4] 为例,数字 3 3 3 的起始位置和终止位置分别是 1 1 1 3 3 3(索引)。如何利用1.1节中的模板来完成此题呢?

现对数组 a a a 作出划分,若令 C 1 : a [ i ] < x ,    C 2 : a [ i ] ≥ x C_1:a[i]<x,\; C_2:a[i]\geq x C1:a[i]<x,C2:a[i]x(注意必须保证 C 1 , C 2 C_1,C_2 C1,C2 互为对立),则求 x x x 的起始位置便转化成了求 C 2 C_2 C2 区域的左边界。若令 C 1 : a [ i ] ≤ x ,    C 2 : a [ i ] > x C_1:a[i]\leq x,\; C_2:a[i]> x C1:a[i]x,C2:a[i]>x,则求 x x x 的终止位置便转化成了求 C 1 C_1 C1 区域的右边界。

在求 C 2 C_2 C2 区域的左边界时,check(mid) 即为 a[mid] >= x。在求 C 1 C_1 C1 区域的右边界时,check(mid) 即为 a[mid] <= x

AC代码如下:

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int a[N];

int left_bound(int l, int r, int x) {
    while (l < r) {
        int mid = l + r >> 1;
        if (a[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

int right_bound(int l, int r, int x) {
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (a[mid] <= x) l = mid;
        else r = mid - 1;
    }
    return l;
}

int main() {
    int n, q;
    cin >> n >> q;
    for (int i = 0; i < n; i++) cin >> a[i];

    while (q--) {
        int k;
        cin >> k;
        int begin = left_bound(0, n - 1, k);
        if (a[begin] != k) cout << "-1 -1" << endl;
        else cout << begin << " " << right_bound(0, n - 1, k) << endl;
    }

    return 0;
}

二、浮点数二分

2.1 浮点数二分模板

相比整数二分,浮点数二分就要简单许多了,因为浮点数二分不涉及到边界问题。

浮点数二分通常用来求某个数 x x x 的近似值 x x x 不易直接求得,例如 x = 2 x=\sqrt{2} x=2 等)。由于此时左右两个指针也均为浮点数,所以我们不能直接判断 l == r,而是判断 r - l 是否小于预先设定的精度。

模板如下:

double fbsearch(double l, double r, double eps) {
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

一些注意事项:

  • 若要求精确到小数点后 k k k 位,则 e p s eps eps 1 0 − ( k + 2 ) 10^{-(k+2)} 10(k+2);
  • m i d ≥ x mid\geq x midx,则 check(mid) 为真,否则为假。

2.2 应用:数的三次方根

原题链接:AcWing 790. 数的三次方根

注意到当 ∣ x ∣ < 1 |x|<1 x<1 时,有 ∣ x 1 / 3 ∣ > ∣ x ∣ |x^{1/3}|>|x| x1/3>x,故选取 l , r l,r l,r 时一定要谨慎。

因为 1000 0 1 / 3 < 22 10000^{1/3}<22 100001/3<22,所以这里取 l = − 22 ,   r = 22 l=-22,\,r=22 l=22,r=22 即可。

#include <iostream>

using namespace std;

double x;

double fbsearch(double l, double r, double eps) {
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (mid * mid * mid >= x) r = mid;
        else l = mid;
    }
    return l;
}

int main() {
    cin >> x;
    printf("%lf", fbsearch(-22, 22, 1e-8));
    return 0;
}

三、使用STL进行二分查找

3.1 std::binary_search

template< class ForwardIt, class T >
bool binary_search( ForwardIt first, ForwardIt last, const T& value );

该函数用来检查 [first, last) 区间内(该区间已排序)是否有数字等于 value,如果有返回 true,否则返回 false

3.2 std::lower_bound

template< class ForwardIt, class T >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );

该函数用来返回 [first, last) 区间内(该区间已排序)首个大于等于 value 的元素的迭代器,如果找不到这种元素则返回 last

3.3 std::upper_bound

template< class ForwardIt, class T >
ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value );

该函数用来返回 [first, last) 区间内(该区间已排序)首个大于 value 的元素的迭代器,如果找不到这种元素则返回 last

3.4 std::equal_range

template< class ForwardIt, class T >
std::pair<ForwardIt, ForwardIt>
    equal_range( ForwardIt first, ForwardIt last, const T& value );

该函数用来返回 [first, last) 区间内(该区间已排序)所有等于 value 的元素的「范围」。

「范围」实际上是由两个迭代器构成的 pairpair 中的第一个元素是 std::lower_bound 的返回值,pair 中的第二个元素是 std::upper_bound 的返回值。


接下来我们用STL来简化1.2节中的代码。

注意到对于数组而言,其迭代器就是指针,因此我们可以通过将返回的迭代器与数组名作差来得到迭代器所指向的元素的下标。

简化后的代码如下:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

int a[N];

int main() {
    int n, q;
    cin >> n >> q;
    for (int i = 0; i < n; i++) cin >> a[i];

    while (q--) {
        int k;
        cin >> k;
        auto p = equal_range(a, a + n, k);
        if (*p.first != k) cout << "-1 -1" << endl;
        else cout << p.first - a << " " << p.second - a - 1 << endl;
    }

    return 0;
}

References

[1] https://www.acwing.com/activity/content/punch_the_clock/11/
[2] https://www.acwing.com/blog/content/31/
[3] https://zh.cppreference.com/w/cpp/algorithm/lower_bound

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

27-Golang中的错误处理机制

Golang中的错误处理机制说明基本说明使用deferrecover处理上述代码错误处理机制的好处自定义错误说明 package main import ("fmt" )func test() {num1 : 10num2 : 0res : num1 / num2fmt.Println("res", res) }func fmt() {test ()fmt.Println("mai…

分享77个Java源码,总有一款适合您

Java源码 分享77个Java源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c;大家下载后可以看到。 源码下载链接&#xff1a;https://pan.baidu.com/s/1EhB4snvWia5eCztyfiX_2A?pwdbofo …

wish、亚马逊、ebay、沃尔玛自养号测评补单优势之分析技巧

测评补单的资源可以说是卖家非常宝贵的财富&#xff0c;通过测评补单和广告相结合&#xff0c;可以快速有效的提升店铺的产品销量&#xff0c;提高转化&#xff0c;提升listing权重&#xff0c;如果是做自养号还可以不用担心以后被别人牵制&#xff0c;毕竟资源掌握在自己手上&…

docker-基础实战第二课

Docker三剑客 Docker 镜像&#xff08;Image&#xff09;&#xff1a;镜像可以用来创建容器&#xff0c;一个镜像可以创建多个容器。 容器&#xff1a;容器实例就是指实际运行的实例&#xff0c;可以理解为镜像是java的类&#xff0c;容器就是new出来的对象。 仓库&#xff1a;…

elasticsearch实现基于拼音搜索

1、背景 一般情况下&#xff0c;有些搜索需求是需要根据拼音和中文来搜索的&#xff0c;那么在elasticsearch中是如何来实现基于拼音来搜索的呢&#xff1f;可以通过elasticsearch-analysis-pinyin分析器来实现。 2、安装拼音分词器 # 进入 es 的插件目录 cd /usr/local/es/…

【论文阅读 CIKM2011】Finding Dimensions for Queries

文章目录ForewordAbsMethodList ExtractionList WeightingList ClusteringDimension and Item RankingForeword This paper is from CIKM2011, so we only condier the method, not resultsThere are many papers that have not been shared. More papers can be found in: Sh…

微信小程序+阿里物联/Onenet物联+esp32搭建无服务器物联系统(一)---ESP32硬件设计开源

目录 简介 一、硬件设计开源连接 二、硬件设计解析 1、电路原理图 2、PCB版图 3、BOM资料 4、整体项目的资料连接 微信小程序阿里物联平台合宙Air724UG搭建无服务器物联系统&#xff08;一&#xff09; 微信小程序阿里物联平台合宙Air724UG搭建无服务器物联系统&#xff08…

uni-app canvas绘制海报流程的一些记录

绘制流程 布局定义 在我们的布局里要声明canvas的定义如下&#xff0c;可以声明class布局样式&#xff0c;width和height是必须的&#xff0c;因为如果没有就绘制不了。 canvas-id也是必须的&#xff0c;我们需要通过id找到对一个你的canvas对象&#xff0c;来做操作 <c…

公测开启!CRMEB 多商户v2.1新功能快来体验

“ 真正实现完美主义当然很困难&#xff0c;但具备追求完美主义的态度&#xff0c;就能减少错误。” ——稻盛和夫 CRMEB 多商户 v2.1公测版正式发布&#xff01;新增付费会员&#xff0c;可进行会员折扣价管理、会员福利设置&#xff1b;新增社区发布短视频&#xff0c;打造短…

Django REST framework--DRF视图

Django REST framework--DRF视图DRF视图编写常规Django视图编写视图装饰器api_view查询资源返回所有数据返回单条数据返回json格式的数据新增资源修改资源删除资源DRF视图编写 常规Django视图编写 序列化器最终的作用是为视图提供转化后的数据&#xff0c;可使用Serializer类…

【饥荒】本地服务器+内网穿透

本地服务器搭建方法 准备工具 网易UU加速器&#xff08;用于加速科雷官网登陆和steam创意工坊&#xff09;Don’t Starve Together Dedicated Server(steam饥荒联机版开服工具) 获取方法&#xff1a;在库中勾选工具&#xff0c;然后搜索Don’t Starve Together Dedicated Ser…

Unity SRP自定义渲染管线学习1.2:初步绘制

绘制物体 绘制物体&#xff0c;包括不透明的物体&#xff0c;透明物体&#xff0c;再加上之前的天空盒 Camera void DrawVisibleGeometry(){//我们需要将不透明物体和透明物体分开绘制//如果我们直接先绘制所有的物体&#xff0c;然后再绘制天空盒&#xff0c;我们就会看到对…

nodejs+vue家庭食谱饮食方案管理系统网站python php java

本系统分为用户和管理员两个角色&#xff0c;其中用户可以注册登陆系统&#xff0c;查看美食食谱&#xff0c;一周健康食谱安排&#xff0c;查看美食食材&#xff0c;在线交流发布帖子&#xff1b;管理员可以对食谱分类&#xff0c;食谱信息&#xff0c;材料信息&#xff0c;美…

区块链技术2---BTC的数据结构

1&#xff1a;Hash pointers&#xff08;哈希指针&#xff09;和普通指针相比&#xff0c;哈希指针除了保存地址还保存哈希值2&#xff1a;Block chain区块链中的区块通过哈希指针相连&#xff0c;这里的哈希指针的哈希值是对前一个区块的整体取哈希值&#xff08;包括前一个区…

linux系统中使用QT实现摄像头功能的方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何使用QT中的Camera的功能和实现。 目录 第一&#xff1a;摄像头资源简介 第二&#xff1a;环境搭建要求 第三&#xff1a;代码编译实现要求 第一&#xff1a;摄像头资源简介 开发板上有一路“CSI”摄像头接口&#xf…

组件封装 - steps组件

首先, 我先来看看效果 steps 组件的封装和 tabs 组件还是相似的 都会去指定两个组件来完成(仿Element UI), 都会去使用 jsx 的语法 让其中一个组件去规定样式和排版, 另外一个组件去接收父组件传入的动态数据 但和面包屑组件还是有区别的(面包屑组件封装): 相同点都是使用两…

v-for 的“就地更新”策略

前言 我们平时使用v-for的时候通常都是加一个唯一标识key&#xff0c;因为不加的时候Vue会给我们发出警告。其实我们加上key的操作&#xff0c;就是为了避免它的“就地更新”策略。我们来看一下官网对“就地更新”的解释&#xff1a; 当 Vue 正在更新使用 v-for 渲染的元素列表…

jsp文化活动系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 文化活动系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统采用web模式开发&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数…

第4章 任务看门狗

任务看门狗 主任务死循环 在app_main任务中死循环 #include <stdio.h> #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h"const char *TAG "COUNTER";int count 0;void app_main(void) {wh…

[翻译]PG15新特性-加速WAL日志归档

PG15新特性-加速WAL日志归档PG15通过&#xff1a;一次扫描64个待归档的日志&#xff0c;将其放到一个数组中以供归档&#xff0c;当处理完这64个文件后&#xff0c;再进行下一次扫描。这样达到减少archive_status目录扫描次数提升性能的目的。WAL归档介绍PG15如何加速归档前&am…