Java 二分查找算法详解及通用实现模板案例示范

news2024/10/23 21:36:36

1. 引言

二分查找(Binary Search)是一种常见的搜索算法,专门用于在有序数组或列表中查找元素的位置。它通过每次将搜索空间缩小一半,从而极大地提高了查找效率。相比于线性查找算法,二分查找的时间复杂度为 O(log n),在处理大规模数据时非常高效。

2. 二分查找算法的基本原理

二分查找要求搜索目标的数据集合是有序的。通过将数组从中间位置一分为二,逐步缩小搜索范围,最终找到目标元素或确定目标元素不存在。算法的关键在于,每次比较时,将要查找的元素与中间元素进行比较,并据此决定继续向左半部分或右半部分搜索。

3. 二分查找适用问题类型

二分查找算法适用于以下类型问题:

  1. 在有序数组中查找元素的索引
  2. 寻找有序数组中满足条件的边界值,如第一个大于等于目标值的元素或最后一个小于等于目标值的元素。
  3. 确定某一条件下的最优解,例如在单调递增或递减函数中查找某一阈值。

4. 二分查找通用实现模板

4.1 标准二分查找模板

首先,我们来看最基础的二分查找算法实现。这个模板适用于在有序数组中查找某个目标值,并返回其索引。

public class BinarySearch {
    /**
     * 标准二分查找,查找目标值的索引
     * @param arr 已排序的数组
     * @param target 要查找的目标值
     * @return 目标值的索引,如果不存在则返回 -1
     */
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] == target) {
                return mid;  // 找到目标值,返回索引
            } else if (arr[mid] < target) {
                left = mid + 1;  // 在右半部分继续查找
            } else {
                right = mid - 1;  // 在左半部分继续查找
            }
        }

        return -1;  // 没有找到目标值
    }

    public static void main(String[] args) {
        int[] sortedArray = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
        int target = 7;
        int result = binarySearch(sortedArray, target);

        if (result != -1) {
            System.out.println("目标值 " + target + " 在索引 " + result);
        } else {
            System.out.println("目标值 " + target + " 不存在于数组中");
        }
    }
}

解释

  • leftright 分别指向数组的左右边界。
  • 每次通过 mid = left + (right - left) / 2 计算中间索引。
  • 如果中间值 arr[mid] 恰好等于目标值,则返回 mid 作为目标值的索引。
  • 如果中间值小于目标值,表示目标值在右侧,则更新左边界 left = mid + 1
  • 如果中间值大于目标值,表示目标值在左侧,则更新右边界 right = mid - 1
4.2 二分查找的变体模板

除了标准的二分查找,还有一些变体问题常常需要我们在查找过程中找到特定的边界值。

查找第一个大于等于目标值的元素
public static int findFirstGreaterOrEqual(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    int result = -1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (arr[mid] >= target) {
            result = mid;  // 记录当前 mid 作为可能的结果
            right = mid - 1;  // 继续在左半部分查找
        } else {
            left = mid + 1;  // 在右半部分查找
        }
    }

    return result;
}
查找最后一个小于等于目标值的元素
public static int findLastLessOrEqual(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    int result = -1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (arr[mid] <= target) {
            result = mid;  // 记录当前 mid 作为可能的结果
            left = mid + 1;  // 继续在右半部分查找
        } else {
            right = mid - 1;  // 在左半部分查找
        }
    }

    return result;
}

5. 二分查找的时序图

为了更好地理解二分查找的执行过程,我们可以通过时序图展示二分查找的各个步骤。时序图展示了从开始到查找到目标值(或确认目标值不存在)的过程。

时序图

在这里插入图片描述

6. 案例分析:电商系统中的二分查找

在电商系统中,二分查找可以被应用在很多场景中,例如:

  1. 商品价格查询:在大量已排序的商品价格列表中快速找到某一商品价格,或者确定某个商品价格在列表中的位置。
  2. 库存检查:通过二分查找,可以快速确定某一商品的库存量,或者查找某类商品的库存是否充足。
  3. 订单历史记录查询:在按时间排序的订单历史记录中,快速查找某一时间段的订单。
案例 1:价格查询

假设电商系统有一份已排序的商品价格列表,用户需要查询某个商品的价格是否存在,或者找到第一个高于某个价格的商品。

public class PriceQuery {
    public static int findFirstPriceGreaterOrEqual(int[] prices, int targetPrice) {
        int left = 0;
        int right = prices.length - 1;
        int result = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (prices[mid] >= targetPrice) {
                result = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        int[] prices = {100, 200, 300, 400, 500, 600, 700};
        int targetPrice = 350;
        int index = findFirstPriceGreaterOrEqual(prices, targetPrice);

        if (index != -1) {
            System.out.println("第一个大于等于 " + targetPrice + " 的价格是: " + prices[index]);
        } else {
            System.out.println("没有找到大于等于 " + targetPrice + " 的商品价格");
        }
    }
}
案例 2:订单历史记录查询

在按日期排序的订单记录中,用户希望快速找到某一天的订单记录,或者查找最近的一笔订单。

public class OrderQuery {
    public static int findClosestOrder(int[] orders, int targetDate) {
        int left = 0;
        int right = orders.length - 1;
        int closest = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (orders[mid] == targetDate) {
                return mid;  // 找到精确匹配的订单
            } else if (orders[mid] < targetDate) {
                closest = mid;  // 记录当前最近的订单
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return closest;  // 返回最近的订单
    }

    public static void main(String[] args) {
        int[] orderDates = {20220101, 20220201, 20220301, 20220401, 20220501};
        int targetDate = 20220315;
        int index = findClosestOrder(orderDates, targetDate);

        if (index != -1) {
            System.out.println("最近的订单日期是: " + orderDates[index]);
        } else {
            System.out.println("没有找到合适的订单");
        }
    }
}

7. 二分查找的常见问题

虽然二分查找看似简单,但在实现过程中容易遇到一些问题。

  1. 越界问题:由于计算中间索引时使用 (left + right) / 2,当 leftright 很大时,可能会导致整型溢出。为此,推荐使用 left + (right - left) / 2 计算中间索引。
  2. 终止条件不清晰:二分查找的循环条件应为 left <= right,而不是 left < right,否则可能会漏掉最后一个可能的索引位置。

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

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

相关文章

利用Docker搭建一套Mycat2+MySQL8一主一从、读写分离的最简单集群(保姆教程)

文章目录 1、Mycat介绍1.1、mycat简介1.2、mycat重要概念1.3、Mycat1.x与Mycat2功能对比1.2、主从复制原理 2、前提准备3、集群规划4、安装和配置mysql主从复制4.1、master节点安装mysql8容器4.2、slave节点安装mysql8容器4.2、配置主从复制4.3、测试主从复制配置 5、安装mycat…

【TDA】持续同调的矢量化方法

Topological deep learning: a review of an emerging paradigm 持续同调与深度学习协同下的3D点云分类_苏潇 Applications of Topology to Data Analysis Computational Topology: An Introduction 持续同调对于特征的方式有条形码PB和持续图表PD两种形式,它们包含了持续同调的…

Qt开发技巧(十八):新窗口控件用智能指针,将一些配置类变量封装起来,Qt窗体的Z序叠放,子窗体的释放,Qt中的事件发送,Qt的全局头文件

继续讲一些Qt开发中的技巧操作&#xff1a; 1.新窗口控件用智能指针 通过对Qt自带Examples的源码研究你会发现&#xff0c;越往后的版本&#xff0c;越喜欢用智能指针QScopedPointer来定义对象&#xff0c;这样有个好处就是用的地方只管new就行&#xff0c;一直new下去&#xf…

2025 年最佳的 Retool 开源替代方案

自 2017 年推出以来&#xff0c;Retool 已迅速成为开发者的热门选择。 Retool 的出现&#xff0c;填补了当时企业在快速构建内部工具上的空白。传统的应用开发往往需要耗费大量时间和资源&#xff0c;尤其是对于定制的内部业务应用。而 Retool 提供了一个灵活的平台&#xff0…

element设置时间和日期框早于现在的时间和日期禁用

效果: 今日此时此刻之前的日期、时间禁止选用&#xff0c;切换日期和时间为“2024-10-19 00:00:00"&#xff0c;再切换为”2024-10-18 00:00:00"时&#xff0c; 会给form.time默认赋值为今日此时此刻&#xff08;日期时间少于今日此时此刻则重新赋值&#xff09; 安…

datax连接池泄漏问题排查及解决

1、问题描述 频繁调用datax服务&#xff08;从oracle同步到mysql&#xff09;出现报错&#xff0c;获取不到连接 oracle读取时报错信息 "errorMessage": "Code:[DBUtilErrorCode-10], Description:[连接数据库失败. 请检查您的 账号、密码、数据库名称、IP、…

print_hex_dump调试内核,嘎嘎香

本文首发于我的公众号 皮塞作坊 专注于干货分享&#xff0c;号欢迎大家关注,二维码文末可以扫。 公众号: 使用print_hex_dump调试内核/驱动&#xff0c;太香了 最近在验证芯片功能的过程中发现了一个好用的内核调试接口&#xff0c;print_hex_dump&#xff0c;除了直接打印16…

【AIGC】关键词智能匹配:AI驱动的RAG知识库检索技术全解析

随着大语言模型的快速发展&#xff0c;AI在知识获取和生成中的应用越发广泛。RAG&#xff08;Retrieval-Augmented Generation&#xff09;模型通过结合外部知识库&#xff0c;提升了生成文本的质量与准确性&#xff0c;而关键词搜索是其关键组成部分。本文将深入探讨AI如何通过…

【java】数组(超详细总结)

目录 一.一维数组的定义 1.创建数组 2.初始化数组 二.数组的使用 1.访问数组 2.遍历数组 3.修改数据内容 三.有关数组方法的使用 1.toString 2. copyOf 四.查找数组中的元素 1.顺序查找 2.二分查找binarySearch 五.数组排序 1.冒泡排序 2.排序方法sort 六.数组逆置…

LabVIEW伺服压机是如何实现压力位移的精度?

LabVIEW伺服压机通过精确的压力和位移控制&#xff0c;实现了高精度的压装操作。为了达到这种精度&#xff0c;系统通常依赖于多个硬件和软件模块的协同工作&#xff0c;包括伺服电机、压力传感器、位移传感器以及LabVIEW的实时控制和数据处理功能。以下是LabVIEW伺服压机如何实…

Linux修改npm的镜像源为淘宝镜像

起因&#xff1a;使用官方镜像源下载软件包速度太慢 1.查看npm当前镜像源命令 npm get registry 执行结果 2.还原为官方镜像源命令 npm config set registry https://registry.npmjs.org/ 3.修改为淘宝镜像命令 npm config set registry https://registry.npmmirror.com …

【你也能从零基础学会网站开发】 SQL Server结构化查询语言数据操作应用--DML篇 delete语句数据删除操作的使用方法

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;程序猿、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 delete介绍与语…

关于武汉芯景科技有限公司的限流开关芯片XJ6288开发指南(兼容SY6288)

一、芯片引脚介绍 1.芯片引脚 二、系统结构图 三、功能描述 1.EN引脚控制IN和OUT引脚的通断 2.OCB引脚指示状态 3.过流自动断开

NC 单据模板自定义项 设置参照,比如部门参照、自定义参照等

NC 单据模板自定义项 设置参照&#xff08;自定义参照&#xff09; 一、如图下图&#xff0c;NC 单据模板自定义项 设置自定义参照&#xff1a; 1、选择需要设置参照的自定义字段&#xff0c;选择高级属性页签&#xff0c;在类型设置中&#xff0c;数据类型选择参照信息&#…

使用JUC包的AtomicXxxFieldUpdater实现更新的原子性

写在前面 本文一起来看下使用JUC包的AtomicXxxxFieldUpdater实现更新的原子性。代码位置如下&#xff1a; 当前有针对int&#xff0c;long&#xff0c;ref三种类型的支持。如果你需要其他类型的支持的话&#xff0c;也可以照葫芦画瓢。 1&#xff1a;例子 1.1&#xff1a;普…

Maven的进阶

目录 一、pom.xml文件 二、坐标 2.1 坐标的概念 2.2 坐标的意义 2.3 坐标的含义 2.4 在IDEA中查看项目的坐标 三、依赖 3.1 依赖的意义 3.2 依赖的使用 3.3 第三方依赖的查找使用方法 3.4 依赖的范围 3.5 依赖传递和可选依赖 3.5.1 依赖传递 3.5.2 依赖范围对传…

【前端】如何制作自己的网站(7)

以下内容接上文。 结合图片的超链接 将img元素作为内容&#xff0c;放在a元素中。即可为图片添加一个超链接。 例如右边的代码&#xff0c;点击头像就会打开“aboutme.html“。 点击右边的图片试试&#xff5e; 两个非文本元素——图片与超链接。 从现在开始&#xff0…

蘑菇书(EasyRL)学习笔记(1)

1、强化学习概述 强化学习&#xff08;reinforcement learning&#xff0c;RL&#xff09;讨论的问题是智能体&#xff08;agent&#xff09;怎么在复杂、不确定的环 境&#xff08;environment&#xff09;里面去最大化它能获得的奖励。如下图所示&#xff0c;强化学习…

【Petri网导论学习笔记】Petri网导论入门学习(七) —— 1.5 并发与冲突

导航 1.5 并发与冲突1.5.1 并发定义 1.14定义 1.15 1.5.2 冲突定义 1.17 1.5.3 一般Petri网系统中的并发与冲突定义 1.18一般网系统中无冲撞概念阻塞&#xff08;有容量函数K的P/T系统&#xff0c;类似于冲撞&#xff09;一般Petri网中并发与冲突共存情况 1.5 并发与冲突 Petr…