一文讲通:二分查找的边界处理

文章目录

二分查找(Binary Search)是计算机科学中最经典的算法之一,广泛应用于排序数组的查找问题。然而,二分查找的边界处理却常常让人困惑,尤其是关于区间的定义(左闭右开、闭区间、开区间)以及循环条件和更新逻辑的选择。本文将详细讲解这些问题,并推荐一种最佳实践,重点分析为什么要这样做。


一、区间的定义与特点

在二分查找中,区间的定义决定了算法的实现方式。常见的区间定义有以下三种:

  1. 左闭右开区间 :[left, right)
    • 特点 :包含 left,不包含 right。
    • 适用场景 :适合处理数组下标范围为 [0, n) 的情况。
    • 跳出条件 :left == right。
    • 为什么这样定义 :
      • 左闭右开的定义使得右边界 right 始终保持为开区间,避免了数组越界的风险。
      • 逻辑清晰,循环条件 while (left < right) 直接反映了区间是否为空。
      • 更新逻辑简单,right = mid 或 left = mid + 1 都能保证区间逐步缩小。
  2. 闭区间 :[left, right]
    • 特点 :包含 left 和 right。
    • 适用场景:更直观,尤其是数学背景的开发者更容易理解。
    • 跳出条件 :left > right。
    • 为什么这样定义 :
      • 闭区间的定义使得左右边界都参与查找,逻辑上更符合"包含所有可能值"的直觉。
      • 循环条件 while (left <= right) 使得每次都能检查完整的区间。
  3. 开区间 :(left, right)
    • 特点 :不包含 left 和 right。
    • 适用场景:较少使用,因为实现复杂且不直观。
    • 跳出条件 :left + 1 == right。
    • 为什么这样定义 :
      • 开区间的定义适合一些特殊场景,但由于边界不参与查找,逻辑较为复杂,实际应用中较少使用。

二、循环条件与跳出逻辑

循环条件的选择与区间定义密切相关。以下是三种区间定义下的循环条件与跳出逻辑,并解释为什么这样设计:

1. 左闭右开区间 [left, right)

  • 循环条件 :while (left < right)
    • 原因 :
      • 右边界 right 是开区间,不参与查找。
      • 当 left == right 时,区间为空,循环结束。
  • 跳出条件 :left == right,此时区间为空。
  • 更新逻辑 :
    • 如果目标值在左半部分:right = mid
      • 原因 :mid 仍然可能是目标值,因此保留 mid,缩小右边界。
    • 如果目标值在右半部分:left = mid + 1
      • 原因 :mid 已经被检查过,排除 mid,缩小左边界。

2. 闭区间 [left, right]

  • 循环条件 :while (left <= right)
    • 原因 :
      • 右边界 right 是闭区间,参与查找。
      • 当 left > right 时,区间为空,循环结束。
  • 跳出条件 :left > right,此时区间为空。
  • 更新逻辑 :
    • 如果目标值在左半部分:right = mid - 1
      • 原因 :mid 已经被检查过,排除 mid,缩小右边界。
    • 如果目标值在右半部分:left = mid + 1
      • 原因 :mid 已经被检查过,排除 mid,缩小左边界。

3. 开区间 (left, right)

  • 循环条件 :while (left + 1 < right)
    • 原因 :
      • 开区间不包含边界,当 left + 1 == right 时,区间为空,循环结束。
  • 跳出条件 :left + 1 == right,此时区间为空。
  • 更新逻辑 :
    • 如果目标值在左半部分:right = mid
      • 原因 :mid 仍然可能是目标值,因此保留 mid,缩小右边界。
    • 如果目标值在右半部分:left = mid
      • 原因 :mid 已经被检查过,排除 mid,缩小左边界。

三、完整代码示例

以下是三种区间定义的完整代码实现:

1. 左闭右开区间 [left, right)

cpp 复制代码
int binarySearch(vector<int>& nums, int target) {
    int left = 0, right = nums.size(); // [left, right)
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1; // 排除 mid
        } else {
            right = mid; // 保留 mid
        }
    }
    return -1; // 未找到
}

2. 闭区间 [left, right]

cpp 复制代码
int binarySearch(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1; // [left, right]
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1; // 排除 mid
        } else {
            right = mid - 1; // 排除 mid
        }
    }
    return -1; // 未找到
}

3. 开区间 (left, right)

cpp 复制代码
int binarySearch(vector<int>& nums, int target) {
    int left = 0, right = nums.size(); // (left, right)
    while (left + 1 < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid; // 保留 mid
        } else {
            right = mid; // 保留 mid
        }
    }
    return -1; // 未找到
}

四、总结与建议

在实现二分查找时,建议严格按照区间的定义来选择循环条件和更新逻辑:

  • 左闭右开区间 :使用 while (left < right),更新逻辑为 left = mid + 1 或 right = mid。
  • 闭区间 :使用 while (left <= right),更新逻辑为 left = mid + 1 或 right = mid - 1。
  • 开区间 :使用 while (left + 1 < right),更新逻辑为 left = mid 或 right = mid。

为什么推荐左闭右开区间?

  • 逻辑清晰:右边界始终不参与查找,避免数组越界。
  • 易于理解 :while (left < right) 直接反映了区间是否为空。
  • 广泛使用 :许多标准库(如 C++ 的 std::lower_bound)采用左闭右开的定义。

强行混用循环条件和区间定义会导致逻辑复杂化,增加代码的理解难度和错误风险。因此,推荐大家按照区间定义的方式去写循环条件,并确保跳出循环的逻辑与区间定义一致。

相关推荐
C++ 老炮儿的技术栈4 小时前
机器人的坐标,像 XYZ 的坐标值,是用单精度浮点还是用双精度浮点?
c语言·c++·人工智能·单片机·c
zaizai10074 小时前
STL深度剖析2
java·c++·算法
幻智星科技4 小时前
FPGA纯硬件UDP桥接:微秒级响应新范式
c++·fpga开发·硬件架构
数智奔流4 小时前
MiniMax的低价攻势,撞上“背题”疑云
前端·javascript·算法
青少儿编程课堂4 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:位运算实战——二进制权限与最大异或解析
c++·python·算法·bfs·信息学竞赛
广州智造5 小时前
Radioss中破坏模型FAILTAB1的详细介绍
数据库·人工智能·算法·机器学习·仿真软件·radioss
青少儿编程课堂5 小时前
2026蓝桥杯青少组图形化真题:校园飞机大战解析(全国青少年信息素养·电子学会图形化考级)
c++·python·算法·bfs·信息学竞赛
开开心心就好5 小时前
证件照排版打印工具,免费可自定义尺寸
服务器·开发语言·算法·随机森林·pdf·音视频·散列表
lisw055 小时前
导数与机器学习优化!
算法·机器学习·数学建模
CodeYp20195 小时前
第16讲:set 和 map 的使用
开发语言·c++·set·map