二分查找算法

一、算法概述

二分查找,又称折半查找,是一种针对有序集合的查找算法。其核心逻辑是通过不断将查找区间折半,缩小查找范围,最终定位到目标元素(若存在)。该算法的前提条件是查找的数据集必须是有序的(通常为升序或降序排列),且数据元素需支持比较操作;同时,数据集需采用随机访问方式存储(如数组),以便能够快速定位区间中间位置的元素,这也是二分查找无法直接应用于链表等链式存储结构的核心原因。

二、核心思想与原理

二分查找的核心思想是"分而治之",即将一个大的查找问题拆解为多个小的子问题,通过逐步缩小查找范围,降低问题的求解难度。具体原理可分为以下三个步骤:

1. 初始化查找区间

首先定义两个指针(或索引),分别指向有序数据集的起始位置(通常记为left)和终止位置(通常记为right),此时查找区间为[left, right],覆盖整个数据集。

2. 折半查找与范围缩小

计算查找区间的中间位置(记为mid),公式为$$mid = left + (right - left) / 2$$(采用该公式而非$$mid = (left + right) / 2$$,可避免left与right数值过大时出现的溢出问题)。然后将中间位置的元素(nums[mid])与目标元素(target)进行比较,根据比较结果分为三种情况:

  • 若nums[mid] == target:查找成功,返回mid索引,即目标元素在数据集中的位置;

  • 若nums[mid] < target:说明目标元素在中间位置的右侧(因数据集有序),此时将left更新为mid + 1,缩小查找区间至[mid + 1, right];

  • 若nums[mid] > target:说明目标元素在中间位置的左侧,此时将right更新为mid - 1,缩小查找区间至[left, mid - 1]。

3. 终止条件

重复步骤2,不断折半查找并缩小区间,直到出现以下两种情况之一:

  • 找到目标元素,返回其索引;

  • 查找区间缩小至left > right,说明目标元素不在数据集中,返回-1(或其他约定的标识),表示查找失败。

三、算法实现(以Java为例)

二分查找有两种常见的实现方式:迭代实现和递归实现。其中,迭代实现因无需递归栈,空间复杂度更低,且避免了递归深度过大导致的栈溢出问题,在实际开发中应用更为广泛。以下分别给出两种实现的代码示例,并附带详细注释。

1. 迭代实现(推荐)

java 复制代码
/**
 * 二分查找迭代实现
 * @param nums 有序数组(升序)
 * @param target 目标查找元素
 * @return 目标元素索引,未找到返回-1
 */
public static int binarySearchIterative(int[] nums, int target) {
    // 边界校验:数组为空或长度为0,直接返回-1
    if (nums == null || nums.length == 0) {
        return -1;
    }
    int left = 0; // 左指针,指向数组起始位置
    int right = nums.length - 1; // 右指针,指向数组终止位置
    
    // 查找区间为[left, right],当left > right时终止
    while (left <= right) {
        // 计算中间索引,避免left + right溢出
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid; // 找到目标元素,返回索引
        } else if (nums[mid] < target) {
            left = mid + 1; // 目标在右侧,缩小左边界
        } else {
            right = mid - 1; // 目标在左侧,缩小右边界
        }
    }
    // 循环结束仍未找到,返回-1
    return -1;
}

2. 递归实现

java 复制代码
/**
 * 二分查找递归实现
 * @param nums 有序数组(升序)
 * @param target 目标查找元素
 * @param left 左边界索引
 * @param right 右边界索引
 * @return 目标元素索引,未找到返回-1
 */
public static int binarySearchRecursive(int[] nums, int target, int left, int right) {
    // 边界校验:数组为空或长度为0,直接返回-1
    if (nums == null || nums.length == 0) {
        return -1;
    }
    // 终止条件:查找区间无效,未找到目标
    if (left > right) {
        return -1;
    }
    // 计算中间索引
    int mid = left + (right - left) / 2;
    if (nums[mid] == target) {
        return mid; // 找到目标,返回索引
    } else if (nums[mid] < target) {
        // 递归查找右半区间
        return binarySearchRecursive(nums, target, mid + 1, right);
    } else {
        // 递归查找左半区间
        return binarySearchRecursive(nums, target, left, mid - 1);
    }
}

// 递归调用入口(简化用户调用)
public static int binarySearchRecursive(int[] nums, int target) {
    return binarySearchRecursive(nums, target, 0, nums.length - 1);
}

四、关键细节与边界处理​

1. 查找区间的定义(左闭右闭 vs 左闭右开)​

本文上述实现采用的是"左闭右闭"区间([left, right]),即区间包含左右两个边界的元素。这种情况下,循环终止条件为left > right,且更新left和right时需分别设为mid + 1和mid - 1(因为mid位置已被排查,无需再纳入下一轮区间)。​

另一种常见的区间定义是"左闭右开"([left, right)),即左边界包含,右边界不包含。此时循环终止条件为left == right,更新left时设为mid + 1,更新right时设为mid(因right本身不包含在区间内,无需减1)。两种区间定义均可实现二分查找,核心是保持区间定义的一致性,避免混淆。​

2. 中间索引的溢出问题​

若直接使用mid=(left+right)/2计算中间索引,当left和right均为较大整数(如Java中int类型的最大值)时,left + right会超出int类型的取值范围,导致数值溢出,进而计算出错误的mid值。采用mid=left+(right−left)/2,可通过等价变换避免溢出,其本质与前者一致,但安全性更高。​

3. 重复元素的处理​

上述实现适用于无重复元素的有序数组,若数组中存在重复元素,二分查找只能找到其中一个目标元素的索引(不一定是第一个或最后一个)。若需求为找到第一个等于target的元素、最后一个等于target的元素,需对算法进行微调,核心是在找到nums[mid] == target时,不立即返回,而是继续缩小区间,确认边界位置。​

4. 边界值校验​

实现算法时,需优先进行边界校验,如数组为空、数组长度为0、target小于数组最小值或大于数组最大值等情况,可直接返回-1,避免无效循环,提升算法效率。

相关推荐
ADDDDDD_Trouvaille1 小时前
2026.2.21——OJ95-97题
c++·算法
blackicexs1 小时前
第五周第七天
数据结构·算法
海兰2 小时前
ES9.x 银行场景:银行卡可疑交易风控工作流示例
java·elasticsearch·搜索引擎
Drifter_yh2 小时前
「JVM」 深入剖析 JVM 内存结构:从底层原理到线上排查
java·jvm
享誉霸王2 小时前
15、告别混乱!Vue3复杂项目的规范搭建与基础库封装实战
前端·javascript·vue.js·前端框架·json·firefox·html5
莫寒清3 小时前
Java 线程池详解
java·面试
近津薪荼3 小时前
dfs专题10——全排列 II
算法·深度优先
Hcoco_me3 小时前
车载摄像头核心知识点结构化总结
人工智能·深度学习·数码相机·算法·机器学习·自动驾驶
廋到被风吹走3 小时前
安全防护深度解析:敏感信息加密、密码哈希与密钥管理实战
java