数据结构与算法系列-二分查找

二分查找

什么是二分查找?

二分查找是一种针对有序集合,每次将要查找的区间缩小一半,直到找到查找元素,或区间被缩小为0。

如何实现二分查找?

实现有3个注意点:

  1. 终止条件是 low <= high
    2.求中点的算法是 low + (high - low)/2
    3.low和high的更新是low=mid+1 即到更大的区间找, high=mid-1  即到更小的区间找。

非递归实现

        // 终止条件low>=high 当low=high时,mid=low+(high-low)/2=low=high,即没有等号将少比较一个点// 中点 mid = low + (high-low)/2 即以low为起点,计算low和high之间的中点// mid<value 找大区间 low=mid+1,mid>value,找小区间,high=mid-1public int binSearch(int[] arr,int n,int value){int low=0;int high=n-1;while(low<=high){int mid=low+((high-low)>>1);//int mid=low+(high-low)/2;if(arr[mid]<value){low=mid+1;} else if(arr[mid]>value){high=mid-1;} else{return mid;}}return -1;}

递归实现

// 二分查找的递归实现
public int bsearch(int[] a, int n, int val) {return bsearchInternally(a, 0, n - 1, val);
}private int bsearchInternally(int[] a, int low, int high, int value) {if (low > high) return -1;int mid =  low + ((high - low) >> 1);if (a[mid] == value) {return mid;} else if (a[mid] < value) {return bsearchInternally(a, mid+1, high, value);} else {return bsearchInternally(a, low, mid-1, value);}
}

二分查找有哪些局限性?

1.  二分查找依赖的是顺序表结构,也就是数组
2.  二分查找要求数据有序
3.  数据量太小不适合二分查找,除非每个数据的操作非常耗时,比如,数组中存储的都是长度超过 300 的字符串,如此长的两个字符串之间比对大小,就会非常耗时。
4.  数据量太大也不适合二分查找,因为数组需要占用大量连续的空间
5.  动态数据不适合二分查找,频繁的插入和删除在数组结构下效率很低

思考题

  1. 如何编程实现“求一个数的平方根”?要求精确到小数点后 6 位。
    可以用牛顿弦切法来求平方跟
    double number = 2; //待求平方根的数
    double xini = 10;//初始点
    while(xinixini - number > 1e-6) {
    xini = (number + xini
    xini)/2/xini;
    }
    // xini为平方根
  2. 我刚才说了,如果数据使用链表存储,二分查找的时间复杂就会变得很高,那查找的时间复杂度究竟是多少呢?
    链表不像数组那样支持随机访问,所以链表主要花的是查找第n个节点的访问的时间。
    第一次需要遍历n/2个节点,
    第二次需要遍历n/4个节点,
    第三次需要遍历n/8个节点,

n/2 + n/4 + n/8 + …
和约等于n,因此算法复杂度为O(n),单考虑到其他二分查找的额外操作,会比直接遍历循环查找慢一些。

二分查找有哪些变体问题?

对于有序数组中有重复元素而言二分查找通常有以下4个变体问题。
查找第一个值等于给定值的元素
查找最后一个值等于给定值的元素
查找第一个大于等于给定值的元素
查找最后一个小于等于给定值的元素

这些都是查找等于给定值的变体问题,想一想,你有什么思路可以实现呢?

如何实现变体的二分查找?

arr[mid]和value的值的比较有三种情况:大于,小于,等于
那对于问题1和2而言我们要特殊处理的等于的情况,
问题3要特殊处理大于等于的情况,
问题4要特殊处理小于等于的情况。
问题1的特殊处理逻辑:
等于的情况下,我们mid的下标有可能是0,即数组的第一个元素,那么我们直接返回mid就好了,
那其余情况就是mid>0,那么我们就看mid的前一个元素是否等于value,如果不等于,说明mid是第一个等于value的元素,也直接返回,
如果等于mid,那么我们就需要在前面的区间去查找了,即 high = mid - 1;

那问题2,3,4和1的处理思路类似,可以试着自己实现。

1

    public int binSearchFirst(int[] arr,int n,int value){int low=0;int high=n-1;while (low<=high) {int mid = low + (high - low) / 2;if(arr[mid]<value){low=mid+1;} else if(arr[mid]>value){high=mid-1;} else{// 需要特殊处理的是等于的情况// 1.看mid是否为第一个元素,是返回mid// 2.看mid的前一个元素是否等于value,不等于返回mid// 3.mid的前一个元素等于value,那么就应该去前面区间找,即 high = mid - 1;if(mid==0||arr[mid-1]!=value) {return mid;}high=mid-1;}}return -1;}

2

    public int binSearchFirst(int[] arr,int n,int value){int low=0;int high=n-1;while (low<=high) {int mid = low + (high - low) / 2;if(arr[mid]<value){low=mid+1;} else if(arr[mid]>value){high=mid-1;} else{// 需要特殊处理的是等于的情况// 1.看是mid否为最后一个元素,是返回mid// 2.看mid的后一个元素是否等于value,不等于返回mid// 3.mid的后一个元素等于value,那么就应该去后面区间找,即 low = mid + 1;if(mid==n-1||arr[mid+1]!=value) {return mid;}low=mid+1;}}return -1;}

3

    public int binSearchFirst(int[] arr,int n,int value) {int low = 0;int high = n - 1;while (low <= high) {int mid = low + (high - low) / 2;if (arr[mid] >= value) {// 主要处理大于等于的情况// 1.看mid是否为第一个元素,是返回mid// 2.看mid的前一个元素是否小于value,是返回mid// 3.前一个元素大于等于value,应该去前面区间找,即high=mid-1if(mid==0||arr[mid-1]<value){return mid;}high = mid - 1;} else {low = mid + 1;}}return -1;}

4

    public int binSearchFirst(int[] arr,int n,int value) {int low = 0;int high = n - 1;while (low <= high) {int mid = low + (high - low) / 2;if (arr[mid] <= value) {// 主要处理大于等于的情况// 1.看mid是否为最后一个元素,是返回mid// 2.看mid的后一个元素是否大于value,是返回mid// 3.后一个元素小于等于value,应该去后面区间找,即low=mid+1if(mid==n-1||arr[mid+1]>value){return mid;}low = mid + 1;} else {high = mid - 1;}}return -1;}

二分查找的实际应用场景?

绝大部分情况能用二分查找解决的问题我们更倾向使用散列表或二叉查找树,
那二分查找其实更适用于近似的查找(范围查找)问题,因为这类问题用上述数据结构都不容易实现。

思考题

我们今天讲的都是非常规的二分查找问题,今天的思考题也是一个非常规的二分查找问题。如果有序数组是一个循环有序数组,比如 4,5,6,1,2,3。针对这种情况,如何实现一个求“值等于给定值”的二分查找算法呢?

我们发现循环数组存在一个性质:以数组中间点为分区,会将数组分成一个有序数组和一个循环有序数组。
 
 如果首元素小于 mid,说明前半部分是有序的,后半部分是循环有序数组;
 如果首元素大于 mid,说明后半部分是有序的,前半部分是循环有序的数组;
 如果目标元素在有序数组范围中,使用二分查找;
 如果目标元素在循环有序数组中,设定数组边界后,使用以上方法继续查找。
 
 时间复杂度为 O(logN)。

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

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

相关文章

Matlab杂项记录

文章目录 其他do nothing command in matlab代码格式化在同一个m文件中写多个独立的功能函数改变启动时的默认文件夹博文链接 table使用 其他 do nothing command in matlab disp() % Does nothing but allows developer to set a breakpoint here.代码格式化 Matlab编辑器具…

强化学习实践(一)Gym介绍

学了一段时间强化学习的理论&#xff0c;近期准备进行一些算法实践。应用算法的前提是要创建一个合适的仿真环境&#xff0c;目前Openai的Gym(https://gym.openai.com) 是主流的强化学习实验环境库。 Gym已经集成许多开发好的环境&#xff0c;让RL的研究者们可以直接上手使用&a…

Python的函数

近期遇到了一个没怎么看懂的Python函数的形式。 def twoSum(self, nums: List[int], target: int) -> List[int]: 后来上网查了资料。

(二)激光线扫描-相机标定

1. 何为相机标定? 当相机拍摄照片时,我们看到的图像通常与我们实际看到的不完全相同。这是由相机镜头引起的,而且发生的频率比我们想象的要高。 这种图像的改变就是我们所说的畸变。一般来说,畸变是指直线在图像中出现弯曲或弯曲。 这种畸变我们可以通过相机标定来进行解…

Hudi SQL DDL

本文介绍Hudi在 Spark 和 Flink 中使用SQL创建和更改表的支持。 1.Spark SQL 创建hudi表 1.1 创建非分区表 使用标准CREATE TABLE语法创建表,该语法支持分区和传递表属性。 CREATE TABLE [IF NOT EXISTS] [db_name.]table_name[(col_name data_type [COMMENT col_comment], ..…

阿里云服务器地域和可用区查询表_地域可用区大全

阿里云服务器地域和可用区有哪些&#xff1f;阿里云服务器地域节点遍布全球29个地域、88个可用区&#xff0c;包括中国大陆、中国香港、日本、美国、新加坡、孟买、泰国、首尔、迪拜等地域&#xff0c;同一个地域下有多个可用区可以选择&#xff0c;阿里云服务器网分享2023新版…

【AntDesign】封装全局异常处理-全局拦截器

[toc] 场景 本文前端用的是阿里的Ant-Design框架&#xff0c;其他框架也有全局拦截器&#xff0c;思路是相同&#xff0c;具体实现自行百度下吧 因为每次都需要调接口&#xff0c;都需要单独处理异常情况&#xff08;code !0&#xff09;&#xff0c;因此前端需要对后端返回的…

【Linux】Linux常用命令—文件管理(上)

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

windows11 安装Nodejs

一、介绍 NPM 全称 Node Package Manager&#xff0c;它是 JavaScript 的包管理工具, 并且是 Node.js 平台的默认包管理工具。通 过 NPM 可以安装、共享、分发代码,管理项目依赖关系。 可从NPM服务器下载别人编写的第三方包到本地使用。可从NPM服务器下载并安装别人编写的命令…

java获取字符串集合中每个字符并且组成一个新的集合实现

直接怼代码&#xff0c;刚好碰到了这种需求&#xff0c;也是想了可久&#xff0c;其实想想也还是挺简单的 public static void main(String[] args) { // 原始字符串集合 List<String> originalList new ArrayList<>(); originalList.add("Hello"); …

基于python的网络爬虫搜索引擎的设计

项目介绍 随着互联网的飞速发展&#xff0c;web已经成为人们主要的检索&#xff0c;和发布的主要平台&#xff0c;在海量的数据中如何快速&#xff0c;准确的找到用户所需要的信息成为人们当前所需求的&#xff0c;而网络爬虫就是为了满足这一需要而产生的研究领域。在现实中我…

【typescript】面向对象(下篇),包含接口,属性的封装,泛型

假期第八篇&#xff0c;对于基础的知识点&#xff0c;我感觉自己还是很薄弱的。 趁着假期&#xff0c;再去复习一遍 面向对象&#xff1a;程序中所有的操作都需要通过对象来完成 计算机程序的本质就是对现实事物的抽象&#xff0c;抽象的反义词是具体。比如照片是对一个具体的…

【C语言】八大排序算法

文章目录 一、冒泡排序1、定义2、思想及图解3、代码 二、快速排序1、hoare版本2、挖坑法3、前后指针法4、非递归快排5、快速排序优化1&#xff09;三数取中选key值2&#xff09;小区间优化 三、直接插入排序1、定义2、代码 四、希尔排序1、定义2、图解3、代码 五、选择排序1、排…

基于YOLOv8的安全帽检测系统(3):EMA基于跨空间学习的高效多尺度注意力、效果优于ECA、CBAM、CA,助力行为检测 | ICASSP2023

目录 1.Yolov8介绍 2.安全帽数据集介绍 3.EMA介绍 4.训练结果分析 1.Yolov8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的&#xff08;SOTA&#xff09;模型&#xff0c;它建立在先前YOLO成功基础上…

操作符keyof的作用是什么

keyof 是 TypeScript 中的一个操作符&#xff0c;它用于获取一个类型的所有属性名&#xff08;键&#xff09;组成的联合类型。 使用 keyof 操作符可以在编译时获得一个类型的属性名&#xff0c;然后将这些属性名作为联合类型进行处理或操作。它可以用于访问对象的属性&#x…

超大视频如何优雅切片

背景 有一次录屏产生了一个大小为33G的文件, 我想把他上传到B站, 但是B站最大只支持4G. 无法上传, 因此做了一个简单的探索. 质疑与思考 a. 有没有一个工具或一个程序协助我做分片呢? 尝试 a. 必剪 > 有大小限制, 添加素材加不进去(而且报错信息也提示的不对) b. PR &…

(高阶) Redis 7 第19讲 缓存过期淘汰策略 大厂篇

🌹 以下分享 Redis 缓存淘汰策略,如有问题请指教。🌹🌹 如你对技术也感兴趣,欢迎交流。🌹🌹🌹 如有对阁下帮助,请👍点赞💖收藏🐱‍🏍分享😀 面试题 1. 生产上,redis内存设置的多少 2. 如何配置、修改Redis 内存大小 3. 如果内存满了,如何处理 4. …

GO 中优雅编码和降低圈复杂度

本次主要是聊聊关于使用接口抽象和降低圈复杂度的方式 工作中&#xff0c;难免会遇到老项目老代码&#xff0c;不仅仅需要我们维护&#xff0c;可能还需要我们在原来的垃圾代码上进行新增功能或者是进行优化调整 例如 现有的老代码中关于用户系统这一块就已经经是摇摇欲坠&a…

OpenMMLab【超级视客营】——支持InverseForm Loss(MMSegmentation的第三个PR)

文章目录 1. 任务目标1.1 issue1.2 原理相关资料&#xff08;论文讲解&#xff09;InverseFormSTN(Spatial Transformer Networks) 1.3 实现相关资料&#xff08;相关PR&#xff09; 2. 理解原理3. 代码实现3.X checklist3.0 Issue中的有效内容3.1 MMSegmentation支持multiple …

flink的计时器

背景 在flink中&#xff0c;我们经常使用ontimer计时器实现很多逻辑的功能&#xff0c;常见的比如某个传感器温度增加连续超过1分钟的告警输出等&#xff0c;本文就来简单记录下计时器的作用 计时器 ontimer的定义 public void onTimer(long timestamp, OnTimerContext ctx…