1-1二分查找

二分查找

  • 1 基础版
    • 1.1 算法描述
    • 1.2 算法流程图
    • 1.3 算法实现
      • 1.3.1 Java实现
  • 2 改动版
    • 2.1 算法描述
    • 2.2 算法流程图
    • 2.3 算法实现
      • 2.3.1 Java实现
    • 2.4 改进点分析
      • 2.4.1 区间定义差异
      • 2.4.2 核心改进原理
      • 2.4.3 数学等价性证明
  • 3 平衡版
    • 3.1 算法描述
    • 3.2 算法流程图
    • 3.3 算法实现
      • 3.3.1 Java实现
    • 3.4 改进点分析
      • 3.4.1 区间定义差异
      • 3.4.2 核心改进原理
      • 3.4.3 数学等价性证明
      • 3.4.4 性能分析
  • 4 对比总结
    • 4.1 区间定义
    • 4.2 中间索引计算
    • 4.3 终止条件
    • 4.4 改进点
    • 4.5 性能分析

二分查找Binary Search)是一种高效的搜索算法,适用于已经排好序的数组。它通过将待查找的元素与数组中间的元素进行比较,从而每次可以排除掉一半的元素,以此来快速缩小搜索范围,直到找到目标元素或确定其不存在于数组中。该算法的时间复杂度为 O ( l o g n ) O(logn) O(logn),意味着随着输入规模的增长,查找时间增长缓慢


1 基础版

需求:在有序数组 A A A中查找值 t a r g e t target target

  • 如果找到,则返回该值在数组中的索引。
  • 如果未找到,则返回 − 1 -1 1

1.1 算法描述

  1. 初始化:给定一个内含 n n n个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ . . . ≤ A n − 1 A_{0} ≤ A_{1} ≤ A_{2} ≤ ... ≤ A_{n−1} A0A1A2...An1,一个待查值 t a r g e t target target
  2. 设置初始索引:设置 i = 0 i = 0 i=0 j = n − 1 j = n − 1 j=n1
  3. 终止条件:如果 i > j i > j i>j,结束查找,找不到需要的值,返回 − 1 −1 1
  4. 计算中间索引:设置 m = ⌊ i + j 2 ⌋ m = \lfloor \frac{i+j}{2} \rfloor m=2i+j m m m为数组的中间索引, ⌊ ⋅ ⌋ \lfloor ⋅ \rfloor 表示向下取整。
  5. 比较并调整索引
    • 如果 t a r g e t < A m target < A_{m} target<Am,设置 j = m − 1 j = m − 1 j=m1,跳转至第2步。
    • 如果 A m < t a r g e t A_{m} < target Am<target,设置 i = m + 1 i = m + 1 i=m+1,跳转至第2步。
    • 如果 A m = t a r g e t A_{m} = target Am=target,结束查找,并返回所在位置的数组索引 m m m

1.2 算法流程图

target < A[m]
A[m] < target
A[m] = target
开始
设置 i=0, j=n-1
检查 i <= j
返回 -1, 结束查找
计算 m = floor((i+j)/2)
比较 target 和 A[m]
设置 j = m - 1
设置 i = m + 1
返回 m, 结束查找

1.3 算法实现

1.3.1 Java实现

这是一种最简单的二分查找算法的实现,从逻辑上没什么问题,但是在实际应用的过程中是可能会出bug的。

/*** 使用二分查找算法在一个有序数组中查找目标值的基本实现** @param arr    一个有序的整数数组* @param target 要查找的目标值* @return 目标值在数组中的索引,如果目标值不在数组中,则返回-1*/
public static int binarySearchBasic(int[] arr, int target) {// TODO 1. 设置初始索引int left = 0; // 左边界int right = arr.length - 1; // 右边界// TODO 2. 循环查找while (left <= right) {// TODO 2.1. 计算中间索引int mid = (right + left) >>> 1;// TODO 2.2. 比较中间索引的值与目标值if (arr[mid] == target) {// TODO 2.3. 如果等于目标值,返回中间索引return mid;} else if (arr[mid] < target) {// TODO 2.4. 如果小于目标值,更新左边界left = mid + 1;} else {// TODO 2.5. 如果大于目标值,更新右边界right = mid - 1;}}// TODO 3. 如果循环结束,没有找到目标值,返回-1return -1;
}
  1. 循环条件为左边界≤右边界
    • 循环条件 left <= right 保证了以下两种情况:
      • 当区间仍有元素时left <= right),继续检查。
      • 当区间为空时left > right),终止循环。
    • 若循环条件为 left < right
      • 问题场景:当区间仅剩一个元素时(left == right),循环条件不成立,直接跳过检查。
      • 后果:若该元素恰好是目标值,算法会错误地返回 -1
    • 正确条件 left <= right
      • 覆盖所有有效区间:即使只剩一个元素(left == right),仍会进入循环检查。
      • 终止条件正确性:当 left > right 时,区间已为空,表明目标值不存在。
  2. 中间索引如何求
    • (right + left) / 2

      这一种是直接计算中间值的方法

      计算方式在数学上是正确的,但是这种方法存在一个严重的潜在问题整数溢出

      • leftright 都接近 int 类型的最大值(Integer.MAX_VALUE,即 2^31 - 1)时,left + right 可能会超过 int 类型的最大值,导致溢出
      • 溢出后,计算结果会变成负数,导致 mid 的值错误,进而引发索引越界或逻辑错误。
    • left + (right - left) / 2

      这一种是通过偏移量计算中间值的方法

      • 避免溢出right - left 的结果一定小于 right,不会超过 int 的范围。
      • 逻辑清晰:通过偏移量计算中间值,更直观地表达“从 left 开始,加上区间长度的一半”。
    • 使用 >>(有符号右移)

      >>有符号右移操作符,它会保留符号位(即最高位),并在左侧补上与符号位相同的位。

      int mid = (left + right) >> 1;
      
      • 特点
        • 等价于除以 2(left + right) >> 1 等价于 (left + right) / 2
        • 保留符号位:如果 left + right 是负数,右移后结果仍然是负数。
        • 性能优化:右移操作比除法操作更快,适合对性能要求较高的场景。
      • 潜在问题
        • 整数溢出:如果 left + right 超过 int 的最大值(Integer.MAX_VALUE),仍然会导致溢出问题。
        • 负数问题:如果 left + right 是负数,右移结果可能与预期不符。相加为负数的原因是:当两个正数相加的结果超过 int 的最大值时,最高位(符号位)会从 0 变为 1,导致结果被解释为负数。
    • 使用 >>>(无符号右移)

      >>>无符号右移操作符,它会忽略符号位,并在左侧补 0。

      int mid = (left + right) >>> 1;
      
      • 特点
        • 等价于除以 2(left + right) >>> 1 等价于将 left + right 视为无符号整数后除以 2。
        • 忽略符号位:无论 left + right 是正数还是负数,右移后结果都是正数。
        • 避免负数问题:即使 left + right 是负数,结果也会被正确处理。
      • 优点
        • 避免溢出问题>>> 可以正确处理 left + right 超过 int 最大值的情况。
          • 例如,left = 1_500_000_000right = 2_000_000_000left + right 会溢出为负数,但 (left + right) >>> 1 会得到正确的结果。
        • 性能优化:与 >> 类似,右移操作比除法更快。
    • 性能对比

      以下是几种计算方式的性能对比:

      计算方式性能是否可能溢出是否支持负数
      (left + right) / 2较慢
      left + (right - left) / 2较慢
      (left + right) >> 1较快
      (left + right) >>> 1较快

2 改动版

需求:在有序数组 A A A中查找值 t a r g e t target target,通过左闭右开区间优化边界处理。

  • 如果找到,则返回该值在数组中的索引。
  • 如果未找到,则返回 − 1 -1 1

2.1 算法描述

  1. 初始化:给定一个内含 n n n个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ . . . ≤ A n − 1 A_{0} ≤ A_{1} ≤ A_{2} ≤ ... ≤ A_{n−1} A0A1A2...An1,一个待查值 t a r g e t target target
  2. 设置初始索引:设置 i = 0 i = 0 i=0 j = n j = n j=n左闭右开区间)。
  3. 终止条件:如果 i ≥ j i \geq j ij,结束查找,返回 − 1 −1 1
  4. 计算中间索引:设置 m = ⌊ i + j 2 ⌋ m = \lfloor \frac{i+j}{2} \rfloor m=2i+j
  5. 比较并调整索引
    • 如果 t a r g e t < A m target < A_{m} target<Am,设置 j = m j = m j=m(保持右开特性)。
    • 如果 A m < t a r g e t A_{m} < target Am<target,设置 i = m + 1 i = m + 1 i=m+1
    • 如果 A m = t a r g e t A_{m} = target Am=target,返回索引 m m m

2.2 算法流程图

target < A[m]
A[m] < target
A[m] = target
开始
设置 i=0, j=n
检查 i < j
返回 -1, 结束查找
计算 m = floor((i+j)/2)
比较 target 和 A[m]
设置 j = m
设置 i = m + 1
返回 m, 结束查找

2.3 算法实现

2.3.1 Java实现

/*** 使用二分查找算法查找目标值在数组中的索引* 如果目标值存在于数组中,则返回其索引;如果目标值不存在于数组中,则返回-1* 二分查找算法的前提是输入数组必须是有序的** @param arr    一个有序的整数数组* @param target 目标值* @return 目标值在数组中的索引,如果目标值不存在于数组中,则返回-1*/
public static int binarySearchAlternative(int[] arr, int target) {// TODO 1. 设置初始索引int left = 0; // 左边界int right = arr.length; // 右边界// TODO 2. 循环查找while (left < right) {// TODO 2.1. 计算中间索引int mid = (right + left) >>> 1;// TODO 2.2. 比较中间索引的值与目标值if (arr[mid] == target) {// TODO 2.3. 如果等于目标值,返回中间索引return mid;} else if (arr[mid] < target) {// TODO 2.4. 如果小于目标值,更新左边界left = mid + 1;} else {// TODO 2.5. 如果大于目标值,更新右边界right = mid;}}// TODO 3. 如果循环结束,没有找到目标值,返回-1return -1;
}

2.4 改进点分析

2.4.1 区间定义差异

特性基础版改进版
初始区间左闭右闭 [0, n-1]左闭右开 [0, n)
循环条件left <= rightleft < right
右边界更新方式right = mid - 1right = mid

2.4.2 核心改进原理

  1. 右开区间的优势
    • 更直观的索引计算:初始右边界直接取数组长度,无需-1调整
    • 减少边界条件判断:当right = mid时,天然保持右开特性
  2. 循环次数优化
    • 基础版终止条件为left > right,需要多一次无效循环
    • 改进版终止条件为left == right,精确控制循环次数

2.4.3 数学等价性证明

对于区间长度 L = j − i L = j - i L=ji

  • 基础版每次迭代减少 L / 2 L/2 L/2
  • 改进版每次迭代减少 ⌈ L / 2 ⌉ \lceil L/2 \rceil L/2

两种方式时间复杂度均为 O ( log ⁡ n ) O(\log n) O(logn),但改进版具有更好的空间局部性


3 平衡版

3.1 算法描述

  1. 初始化:给定一个内含 n n n 个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ . . . ≤ A n − 1 A_0≤A_1≤A_2≤...≤A_{n−1} A0A1A2...An1,一个待查值 t a r g e t target target
  2. 设置初始索引:设置左边界 l e f t = 0 left=0 left=0,右边界$ right=n$(左闭右开区间)。
  3. 终止条件:如果 r i g h t − l e f t ≤ 1 right−left≤1 rightleft1,结束查找,返回 − 1 −1 1
  4. 计算中间索引:设置$ mid=\lfloor \frac{i+j}{2} \rfloor$。
  5. 比较并调整索引
    • 如果 t a r g e t < A m i d target<A_{mid} target<Amid,设置 r i g h t = m i d right=mid right=mid
    • 如果 A m i d < t a r g e t A_{mid}<target Amid<target,设置$ left=mid$。
    • 如果 A m i d = t a r g e t A_{mid}=target Amid=target,返回索引$ mid$。

3.2 算法流程图

target < A[mid]
A[mid] < target
A[mid] = target
开始
设置 left=0, right=n
检查 right - left > 1
返回 -1, 结束查找
计算 mid = (left + right) >>> 1
比较 target 和 A[mid]
设置 right = mid
设置 left = mid
返回 mid, 结束查找

3.3 算法实现

3.3.1 Java实现

/*** 使用平衡二分查找算法在已排序的数组中搜索指定目标值* 此方法通过不断缩小搜索范围的一半来高效查找目标值,适用于大规模数据集** @param arr    已排序的整数数组,不包含重复元素* @param target 要搜索的目标值* @return 目标值在数组中的索引;如果目标值不在数组中,则返回-1*/
public static int binarySearchBalanced(int[] arr, int target) {// 1. 设置初始索引int left = 0; // 左边界int right = arr.length; // 右边界// 2. 循环查找while (1 < right - left) {// 2.1. 计算中间索引int mid = (right + left) >>> 1;// 2.2. 比较中间索引的值与目标值if (arr[mid] < target) {// 2.4. 如果小于目标值,更新左边界left = mid;} else {// 2.5. 如果大于目标值,更新右边界right = mid;}}// 3. 最后检查左边界是否为目标值if (arr[left] == target) {// 3.1 如果等于目标值,返回左边界return left;} else {// 3.2 如果没有找到目标值,返回-1return -1;}
}

3.4 改进点分析

3.4.1 区间定义差异

特性基础版平衡版
初始区间左闭右闭$ [0, n-1]$左闭右开$ [0, n)$
循环条件left <= right1 < right - left
右边界更新方式right = mid - 1right = mid

3.4.2 核心改进原理

  1. 右开区间的优势
    • 更直观的索引计算:初始右边界直接取数组长度,无需 -1 调整。
    • 减少边界条件判断:当 right = mid 时,天然保持右开特性。
  2. 循环次数优化
    • 基础版终止条件为 left > right,需要多一次无效循环。
    • 平衡版终止条件为 1 < right - left,精确控制循环次数。

3.4.3 数学等价性证明

对于区间长度 L = j − i L=j−i L=ji

  • 基础版每次迭代减少 L / 2 L/2 L/2
  • 平衡版每次迭代减少 ⌈ L / 2 ⌉ ⌈L/2⌉ L/2

两种方式时间复杂度均为 O ( l o g n ) O(logn) O(logn),但平衡版具有更好的空间局部性。

3.4.4 性能分析

  • 时间复杂度 O ( l o g n ) O(logn) O(logn)。每次迭代将搜索范围缩小一半,因此查找效率非常高。
  • 空间复杂度 O ( 1 ) O(1) O(1)。算法只使用了常量级别的额外空间,适合大规模数据集。

4 对比总结

4.1 区间定义

版本初始区间循环条件右边界更新方式
基础版左闭右闭 [ 0 , n − 1 ] [0, n-1] [0,n1]left <= rightright = mid - 1
改进版左闭右开$ [0, n)$left < rightright = mid
平衡版左闭右开$ [0, n)$right - left > 1right = mid

4.2 中间索引计算

  • 基础版int mid = (left + right) >>> 1;
  • 改进版int mid = (left + right) >>> 1;
  • 平衡版int mid = (left + right) >>> 1;

4.3 终止条件

  • 基础版:当循环结束时,若未找到目标值,返回 − 1 -1 1
  • 改进版:当循环结束时,若未找到目标值,返回 − 1 -1 1
  • 平衡版:循环结束后,检查左边界是否为目标值。

4.4 改进点

  • 基础版:逻辑简单,易于理解。
  • 改进版
    • 使用左闭右开区间,右边界更新更直观。
    • 使用无符号右移运算符 >>> 计算中间值,避免整数溢出。
  • 平衡版
    • 通过左闭右开区间和特定的终止条件,进一步优化循环次数。
    • 最后检查左边界,减少不必要的比较。

4.5 性能分析

  • 时间复杂度
    • 所有版本均为$ O(log n)$,但在实际运行中,平衡版的循环次数更少,性能稍优。
  • 空间复杂度
    • 所有版本均为$ O(1)$,不占用额外空间。

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

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

相关文章

Elasticsearch去分析目标服务器的日志,需要在目标服务器上面安装Elasticsearch 软件吗

Elasticsearch 本身并不直接收集目标服务器的日志&#xff0c;它主要用于存储、搜索和分析数据。要收集目标服务器的日志&#xff0c;通常会借助其他工具&#xff0c;并且一般不需要在目标服务器上安装 Elasticsearch 软件&#xff0c;常见的日志收集方案&#xff1a; Filebeat…

Ajax-介绍

概念: Asynchronous JavaScript And XML&#xff0c;异步的JavaScript和XML. 作用: 数据交换:通过Aiax可以给服务器发送请求&#xff0c;并获取服务器响应的数据 异步交互: 可以在不重新加载整个页面的情况下&#xff0c;与服务器交换数据并更新部分网页的技术, 如:搜索联想、…

论软件架构风格论文

摘要: 本人于2023年1月参与广东省某公司委托我司开发的“虚拟电厂”项目,主要负责系统整体架构设计和中间件选型。该项目为新型电力存储、电力调配、能源交易提供一套整体的软件系统。本文以虚拟电厂项目为例,主要讨论架构风格在本项目中的具体应用,主要包括如下,底层架构…

基于可信数据空间的企业数据要素与流通体系建设(附ppt 下载)

近期&#xff0c;可信数据空间会议召开。大数据系统软件国家工程研究中心总工程师王晨发表了题为《基于可信数据空间的企业数据要素与流通体系建设》主旨演讲。 WeChat Subscription Account【智慧城市指北】&#xff0c;可搜索相关关键字“20250107”&#xff0c;可获取具体获…

idea整合deepseek实现AI辅助编程

1.File->Settings 2.安装插件codegpt 3.注册deepseek开发者账号&#xff0c;DeepSeek开放平台 4.按下图指示创建API KEY 5.回到idea配置api信息&#xff0c;File->Settings->Tools->CodeGPT->Providers->Custom OpenAI API key填写deepseek的api key Chat…

CentOS 7配置samba服务设置文件共享

CentOS 7配置samba服务设置文件共享 一、生成另一个Linux系统&#xff0c;名为Linux-client&#xff0c;作为测试系统。 [rootliunx-client ~]# hostnamectl set-hostname Liunx-client二、如果没有则安装Samba服务&#xff0c;如果已经安装则省略此步。 yum install samba…

Composo:企业级AI应用的质量守门员

在当今快速发展的科技世界中,人工智能(AI)的应用已渗透到各行各业。然而,随着AI技术的普及,如何确保其可靠性和一致性成为了企业面临的一大挑战。Composo作为一家致力于为企业提供精准AI评估服务的初创公司,通过无代码和API双模式,帮助企业监测大型语言模型(LLM)驱动的…

##__VA_ARGS__有什么作用

##__VA_ARGS__ 是 C/C 中宏定义&#xff08;Macro&#xff09;的一种特殊用法&#xff0c;主要用于可变参数宏&#xff08;Variadic Macros&#xff09;的场景&#xff0c;解决当可变参数为空时可能导致的语法错误问题。以下是详细解释&#xff1a; 核心作用 消除空参数时的多余…

增加工作台菜单页面,AI问答应用支持上下文设置,数据库表索引优化,zyplayer-doc 2.4.8 发布啦!

zyplayer-doc是一款适合企业和个人使用的WIKI知识库管理工具&#xff0c;支持在线编辑富文本、Markdown、表格、Office文档、API接口、思维导图、Drawio以及任意的文本文件&#xff0c;专为私有化部署而设计&#xff0c;最大程度上保证企业或个人的数据安全&#xff0c;支持以内…

线性dp-拍照

问题描述 小椒是个摄影爱好者。恰逢班级合照&#xff0c;他受邀帮忙拍照&#xff08;站成一排&#xff09;。这本是一件简单的事&#xff0c;但由于啾啾是个完美主义者&#xff0c;他希望他拍的照片必须符合美学&#xff0c;即存在一个身高较大值&#xff0c;使得较大值无论是…

C++开发(软件开发)常见面试题

目录 1、C里指针和数组的区别 2、C中空指针请使用nullptr不要使用NULL 3、http/https区别和头部结构&#xff1f; 4、有了mac地址为什么还要ip地址&#xff1f;ip地址的作用 5、有了路由器为什么还要交换机&#xff1f; 6、面向对象三大特性 7、友元函数 8、大端小端 …

智能理解 PPT 内容,快速生成讲解视频

当我们想根据一版 PPT 制作出相对应的解锁视频时&#xff0c;从撰写解锁词&#xff0c;录制音频到剪辑视频&#xff0c;每一个环节都需要投入大量的时间和精力&#xff0c;本方案将依托于阿里云函数计算 FC 和百炼模型服务&#xff0c;实现从 PPT 到视频的全自动转换&#xff0…

Qt —— 加载百度离线地图、及简单绘图(附源码)

效果 说明 软件代码已下载了某区域的离线瓦片地图,通过百度离线api进行调用的地图效果。 源码 void PointMapTEST

C++ Attribute 属性说明符

目录 属性说明符 Attribute编译警告相关[[deprecated]][[maybe_unused]][[fallthrough]][[nodiscard]] 可能触发编译优化[[noreturn]][[likely]]、[[unlikely]][[assume]][[carries_dependency]][[no_unique_address]] 属性说明符 Attribute 属性说明符Attribute自C11起&#…

openEuler部署 sysstat工具

查看环境 [rootlocalhost lxm]# cat /etc/os-release NAME"openEuler" VERSION"23.09" ID"openEuler" VERSION_ID"23.09" PRETTY_NAME"openEuler 23.09" ANSI_COLOR"0;31"查看 yum 源 [rootlocalhost lxm]# he…

第八届大数据与应用统计国际学术研讨会(ISBDAS 2025)

重要信息 官网&#xff1a;www.is-bdas.org 时间&#xff1a;2025年2月28-3月2日 地点&#xff1a;中国 广州 主办单位&#xff1a;广东省高等教育学会人工智能与高等教育研究分会 协办单位&#xff1a;北京师范大学人工智能与未来网络研究院、人工智能与大数据科研基地 …

Dify使用

1. 概述 官网:Dify.AI 生成式 AI 应用创新引擎 文档:欢迎使用 Dify | Dify GITHUB:langgenius/dify: Dify is an open-source LLM app development platform. Difys intuitive interface combines AI workflow, RAG pipeline, agent capabilities, model management, ob…

认识O(NlogN)的排序

归并排序 归并排序&#xff08;任何一个递归&#xff09;如果不懂可以画一个树状结构去帮助自己去理解。 核心排序方法为Merger public class 归并排序 {public static void main(String[] args) {int[] arr1 {3, 1, 2, 2, 5, 6};int[] arr2 Arrays.copyOf(arr1, arr1.len…

方波的基波和谐波详细推导,以及matlab验证[电路原理---2]

最近要滤波&#xff0c;从1KHZ 方波中获得正弦波&#xff0c;这让我们要对方波的频谱有具体的了解。虽然楼主一年前刚学过傅里叶。但也是忘的干干净净查阅资料后终于是整理出来。用漂亮的latex打出来了&#xff0c;为自己留存一份记录&#xff0c;也分享给大家学习。 方波的基…

计算机组成原理(3)

计算机组成原理&#xff08;3&#xff09; 存储器层次结构存储器概述存储器分类存储器性能指标 半导体随机存储SRAM和DRAM 存储器层次结构 主存-辅存&#xff1a;实现了虚拟存储系统&#xff0c;解决了主存容量不足的问题&#xff1b; Cache-主存&#xff1a;解决了主存于CPU速…