二分查找学习笔记

一、核心概念

二分查找是一种高效的查找算法,核心思想是分治 + 区间缩减:将有序数据的查找区间不断折半,每次舍弃不符合条件的一半,仅在剩余区间继续查找,时间复杂度为 O (log n),远优于暴力遍历的 O (n),尤其适用于大数据量(如 n>10⁸)场景。

适用前提 :查找对象必须是有序数列(升序或降序),无序数据无法通过二分法缩小查找范围。

二、基础实现:数组中查找目标值的起止位置

(一)核心工具:双指针 + 中间指针

  • 左指针(l):指向查找区间左边界
  • 右指针(r):指向查找区间右边界
  • 中间指针(mid):通过计算划分区间,有两种核心写法:
    1. mid = (l + r) / 2:偏向左侧的中间位置(偶数长度数组时取左半部分末尾)
    2. mid = (l + r + 1) / 2:偏向右侧的中间位置(偶数长度数组时取右半部分开头)

(二)查找起始位置(第一个≥目标值的元素)

1. 实现逻辑
  • 区间划分:左半区(<target)、右半区(≥target)
  • 指针移动规则:
    • 若 a [mid] ≥ target:目标在左半区,r = mid(保留 mid 所在的右半区左边界)
    • 若 a [mid] < target:目标在右半区,l = mid + 1(舍弃 mid,直接跳转至下一位)
  • 关键细节:循环条件为 l < r(避免 l=r 时进入死循环),循环结束后需验证结果是否为目标值。
2. 示例代码(C 语言)
复制代码
#include <stdio.h>

// 查找第一个大于等于target的元素下标,不存在则返回-1
int findFirstPosition(int nums[], int numsSize, int target) {
    int l = 0, r = numsSize - 1;
    // 循环条件:左指针小于右指针
    while (l < r) {
        // 偏向左侧的mid写法,适配起始位置查找
        int mid = (l + r) / 2;
        if (nums[mid] >= target) {
            // 目标在左半区,收缩右边界
            r = mid;
        } else {
            // 目标在右半区,收缩左边界
            l = mid + 1;
        }
    }
    // 循环结束后l==r,验证是否找到目标值
    if (nums[l] == target) {
        return l;
    } else {
        return -1;
    }
}

int main() {
    // 测试用例:有序数组
    int nums[] = {1, 3, 5, 5, 5, 7, 9};
    int numsSize = sizeof(nums) / sizeof(nums[0]);
    int target = 5;
    int pos = findFirstPosition(nums, numsSize, target);
    if (pos != -1) {
        printf("目标值%d的第一个位置:%d\n", target, pos); // 输出:2
    } else {
        printf("未找到目标值\n");
    }
    return 0;
}

(三)查找结束位置(最后一个≤目标值的元素)

1. 实现逻辑
  • 区间划分:左半区(≤target)、右半区(>target)
  • 指针移动规则:
    • 若 a [mid] > target:目标在左半区,r = mid - 1(舍弃 mid,直接跳转至上一位)
    • 若 a [mid] ≤ target:目标在右半区,l = mid(保留 mid 所在的左半区右边界)
  • 关键细节:mid 必须用 (l + r + 1)/2,否则会陷入死循环。
2. 示例代码
复制代码
#include <stdio.h>

// 查找最后一个小于等于target的元素下标,不存在则返回-1
int findLastPosition(int nums[], int numsSize, int target) {
    int l = 0, r = numsSize - 1;
    while (l < r) {
        // 偏向右侧的mid写法,适配结束位置查找
        int mid = (l + r + 1) / 2;
        if (nums[mid] > target) {
            // 目标在左半区,收缩右边界
            r = mid - 1;
        } else {
            // 目标在右半区,收缩左边界
            l = mid;
        }
    }
    if (nums[l] == target) {
        return l;
    } else {
        return -1;
    }
}

int main() {
    int nums[] = {1, 3, 5, 5, 5, 7, 9};
    int numsSize = sizeof(nums) / sizeof(nums[0]);
    int target = 5;
    int pos = findLastPosition(nums, numsSize, target);
    if (pos != -1) {
        printf("目标值%d的最后一个位置:%d\n", target, pos); // 输出:4
    } else {
        printf("未找到目标值\n");
    }
    return 0;
}

三、C++ 内置二分函数

C++ STL 提供了lower_boundupper_bound函数,可直接实现二分查找,无需手动写循环,大幅提升编码效率。

(一)核心函数说明

函数 功能(升序数组) 参数格式 返回值
lower_bound 查找第一个≥target 的元素 (数组首地址,数组尾地址 + 1, target) 对应元素的迭代器
upper_bound 查找第一个 > target 的元素 (数组首地址,数组尾地址 + 1, target) 对应元素的迭代器

(二)升序数组示例代码

复制代码
#include <iostream>
#include <algorithm> // 必须包含头文件
#include <vector>
using namespace std;

int main() {
    vector<int> nums = {1, 3, 5, 5, 5, 7, 9};
    int target = 5;

    // 1. lower_bound:查找第一个≥5的元素
    // auto需C++11及以上版本,等价于vector<int>::iterator
    auto it1 = lower_bound(nums.begin(), nums.end(), target);
    if (it1 != nums.end()) {
        cout << "第一个≥" << target << "的元素:" << *it1 
             << ",下标:" << it1 - nums.begin() << endl; // 输出:5,下标2
    }

    // 2. upper_bound:查找第一个>5的元素
    auto it2 = upper_bound(nums.begin(), nums.end(), target);
    if (it2 != nums.end()) {
        cout << "第一个>" << target << "的元素:" << *it2 
             << ",下标:" << it2 - nums.begin() << endl; // 输出:7,下标5
    }

    // 3. 特殊情况:查找不存在的元素(如10)
    auto it3 = upper_bound(nums.begin(), nums.end(), 10);
    if (it3 == nums.end()) {
        cout << "未找到大于10的元素" << endl;
    }

    return 0;
}

(三)降序数组示例代码

降序数组使用内置函数时,需额外传入greater<int>()作为比较规则,函数功能会反转:

  • lower_bound:查找第一个≤target 的元素

  • upper_bound:查找第一个 < target 的元素

    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;

    int main() {
    // 降序数组
    vector<int> nums = {9, 7, 5, 5, 5, 3, 1};
    int target = 5;

    复制代码
      // 查找第一个≤5的元素(降序)
      auto it1 = lower_bound(nums.begin(), nums.end(), target, greater<int>());
      cout << "第一个≤" << target << "的元素:" << *it1 
           << ",下标:" << it1 - nums.begin() << endl; // 输出:5,下标2
    
      // 查找第一个<5的元素(降序)
      auto it2 = upper_bound(nums.begin(), nums.end(), target, greater<int>());
      cout << "第一个<" << target << "的元素:" << *it2 
           << ",下标:" << it2 - nums.begin() << endl; // 输出:3,下标5
    
      return 0;

    }

(四)编译器要求

  • 使用auto关键字接收迭代器时,需将编译器版本切换为C++11 及以上(如 Dev-C++ 在 "工具 - 编译选项" 中设置,VS 默认支持)。

四、注意事项

  1. 数据有序性:若输入数据无序,C 语言中需手动实现排序函数(如冒泡、快速排序),C++ 可调用sort(nums.begin(), nums.end()),排序后再使用二分查找。
  2. 边界处理:C 语言中需注意数组越界问题(如数组为空、numsSize=0 时需提前判断),目标值不存在、目标值在数组两端等场景需额外验证。
  3. 效率对比:C 语言手动实现二分查找更贴近底层原理,适合理解逻辑;C++ 内置函数代码简洁、不易出错,适合实战开发。

五、总结

关键点回顾

  1. 二分查找的核心是有序区间 + 折半缩减 ,C 语言手动实现时,找起始位置用mid=(l+r)/2,找结束位置用mid=(l+r+1)/2,避免死循环;
  2. C++ 内置lower_bound/upper_bound函数可快速实现二分查找,升序 / 降序数组的参数和功能需区分,auto关键字需 C++11 支持;
  3. 所有二分查找实现后,都需验证结果有效性(如 C 语言判断返回值是否为 - 1,C++ 判断迭代器是否等于end()),避免数组越界。
相关推荐
daidaidaiyu2 小时前
一文入门 Android NDK 开发
c++
Ethernet_Comm2 小时前
从 C 转向 C++ 的过程
c语言·开发语言·c++
难得的我们2 小时前
C++与区块链智能合约
开发语言·c++·算法
diediedei2 小时前
C++编译期正则表达式
开发语言·c++·算法
夏鹏今天学习了吗3 小时前
【LeetCode热题100(97/100)】二叉搜索树中第 K 小的元素
算法·leetcode·职场和发展
炽烈小老头3 小时前
【 每天学习一点算法 2026/01/26】缺失数字
学习·算法
小桃酥ღ3 小时前
[力扣每日习题][1339]. 分裂二叉树的最大乘积 2026.01.07
算法·leetcode·职场和发展
Tianwen_Burning4 小时前
c++ release下的debug
c++
谦宸、墨白4 小时前
从零开始学C++:二叉树进阶
开发语言·数据结构·c++