透彻理解二分查找-算法通关村

透彻理解二分查找-算法通关村


  • 常见的查找算法有顺序查找、二分查找、插值查找,树表查找、分块查找、哈希查找等等。其实二分查找、插值查找以及斐波那契查找都可以归为一类—插值查找。插值查找是在二分查找的基础上的优化查找算法。

  • 二分查找的价值,请记住:
    **凡是在排好序的地方查找的都就可以考虑用二分来优化查找效率。不一定全局都排好才行,只要某个部分是排好的,就可以针对该部分进行二分查找,这是很多题目优化查找的重要途径。**

  • 我们知道二分就是每次只取一半,但是在有些场景下,我们大致知道数据的位置了,就不必折半,取1/3,3/4这样不是也可以吗?当然可以!为了更有通用性,插值查找使用的公式是:

  • mid = low + (key - a[low]) / (a[high] - a[low]) * (high-low)
  • 分块查找是折半查找和顺序查找的一种改进方法,分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合于节点动态变化的情况。分块查找要求把一个数据分为若干块,每一块里面的元素可以是无序的,但是块与块之间的元素需要是有序的。即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,依次类推。


1 基本查找

  • 查找算法中顺序查找算是最简单的了,无论是有序的还是无序的都可以,也不需要排序,只需要一个个对比即可,但其实效率很低。

  •   int search(int[] arr, int key){
              for(int i = 0; i < arr.length; i++){
                  if(arr[i] == key){
                      return i;
                  }
              }
              return -1;
          }
    

2 二分查找与分治

  • 在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
  • 这个技巧是很多高效算法的基础,如二分搜索、排序算法(快速排序,归并排序)等等…,任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,⋯。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。
    二分查找就是将中间结果与目标进行比较,一次去掉一半,因此二分查找可以说是最简单、最典型的分治了。
  • 二分查找,不管是循环还是递归方式,我觉得应该达到写到闭着眼睛,一分钟就能写出来的地步。
    这里再补充一个问题,分治和递归是一回事吗?很显然不是。这是两种完全不同的思想,二分查找是分治思想,我们可以使用递归或者循环的方式来做。而很多递归问题也不一定是分治的,因此两个完全不是一回事。

2.1 循环的方式

  • 常见的使用循环的方式来实现二分查找如下:

  •   public int binarySearch(int[] array, int low, int high, int target){
              while(low <= high){
                  int mid = (low + high)/2;
                  if(array[mid] == target){
                      return mid;
                  }
                  if(array[mid] < target){
                      //由于array[mid] 不是目标值,
                      // 因此再次递归搜索时,可以将其排除
                      low = mid + 1;
                  }
                  if(array[mid] > target){
                      high = mid - 1;
                  }
              }
              return -1;
          }
    
  • 观察上面的代码,有个很重要的细节没有处理,在计算机中,除的效率非常低,一般可以使用移位来代替:

  • 将:int mid = (low + high) / 2;
    换成:int mid = (low + high) >> 1;
  • 但是还没完,问题就是假如 low 和 high 很大的话,low + high 可能会溢出

  • 将:int mid = (low + high) >> 1;
    换成:int mid = low + (high - low) >> 1;
  • 只要low和high没有溢出,上面的mid一定不会溢出。

  • 如果你觉得这就没问题了,那你可能会输得很惨,因为当你测试的时候,可能会出现死循环,例如原始序列是1到8,搜索3的时候就死循环了,为什么呢?

  • 这是因为移位的运算符>>优先级比加减要低,所以上面的代码等价结构是这样的:
    (low+(high - low))>>1
    很明显这不是我们预期的。解决方法也很简单,加括号就行了。所以最终的代码就是:

  •   public int binarySearch3(int[] array, int low, int high, int target){
              while(low <= high){
                  int mid = low + ((high - low) >> 1); // 使用位运算符计算中间索引
                  if(array[mid] == target){
                      return mid;
                  }
                  if(array[mid] < target){
                      //由于array[mid] 不是目标值,可以将其排除
                      low = mid + 1;
                  }
                  if(array[mid] > target){
                      high = mid - 1;
                  }
              }
              return -1;
          }
    

2.2 递归的方式

  • 递归的话就不必多说了,直接看代码:

  •   public int binarySearch4(int[] array, int low, int high, int target){
              //递归终止时间
              while(low <= high){
                  int mid = low + ((high - low) >> 1); // 使用位运算符计算中间索引
                  if(array[mid] == target){
                      return mid;//返回目标值的位置,从1开始
                  }else if(array[mid] < target){
                      //由于array[mid] 不是目标值,
                      // 因此再次递归搜索时,可以将其排除
                      return binarySearch(array, mid+1, high, target);
                  }else if(array[mid] > target){
                      return binarySearch(array, low, mid-1, target);
                  }
              }
              return -1;//没有搜索到
          }
    

3 元素中有重复的二分查找

  • 假如在上面的基础上,元素存在重复,如果重复则**找左侧第一个**,请问该怎么做呢?
    这里的关键是找到目标结果之后不是返回而是继续向左侧移动。第一种,也是最简单的方式,找到相等位置向左使用线性查找,直到找到相应的位置。

  •   public int binarySearchOfRepeat(int[] array, int target){
              if(array == null || array.length == 0){
                  return -1;
              }
              int left = 0;
              int right = array.length-1;
              while(left <= right){
                  // 使用位运算符计算中间索引
                  int mid = left + ((right - left) >> 1); 
                  if(array[mid] < target){
                      left = mid+1;
                  }else if(array[mid] > target){
                      right = mid-1;
                  }else{
                      //该循环会持续向左移动 mid 指针,
                      // 直到它不再指向目标值或者到达数组的开始位置。
                      // 这样做是为了跳过所有重复的目标值,直到找到第一个出现的目标值。
                      while(mid != 0 && array[mid] == target){
                          mid--;
                      }
                      //说明这是数组中目标值的第一个出现位置,因此返回 mid。
                      if(mid == 0 && array[mid] == target){
                          return mid;
                      }
                      return mid+1;
                  }
              }
              return -1;//没有搜索到
          }
    
  • 注意这里在找到之后的while循环结束后,为什么要返回mid+1,而不是mid呢?这是因为此时 array[mid] 已经不等于target了,例如假如序列为 { 1, 2, 2, 2, 2, 3, 3},当target=3,当内层的while退出时,array[mid] = 2,因此我们必须返回mid+1。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据分析与内存计算——Spark安装以及Hadoop操作——注意事项

一、Spark安装 1.相关链接 https://dblab.xmu.edu.cn/blog/4322/ 2.安装Spark&#xff08;Local模式&#xff09; 按照文章中的步骤安装即可 遇到问题&#xff1a;xshell以及xftp不能使用 解决办法&#xff1a; 在linux使用镜像网站进行下载&#xff1a;wget https://mi…

Three.js真实相机模拟

有没有想过如何在 3D Web 应用程序中模拟物理相机&#xff1f; 在这篇博文中&#xff0c;我将向你展示如何使用 Three.js和 OpenCV 来完成此操作。 我们将从模拟针孔相机模型开始&#xff0c;然后添加真实的镜头畸变。 具体来说&#xff0c;我们将仔细研究 OpenCV 的两个失真模…

【Java 集合进阶】单练集合顶层接口collction迭代器

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

适合初学者的Linux的综合项目

大家好&#xff0c;今天给大家介绍适合初学者的Linux的综合项目&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 对于初学者来说&#xff0c;Linux的综合项目应当既具有教育意义又…

element plus 输入框样式模仿Material-UI

获取焦点状态 自定义指令 app.directive(focus, { // 当被绑定的元素插入到 DOM 中时…… mounted(el) { const descendants el.querySelectorAll(.el-input__inner); var cssClass newLable;el.classList.add(cssClass); // 遍历并操作这些子孙节点 descendants.forE…

(24年4月2日更新)Linux安装chrome及chromedriver(Ubuntu20.0416.04)

一、安装Chrome 1&#xff09;先执行命令下载chrome&#xff1a; wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb2&#xff09;安装chrome sudo dpkg -i google-chrome-stable_current_amd64.deb踩坑&#xff1a;这里会提示如下报错&…

安卓主板MT8390(Genio 700)_MTK联发科Linux开发板方案

MediaTek Genio 700 &#xff08;MT8390&#xff09;是一款高性能的边缘 AI 物联网平台&#xff0c;专为智能家居、互动零售、工业与商业应用而设计。提供快速响应的边缘计算能力、先进的多媒体功能、广泛的传感器和连接方式&#xff0c;且支持多任务操作系统。 MT8390安卓核心…

ArrayList扩容原理

ArrayList源码分析 分析ArrayList源码主要从三个方面去翻阅&#xff1a;成员变量&#xff0c;构造函数&#xff0c;关键方法 以下源码都来源于jdk1.8 1 成员变量 DEFAULT_CAPACITY 10; 默认初始的容量**(CAPACITY) EMPTY_ELEMENTDATA {}; 用于空实例的共享空数组实例 DEFAU…

Java项目:85 springboot智能物流管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本美发门店管理系统有管理员和用户两个角色。 用户功能有项目预定管理&#xff0c;产品购买管理&#xff0c;会员充值管理&#xff0c;余额查询管理。…

文本自动粘贴编辑器:支持自动粘贴并筛选手机号码,让信息处理更轻松

在信息时代的浪潮中&#xff0c;文本处理已成为我们日常工作与生活的重要组成部分。无论是商务沟通、社交互动还是个人事务处理&#xff0c;手机号码的筛选与粘贴都显得尤为关键。然而&#xff0c;传统的文本处理方式效率低下、易出错&#xff0c;已无法满足现代人的高效需求。…

Linux(05) Debian 系统修改主机名

查看主机名 方法1&#xff1a;hostname hostname 方法2&#xff1a;cat etc/hostname cat /etc/hostname 如果在创建Linux系统的时候忘记修改主机名&#xff0c;可以采用以下的方式来修改主机名称。 修改主机名 注意&#xff0c;在linux中下划线“_”可能是无效的字符&…

disearch目录扫描工具

项目地址 GitHub - maurosoria/dirsearch: Web path scanner 安装 apt-get install dirsearch 使用 dirsearch -u http://61.147.171.105:56237/

网络协议学习——HTTPS

目录 ​编辑 一&#xff0c;认识HTTPS 二&#xff0c;加密方式 1&#xff0c;对称式加密 2&#xff0c;非对称式的加密 3&#xff0c;数据指纹&#xff08;数据摘要&#xff09; 4&#xff0c;数据签名 三&#xff0c;HTTPS的工作原理 实现方式 数字证书 一&#xff0c…

配mmdetection

总流程&#xff1a; 1. 安装conda 参考链接后面补上 列出可用的conda环境 conda env list 删除指定环境 conda remove --name myenv --all 创建并激活指定环境 conda create --name openmmlab python3.8 -y conda activate openmmlab 2. 装pytorch&#xff0c;版本别装错…

zabbix图表时间与服务器时间不一致问题

部署完zabbix后&#xff0c;有时候会发现zabbix服务器的时间明明是对的&#xff0c;但是图标的时间不对&#xff0c;通过以下的配置可以快速解决。 登录zabbix-nginx容器 docker exec -u root -it docker-compose-zabbix-zabbix-web-nginx-mysql-1 bash修改php配置文件 vi /e…

excel散点图怎么每个点添加名称

最终效果图&#xff1a; 添加图标元素->数据标签->其他数据标签选项 选择单元格中的值 手动拖动数据标签&#xff0c;调整到合适的位置。

javaweb学习(day11-监听器Listener过滤器Filter)

一、监听器Listener 1 Listener介绍 Listener 监听器它是 JavaWeb 的三大组件之一。JavaWeb 的三大组件分别是&#xff1a;Servlet 程 序、Listener 监听器、Filter 过滤器 Listener 是 JavaEE 的规范&#xff0c;就是接口 监听器的作用是&#xff0c;监听某种变化(一般就是对…

RISC-V GNU Toolchain 工具链安装问题解决(含 stdio.h 问题解决)

我的安装过程主要参照 riscv-collab/riscv-gnu-toolchain 的官方 Readme 和这位佬的博客&#xff1a;RSIC-V工具链介绍及其安装教程 - 风正豪 &#xff08;大佬的博客写的非常详细&#xff0c;唯一不足就是 sudo make linux -jxx 是全部小写。&#xff09; 工具链前前后后我装了…

搜维尔科技:SenseGlove Nova 允许以最简单的方式操作机器人并与物体交互

扩展 Robotics 和 QuarkXR 人机界面 XR 应用 Extend Robotics 利用扩展现实技术&#xff0c;让没有机器人专业知识的个人能够远程控制机器人。他们的 AMAS 解决方案使操作员能够不受地理限制地轻松控制机器人。 需要解决的挑战【搜维尔科技】 目前&#xff0c;操作机器人是一…

day4|gin的中间件和路由分组

中间件其实是一个方法&#xff0c; 在.use就可以调用中间件函数 r : gin.Default()v1 : r.Group("v1")//v1 : r.Group("v1").Use()v1.GET("test", func(c *gin.Context) {fmt.Println("get into the test")c.JSON(200, gin.H{"…