原理
基本原理:
二分查找(Binary Search)是一个在有序空间中快速定位答案的经典算法。
它的基本思想是: 每次把当前区间“对半”切开,保留可能包含答案的一半,丢弃另一半; 重复这个过程,直到区间足够小或者找到答案。
二分的本质: 一个有单调性的搜索空间中寻找满足条件的点。
比如:
有序数组里找元素。
最小化最大值,最大化最小值问题。
某个判断条件随着参数变化是单调的(true->false 或 false->true),就可以二分。
简言之: 单调性 + 确定区间收缩方式 → 能二分
二分的常见应用
- 标准数值二分:在一个升序数组里找一个数,找不到就返回最近的位置。
- 条件判断二分:定义一个条件
check(x)。check(x)随着 x 的变化是单调的。二分最小的/最大的符合条件的 x。 - 浮点数二分:如果是连续数轴上找一个实数,比如找根号、找最优值,就需要浮点二分。
- 二分答案:有些问题本身没有排序,但答案有单调性。
- 三分:如果目标函数是单峰的(先增后减或先减后增),可以用三分法找最优点。
什么时候能用二分?
| 条件 | 解释 |
|---|---|
| 1. 有序数据结构 | 比如升序数组,按位置二分。 |
| 2. 单调性质 | 比如随着某个参数变化,check结果单调变化。 |
| 3. 答案有界且单调 | 比如最小化最大值、最大化最小值。 |
| 4. 函数极值问题 | 如果是单峰,可以用三分。 |
板子
整数二分的板子代码:
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;//r=mid不需要加1
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1; //其实就是l=mid 这里加1
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
整数二分的板子代码有两种常见的形式,一种的终止条件是基于区间的即l==r是区间内的满足条件的就一个就是答案,另一种的终止条件是l+1==r即终止条件是找到了满足条件的分界线。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],n,m;
int main(void)
{
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
while(m--)
{
int x; cin>>x;
int l=0,r=n-1;
while(l<r)
{
int mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
int l1=0,r1=n-1;
while(l1<r1)
{
int mid=(l1+r1+1)/2;
if(a[mid]<=x) l1=mid;
else r1=mid-1;
}
if(a[l]!=x) cout<<-1<<" ";
else cout<<l<<" ";
if(a[l1]!=x) cout<<-1<<'\n';
else cout<<l1<<'\n';
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],n,m;
int main(void)
{
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
while(m--)
{
int x; cin>>x;
int l=-1,r=n;//注意这里,初始条件是l的右边和r的左边是满足条件的
while(l+1<r)
{
int mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid;
}
int l1=-1,r1=n;
while(l1+1<r1)
{
int mid=(l1+r1)/2;
if(a[mid]<=x) l1=mid;
else r1=mid;
}
if(a[r]==x) cout<<r<<" ";
else cout<<-1<<" ";
if(a[l1]==x) cout<<l1<<'\n';
else cout<<-1<<'\n';
}
return 0;
}
浮点数二分的板子代码:
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
习题
标准数值二分
二分答案
最小化最大值问题
浮点数二分