一、核心概念
二分查找是一种高效的查找算法,核心思想是分治 + 区间缩减:将有序数据的查找区间不断折半,每次舍弃不符合条件的一半,仅在剩余区间继续查找,时间复杂度为 O (log n),远优于暴力遍历的 O (n),尤其适用于大数据量(如 n>10⁸)场景。
适用前提 :查找对象必须是有序数列(升序或降序),无序数据无法通过二分法缩小查找范围。
二、基础实现:数组中查找目标值的起止位置
(一)核心工具:双指针 + 中间指针
- 左指针(l):指向查找区间左边界
- 右指针(r):指向查找区间右边界
- 中间指针(mid):通过计算划分区间,有两种核心写法:
- mid = (l + r) / 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_bound和upper_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 默认支持)。
四、注意事项
- 数据有序性:若输入数据无序,C 语言中需手动实现排序函数(如冒泡、快速排序),C++ 可调用
sort(nums.begin(), nums.end()),排序后再使用二分查找。 - 边界处理:C 语言中需注意数组越界问题(如数组为空、numsSize=0 时需提前判断),目标值不存在、目标值在数组两端等场景需额外验证。
- 效率对比:C 语言手动实现二分查找更贴近底层原理,适合理解逻辑;C++ 内置函数代码简洁、不易出错,适合实战开发。
五、总结
关键点回顾
- 二分查找的核心是有序区间 + 折半缩减 ,C 语言手动实现时,找起始位置用
mid=(l+r)/2,找结束位置用mid=(l+r+1)/2,避免死循环; - C++ 内置
lower_bound/upper_bound函数可快速实现二分查找,升序 / 降序数组的参数和功能需区分,auto关键字需 C++11 支持; - 所有二分查找实现后,都需验证结果有效性(如 C 语言判断返回值是否为 - 1,C++ 判断迭代器是否等于
end()),避免数组越界。