全文目录
- 前言
- 二分查找动图演示
- 代码模板
- ❗️ 使用哪个模板问题 ❗️
- mid为何+1问题
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。它要求元素具有有序性,或者具有跟有序性类似的性质,能够不断地缩小查找范围。
查找操作主要有一下几点:
1、确定需要查找的值,设立基准值(一般是中间值),设立判断条件,判断基准值是否满足某种性质
2、以基准值为边界,划分左右两个区间
3、根据判断条件是否成立,分析结果可能存在的区间,更新查找范围
4、重复以上操作,直到区间不存在
(左边界大于右边界)
或查找到需要的值时,查找结束
我们这里以整数的有序序列为例,演示二分查找的代码和需要注意的事项。整数二分的代码分为两种情况:
- 判断条件成立时新区间需要更新在左半区间,即
[l, mid]
- 判断条件成立时新区间需要更新在右半区间,即
[mid, r]
需要注意的是不管哪种情况,条件成立都需要将取到mid
,因为mid
可能为答案
bool check(int x) {/* ... */} // 检查答案是否满足某种性质
// 查找左区间
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:条件成立,新区间在[l, mid]
int SearchLeft(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 查找右区间
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:条件成立,新区间在[mid, r]
int SearchRight(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
决定使用哪个模板,是根据check(mid)
来决定的:
check(mid)
就是判断答案在左半区间还是右半区间
1、如果
check(mid)
是判断答案在左半区间,使用SearchLeft
模板(mid
不需要+1):如果在条件成立,说明答案在左半区间,也就是
[l, mid]
,新区间为[l, mid]
,区间更新方式为r = mid
。
如果条件不成立,说明答案在右半区间,也就是[mid + 1, r]
,新区间为[mid + 1, r]
,区间更新方式为l = mid + 1
。
2、如果
check(mid)
是判断答案在右半区间,使用第二个模板(mid
需要+1):如果在条件成立,说明答案在右半区间,也就是
[mid, r]
,新区间为[mid, r]
,更新条件为l = mid
。
如果条件不成立,说明答案在左半区间,也就是[l, mid - 1]
,新区间为[l, mid - 1]
,更新条件为r = mid - 1
。
在这两种情况中,区间划分好理解,不就是一左一右、一加一减嘛。但是在mid
为何需要加1可能会让很多人疑惑,我在学习的时候也是迷糊了好久。现在将从大佬那里总结到的经验分享一下:
1、在SearchLeft
中 mid
为何不需要 +1
设左右两个边界只相差1,即
l = r - 1
时,如果mid + 1
,mid
向下取整 ,即mid == r
,如果条件刚好成立的话,就会导致更新完区间后r
还是等于mid
,等于没有更新,这时候就是一个死循环了。所以mid
不需要 +1,进行向上取整,让mid == l
,破坏死循环的条件。
2、在SearchRight
中mid
为何需要 +1
设左右两个边界只相差1,即
l = r - 1
时,当mid
不 +1 时mid
是向上取整的,所以mid == l
,如果条件刚好成立的话,就会导致更新完 区间后l
还是等于mid
,等于没有更新,这时候就是一个死循环了。所以需要mid+1
进行向下取整,让mid == r
,破坏死循环的条件。
大佬原话:
对于mid + 1与否我觉得是为了让区间平分
mid = left + right >> 1; 这里mid是上中位数
mid = left + right + 1 >> 1; 这里mid是下中位数
如果取left = mid, 即[mid, right], 则mid取下中位数才能平分区间
如果取right = mid, 即[left, mid], 则mid取上中位数才能平分区间