vector 查找_怎么写出无bug的二分查找算法代码

2023-11-17

封面图来自 geeksforgeeks

1 简介

二分查找算法是一类比较基础的算法。然而想要短时间内,写出二分查找的无 bug 版本,也不是很容易的。为此我查找了一些资料,终于弄清了二分查找算法的套路,在此分享给大家,也算是对自己学习知识的一个总结。

2 算法介绍

还是来简单介绍一下二分查找算法,了解算法的目的、思路、套路,才能让我们更好地写出对应的代码。

2.1 算法目的

顾名思义,这是一类查找算法,目的是在一个容器(比如 vector)中查找某个符合要求的元素。在我看来,下面三种情景能解决大部分的二分查找问题(以 vector 为例):

  1. 假设 vector 中没有重复元素,查找 vector 中是否存在某个元素。如果存在,返回它的索引,否则返回 -1。
  2. 假设 vector 中存在重复元素,查找 vector 中第一个符合某条件的元素。如果存在,返回它的索引,否则返回 -1。
  3. 假设 vector 中存在重复元素,查找 vector 中最后一个符合某条件的元素。如果存在,返回它的索引,否则返回 -1。

2.2 算法思路

顾名思义,算法是使用“二分”的方法来进行查找的,这里的“二分”,其实是“减而治之”的意思。怎么理解这里的“减而治之”呢,我自己是这样理解的:每次我们将 vector 分成两半,根据分界点的情况,决定我们往哪一边进行查找,直到最终找到符合条件的元素。这里有几个注意点:

  1. 首先要求 vector 中的元素是有序的。因为如果元素无序,那么我们没有办法根据分界点的情况来判断可行解的范围,换句话说,它不能提供给我们额外的,关于 vector 前半部分或者后半部分的信息。
  2. 在每一次判断分界点的情况后,我们都需要缩小可行解的范围,不然的话,算法就可能陷入死循环跳不出来。
  3. 我们需要保证,在每次缩小范围时,不会影响解的情况。换句话说,每次我们排除的,都是非可行解的那一部分。
  4. 我们需要仔细思考边界情况。上面都是在说,我们怎么缩小搜索范围,那么当搜索范围足够小之后(比如搜索范围为空,或者搜索范围里只有一个元素),我们需要返回什么。

上面说的几点保证了算法的正确性。而我们写代码时,经常出错的地方,就是没有处理好上面这几点,导致数组越界,或者陷入死循环等。

3 算法实现

OK,在知道算法的作用原理之后,其实写代码也不难了,只需要对上面说的几点加以注意就好了。

3.1 情况一

假设 vector 中没有重复元素,查找 vector 中是否存在某个元素。如果存在,返回它的索引,否则返回 -1。

int binary_search(vector<int>& nums, int target) {
    int lo = 0, hi = nums.size() - 1; // 在闭区间 [lo, hi] 中查找
    // 退出查找的条件是:查找范围为空
    while (lo <= hi) {
        int mi = lo + (hi - lo) / 2;
        if (nums[mi] < target) {
            lo = mi + 1; // mi 不是可行解,范围变成 [mi+1, hi]
        } else if (nums[mi] > target) {
            hi = mi - 1; // mi 不是可行解,范围变成 [lo, mi-1]
        } else {
            return mi;
        }
    }
    return -1;
}

看代码中的 if 判断语句,三个分支中,要么是退出循环,要么是缩小了范围。因此,查找范围会不断减小,直到为空,对应了 while 语句中的判断。

3.2 情况二

假设 vector 中存在重复元素,查找 vector 中第一个符合某条件的元素。如果存在,返回它的索引,否则返回 -1。

我们将这种情况换成一个更具体的例子:假设 vector 是一个 0 和 1 组成有序序列,前面都是0,后面都是1,求第一个 1 元素的索引。我们把这种情况称为:0011 模式下查找第一个1。

int binary_search(vector<int>& nums) {
    int lo = 0, hi = nums.size() - 1; // 在闭区间 [lo, hi] 中查找
    // 退出查找的条件是:查找范围为1个元素
    while (lo < hi) {
        int mi = lo + (hi - lo) / 2;
        if (nums[mi] == 1) {
            hi = mi; // mi 在可行解区间中,可行解区间范围缩小为 [lo, mi]
        } else {
            lo = mi + 1; // mi 不在可行解区间中,可行解区间范围缩小为 [mi+1, hi]
        }
    }
    return lo;
}


当 nums[mi] == 1 时,mi 可能是最终的解,因此需要保留,范围缩小为 [lo, mi];当 nums[mi] != 1 时,mi 不可能是最终的解,因此不需保留,范围缩小为 [mi+1, hi]。

3.3 情况三

假设 vector 中存在重复元素,查找 vector 中最后一个符合某条件的元素。如果存在,返回它的索引,否则返回 -1。

我们将这种情况换成一个更具体的例子:假设 vector 是一个 0 和 1 组成有序序列,前面都是1,后面都是0,求最后一个 1 元素的索引。我们把这种情况称为:1100 模式下查找最后一个1。

int binary_search(vector<int>& nums) {
    int lo = 0, hi = nums.size() - 1; // 在闭区间 [lo, hi] 中查找
    // 退出查找的条件是:查找范围为1个元素
    while (lo < hi) {
        int mi = lo + (hi - lo + 1) / 2; // +1 保证可行解区间范围缩小
        if (nums[mi] == 1) {
            lo = mi; // mi 在可行解区间中,可行解区间范围缩小为 [mi, hi]
        } else {
            hi = mi - 1; // mi 不在可行解区间中,可行解区间范围缩小为 [lo, mi-1]
        }
    }
    return lo;
}

当 nums[mi] == 1 时,mi 可能是最终的解,因此需要保留,范围缩小为 [mi, hi];当 nums[mi] != 1 时,mi 不可能是最终的解,因此不需保留,范围缩小为 [lo, mi-1]。需要注意的是,在这种情况下,mi 的取值是 lo +(hi - lo +1)/2,在这里 +1 的目的是保证可行解区间范围缩小(想象 lo = hi-1 的情况)。

4 实战

上面这三种情况足以应对绝大部分的二分查找的情景,只需要确定原始序列中的 “0” 和 “1” 分别表示什么含义,这就是二分查找算法的套路。

我们找 leetcode 上几道典型的二分查找的题目练练手看看。

4.1 Find First and Last Position of Element in Sorted Array

Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value.
Your algorithm's runtime complexity must be in the order of O(log n).
If the target is not found in the array, return [-1, -1].

题目让我们在数组中查找某个元素出现的第一个位置以及最后一个位置。在这里

  • 第一个位置就对应了算法实现中的“情况二”,在这个情况下,我们要查找第一个 “1” ,这里的 “1” 表示 “大于等于 target”。
  • 最后一个位置就对应了算法实现中的“情况三”,在这个情况下,我们要查找最后一个 “1” ,这里的 “1” 表示 “小于等于 target”。

于是直接套上面的模版就可以轻松写出代码。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.size() == 0) return {-1, -1};
        int lo = 0, hi = nums.size() - 1;
        int left, right;
        
        // 0011 模式中第一个 1 其中 1 表示 “>= target”
        while (lo < hi) {
            int mi = lo + (hi - lo) / 2;
            if (nums[mi] >= target) hi = mi;
            else lo = mi + 1;
        }
        left = (nums[lo] == target) ? lo : -1;
        
        // 1100 模式最后一个 1,其中 1 表示 “<= target”
        lo = 0, hi = nums.size() - 1;
        while (lo < hi) {
            int mi = lo + (hi - lo + 1) / 2;
            if (nums[mi] <= target) lo = mi;
            else hi = mi - 1;
        }
        right = (nums[lo] == target) ? lo : -1;
        
        return {left, right};
    }
};

4.2 https://leetcode.com/problems/search-insert-position/

Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
You may assume no duplicates in the array.
  • 这到题对应了算法实现中的“情况二”,查找第一个大于等于目标值的位置。
  • 注意特殊情况,当目标值比数组中所有数都大时,需要特殊处理。
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        if (nums.back() < target) return nums.size(); // 特殊情况
        // 0011 模式下的第一个 1,1 表示大于等于 target
        int lo = 0, hi = nums.size() - 1;
        while (lo < hi) {
            int mi = lo + (hi - lo) / 2;
            if (nums[mi] >= target) {
                hi = mi;
            } else {
                lo = mi + 1;
            }
        }
        return lo;
    }
};

5 总结

总结一下,二分查找算法是一种“减而治之”的查找算法。

算法正确性的保证:序列有序、每次搜索范围都会减小、每次减小搜索范围不影响解的情况。

三种情景下对应的算法:无重复元素下的查找、0011 模式下查找第一个1、1100 模式下查找最后一个1。

大部分二分查找都可以转化为上面的三种模式,于是可以比较方便地写出代码。

6 参考资料

在写作的过程中,参考了以下一些资料,在此表示感谢

漫谈二分查找-Binary Search http:// duanple.blog.163.com/bl og/static/709717672009049528185/
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vector 查找_怎么写出无bug的二分查找算法代码 的相关文章

  • 手写体数字识别例程——LeNet-5模型

    上一篇博客中介绍了Caffe环境的搭建 本片博客中介绍一下 在caffe中训练的第一个CNN模型LeNet 5 如果存在不正确的地方欢迎指正 该例程用的数据集是MNIST 该数据集中包含60000个训练集和10000个测试集 使用的CNN模
  • 网络基础——传输层中的TCP,UDP和Wireshark抓包过程详解

    传输层 传输层向上面的应用层提供通信服务 属于面向通信部分的最高层 也是用户功能中的最底层 传输层为相互通信的应用进程提供了逻辑通信 主要包括两个协议 TCP协议和UDP协议 传输层的主要作用 分段及封装应用层送来的数据 提供端到端的传输服
  • springboot部署项目的几种方式

    1 前言 springboot部署项目有两种方式 2 第一种方式是使用外置tomcat部署项目 添加如下代码 即可使用外tomcat部署项目 public class ServletInitializer extends SpringBoo
  • Vue中动态设置img的src值

    Vue中动态设置img的src值 问题 循环li组件时 动态设置img 设置img时 src属性报错 src数据格式 companyImages http localhost 8080 cszj image image image 2757
  • splinter 中chromdriver驱动问题

    windows32 plinter chrome 浏览器 1 首先需要安装splinter 2 调用函数 from splinterbrowser import Browser 3 Browser 这里会报错 因为Browser会默认浏览器
  • 2023届计算机专业弄潮儿如何快速找毕业论文文献?

    人生苦短 我用Python 一 准备工作 软件选择 Python3 8 pycharm 模块 requests 模拟请求 Selenium 浏览器自动化操作 win r打开搜索框 输入cmd按确定打开命令提示符窗口 输入pip instal
  • 孔乙己 长衫

    学历是 敲门砖 or 枷锁 孔乙已是鲁迅笔下人物 穷困流倒还穿着象征读书人的长衫 迁腐 麻木 最近 大家自我调佩是 当代孔乙己 学历成为思想负担 找工作时高不成低不就 你可以从以下几个角度说说你对看法 一 社会对于学历和职业之间的关系认知是
  • MAC 命令行拷贝文件夹

    命令 cp R 源文件 目标文件 cp R libsvm 3 23 Applications MATLAB R2017b app toolbox 将当前目录下的libsvm 3 23拷贝到 Applications MATLAB R2017
  • 利用xpath爬取网页

    xpath应该是爬取网页最简单的方法啦 因为你需要要懂xpath 可以直接通过浏览器来获取你想要的内容 以Chrome为例 按f12检查网页 用箭头点击自己想要的地方 比如我想提取出 故宫博物院 的xpath地址 右击 点击copy 然后选
  • PIE-engine 下载MODIS Mod09A1,去云并年度平均值合成

    最近毕设需要下载数据 本来懒想买现成的 发现价格实在离谱 考虑了GEE 梯子钱也不想花 一怒之下学了PIE 改的示例 没啥技术含量 var featureCollection0 pie FeatureCollection user 1506
  • 杭电 oj 2010 水仙花数 C++

    Problem Description 春天是鲜花的季节 水仙花就是其中最迷人的代表 数学上有个水仙花数 他是这样定义的 水仙花数 是指一个三位数 它的各位数字的立方和等于其本身 比如 153 1 3 5 3 3 3 现在要求输出所有在m和
  • 瀑布流布局 (移动端多数用的比较多 直播软件 浏览图片)

    瀑布流布局的核心是基于一个网格的布局 而且每行包含的项目列表高度是随机的 随着自己内容动态变化高度 同时每个项目列表呈堆栈形式排列 最为关键的是 堆栈之间彼此之间没有多余的间距差存大 场景 视频图片封面因高度不同 展示 案例效果 直播软件
  • vue的package.json中dependencies和devDependencies区别

    1 dependencies 应用能够正常运行所依赖的包 这种 dependencies 是最常见的 用户在使用 npm install 安装你的包时会自动安装这些依赖 2 devDependencies 开发应用时所依赖的工具包 通常是一
  • 【解决方案】LeetCode中的Monaco编辑器无法加载

    在Edge浏览器中经常出现LeetCode网站中的Monaco编辑器加载不出来的情况 而Codemirror编辑器又不是很好用 所以特此记录一下这个问题的解决方案 解决方法 打开Edge的设置 进入 隐私 搜索和服务 关掉 增强Web安全性
  • 13-FreeRTOS任务创建与删除

    任务创建和删除API函数位于文件task c中 需要包含task h头文件 task h里面包函数任务的类型函数 例如 对xTaskCreate的调用 通过指针方式 返回一个TaskHandle t 变量 然后可将该变量用vTaskDele
  • 模板的特化和萃取

    之前对模板化编程进行了总结 详见https blog csdn net timecur article details 89949643 这篇将介绍模板的重要概念 模板特化 模板的特化 模板针对某些具体的类型不能处理或者处理结果有误 就需要
  • vue项目开发流程

    1 创建项目 1 首先创建项目 我用的项目是vue 3 0 可以在新建文件夹中cmd 进入命令符 vue create 项目名 创建项目也可以在 命令符中vue ui 在浏览器中创建项目 2 项目安装好后 安装自己需要的各种插件 3 我们常
  • 【C语言技巧】STM32实现 printf 打印语句

    包含头文件 include
  • 计算机英语 st,1st、2nd、3rd、…10th都是什么的缩写?怎么读?10th之...-1st-英语-司俜辰同学...

    概述 本道作业题是司俜辰同学的课后练习 分享的知识点是1st 指导老师为任老师 涉及到的知识点涵盖 1st 2nd 3rd 10th都是什么的缩写 怎么读 10th之 1st 英语 下面是司俜辰作业题的详细 题目 1st 2nd 3rd 1

随机推荐

  • mysql的高级查询实例_MySQL高级查询---连接查询实例

    MySQL高级查询 连接查询实例 使用sql查询很简单 很基础的SQLECT语句查询 如果想从多个表查询比较复杂的信息 就会使用高级查询实现 常见的高级查询包括多连接查询 外连接查询与组合查询等 今天我先学习最常用的连接查询 我先以一张pe
  • 编译qt5.9-arm-qmake

    一 arm gcc环境配置 tar xvf rock3288 kernel arm linux gcc C opt vim basgrc 在最后面添加 export PATH opt gcc linaro arm linux gnueabi
  • Android EditText文本改变监听和获取到焦点的监听

    开发app快两年了 总结了一些小知识 以前没时间发表 最近有时间了 和大家分享一下 别忘记初始化 EditText edtUserName 添加文本改变的监听 edtUserName addTextChangedListener new T
  • VSCode离线汉化教程

    VSCode汉化包下载路径 https marketplace visualstudio com items itemName MS CEINTL vscode language pack zh hans 选择 Version Histor
  • 代码丢了不要怕,有jar包就能反编译找回

    推荐一个好用的反编译工具 直接上下载地址 http jd benow ca 根据自己的电脑下载版本 我下载的是windows版本 压缩包解压运行 打开jar包找到你的代码 注意 如果jar包也没有的就想想该重写了
  • C++类和对象:补充拷贝构造

    前言 如果一个类中什么成员都没有 简称为空类 空类中什么都没有吗 并不是的 任何一个类在我们不写的情况下 都会自动生成下面6个默认成员函数 目录 一 六大函数 1 构造函数 1 定义 2 特性 3 赋值 4 初始化列表 2 拷贝构造函数 3
  • Linux基础命令大全(下)

    作者 小刘在C站 个人主页 小刘主页 每天分享云计算网络运维课堂笔记 努力不一定有回报 但一定会有收获加油 一起努力 共赴美好人生 夕阳下 是最美的绽放 树高千尺 落叶归根人生不易 人间真情 目录 前言 编辑 一 命令到末行模式
  • 今日头条 文章采集_如何利用文章在今日头条引流精准粉

    今日头条这个平台 基本上从事互联网项目的人应该都知道 平台流量本身是非常庞大的 采用大数据算法推荐机制 自动采集判断用户的喜好 并且推荐的量也是非常可观的 对于那些知名作者而言 一篇文章即可拥有几十万甚至数百万的阅读量 这么大的一个流量池摆
  • Java调用Win API

    官方网站 http jawinproject sourceforge net 把lib文件夹下的jawin jar和jawin stubs jar放到 JAVA HOME jre lib ext 目录下 把bin文件夹下的jawin dll
  • 永磁同步电机矢量控制到无速度传感器控制学习教程(PMSM)(一)

    一个阶段的学习结束了 整理了之前的过程中的学习成果 已经过了工作的年纪 在这里稍微出一下自己做的一套永磁同步电机的教程 从基础的矢量控制 到应用性较强的MTPA 弱磁控制等 最后深入到无速度传感器的控制 搜集了三种无速度的方法 足够大家从基
  • html/css笔记 table表格文本垂直水平居中对齐方法

    简介 平时工作中开发经常会遇到html网页样式设计 这里记录一下笔记方便后期查看 也顺便给其他人提供一个参考 HTML 文本垂直水平居中对齐方法 一 css样式 水平居中 text align 应用于块级元素的文本水平居中 text ali
  • React 中ref的几种用法

    React 中ref的几种用法 1 字符串 通过 this refs a 来引用真实dom的节点 dom 节点上使用
  • 结构光相机国产、非国产统计参数对比分析

    结构光相机国产 非国产统计参数对比分析 1 Kinect v1 Kinect v1深度相机拥有一个RGB彩色摄像头 一个红外线CMOS摄像机和一个红外发射器 相机的红外线CMOS摄像机和红外发射器以左右水平的方式分布 该相机采用的是以结构光
  • Unix环境下Oracle数据库完全优化详解

    Unix环境下Oracle数据库完全优化详解 2007 04 19 12 54 02 作者 changelive 浏览次数 14 文字大小 大 中 小 进入论坛 如今的优化己经向优化等待 waits 转型了 实际中性能优化最根本的出现点也都
  • Windows驱动开发第11课(R3与R0通信交换数据第二节)

    在上一节课我们证实了在用户层调用CreateFile函数时 相应的在驱动层会响应一个IRP MJ CREATE的事件 这节课我们来看看用户层和驱动层是怎么交换数据的 首先来介绍一下控制码 由CTL CODE宏创建 是一个唯一的32位系统I
  • 数据库系统原理(第二版)知识点总结

    目录 第一章 概述 基本知识 数据模型 数据模型的组成要素 数据模型的分类 数据库系统的结构 第二章 关系运算 2 1 关系运算语言 1 关系代数语言 第三章 数据完整性 实体完整性 主属性的取值不能为空值 主属性的候选键的取值要非空且唯一
  • Python中的一些特殊函数

    https www cnblogs com maybe2030 p 4678920 html
  • centos7系统启动流程

    开机自检 gt 查找第一启动项设备 gt 加载第一启动项设备上的bootloader 存在于MBR中 gt 加载内核 initramfs gt 只读加载rootfs gt sbin init 即systemd
  • Flask 数据库 连接池、DBUtils、http 连接池

    1 DBUtils 简介 使用 DBUtils 简介 DBUtils 是一套用于管理 数据库 连接池 的Python包 为 高频度 高并发 的数据库访问提供更好的性能 可以自动管理连接对象的创建和释放 并允许对非线程安全的数据库接口进行线程
  • vector 查找_怎么写出无bug的二分查找算法代码

    封面图来自 geeksforgeeks 1 简介 二分查找算法是一类比较基础的算法 然而想要短时间内 写出二分查找的无 bug 版本 也不是很容易的 为此我查找了一些资料 终于弄清了二分查找算法的套路 在此分享给大家 也算是对自己学习知识的