二分查找
二分法(Binary Search)是一种高效的有序数据集查找算法,核心思想是 “通过不断将查找范围减半,快速缩小目标位置”,其时间复杂度为 O(log n)(n 为数据集长度),远优于线性查找的 O (n)。但需注意:二分法的前提是数据集已按升序或降序排列(通常默认升序)。
使用场景特点
- 搜索空间有序:无论是数组元素、函数输入、解的范围,其 “排序性” 是二分的基础;
- 判断可缩小空间:能通过一次简单判断(如 “中间值与目标的大小”“解是否可行”)将搜索空间直接减半。
实际应用问题场景
- 有序数据结构中的 “查找问题”
- 判断有序数组中是否存在某个目标值,若存在则返回索引
- 定位目标值的第一个出现位置或最后一个出现位置(如去重、统计元素个数)。
- 查找目标值的前驱(小于目标的最大元素)、后继(大于目标的最小元素)
- “单调函数” 的求解问题
- 求解 “函数零点”(方程求解)
- “有序解空间” 的资源优化问题
- 比如查找与一个数相等的另一个数的基数
算法原理
二分法的本质是 “利用有序性排除无效范围”,具体逻辑如下:
- 确定范围:初始查找范围是整个有序数据集(左边界
left
指向第一个元素,右边界right
指向最后一个元素)。 - 计算中点:找到当前范围的中间位置
mid
,取mid
处的元素与目标值target
比较。 - 缩小范围
- 若
mid
处元素 等于target
:找到目标,返回mid
(或记录结果)。 - 若
mid
处元素 小于target
:目标在mid
的右侧(因数组有序),将左边界left
调整为mid + 1
(排除左半部分)。 - 若
mid
处元素 大于target
:目标在mid
的左侧,将右边界right
调整为mid - 1
(排除右半部分)。
- 若
- 循环终止:重复步骤 2-3,直到
left > right
(说明范围为空,目标不存在)。
实现步骤
步骤 1:初始化边界指针
定义两个变量 left
和 right
,分别表示当前查找范围的 “左边界” 和 “右边界”:
步骤 2:循环缩小查找范围(核心步骤)
循环条件:left <= right
(当 left > right
时,范围为空,终止循环)。
- 计算中点索引
mid
: - 比较
arr[mid]
与target
:
步骤 3:处理查找失败
若循环结束(left > right
)仍未返回,说明目标值不在数组中,返回 -1
(或其他约定的 “不存在” 标记)
通用模版
模版1
BinarySearch(int arr[], int target) {// 初始化边界指针int left = 0, right = arr.size() - 1;//循环缩小查找范围(核心步骤)while(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:二分法(找左边界,含重复元素)
适用于有序数组中查找 “第一个≥目标值” 的元素索引(如找第一个大于等于 target 的位置,常用于插入排序定位、重复元素中找首次出现的位置)。
// 函数功能:在升序数组nums中,找第一个≥target的元素索引(左边界)
// 参数:nums-升序有序数组,target-待查找目标值
int binarySearch_LeftBound(const vector<int>& nums, int target) {// 1. 初始化:查找范围覆盖整个数组int left = 0;int right = nums.size() - 1;int result = nums.size(); // 初始化为数组长度(表示未找到时返回插入位置)// 2. 核心循环:缩小范围,定位左边界while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] >= target) {// 找到可能的左边界,继续向左缩小范围(找更靠左的≥target的元素)result = mid; // 暂存当前mid为候选结果right = mid - 1; // 右边界左移,探索左半区} else {// 当前mid值小于target,目标在右半区left = mid + 1;}}// 3. 结果处理:返回最终找到的左边界(或插入位置)return result;
}
模板 3:二分法(找右边界,含重复元素)
适用于有序数组中查找 “最后一个≤目标值” 的元素索引(如找最后一个小于等于 target 的位置,常用于重复元素中找末次出现的位置)。
// 函数功能:在升序数组nums中,找最后一个≤target的元素索引(右边界)
// 参数:nums-升序有序数组,target-待查找目标值
int binarySearch_RightBound(const vector<int>& nums, int target) {// 1. 初始化:查找范围覆盖整个数组int left = 0;int right = nums.size() - 1;int result = -1; // 初始化为-1(表示未找到时返回无效索引)// 2. 核心循环:缩小范围,定位右边界while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] <= target) {// 找到可能的右边界,继续向右缩小范围(找更靠右的≤target的元素)result = mid; // 暂存当前mid为候选结果left = mid + 1; // 左边界右移,探索右半区} else {// 当前mid值大于target,目标在左半区right = mid - 1;}}// 3. 结果处理:返回最终找到的右边界(或-1表示无符合元素)return result;
}
不同情况的不同之处
- 找左边界(找第一个大于等于 target 的位置) :>= 找右边界(找第一个小于等于target的位置):<= ,找哪一边的边界,就向哪一边缩小范围
中位数选取的核心原则
mid
的计算方式(向下 / 向上取整)由边界收缩方向决定:
- 当收缩方向为 向左(
right = mid - 1
)时,用向下取整(默认方式),确保范围有效缩小。
mid = left + (right - left) / 2
- 当收缩方向为 向右(
left = mid + 1
)时,若left
和right
可能相邻(差 1),需用向上取整,避免mid
始终等于left
导致的死循环。
left + (right - left + 1) / 2
本质上,所有调整都是为了保证:每次迭代后,搜索范围的大小必须严格减小,最终能在有限步内结束循环。
若都考虑使用 int mid = left + (right - left) / 2; 则需要像
// 找到可能的右边界,继续向右缩小范围(找更靠右的≤target的元素) result = mid; // 暂存当前mid为候选结果 left = mid + 1; // 左边界右移,探索右半区
用result保存答案,用left = mid + 1 来防止陷入循环
实数二分
实数二分
while((left-right)>eps)
{int ans;mid = left + (right - left) / 2;if (check(mid)){right = mid;} else left = mid;
}