来自算法蒟蒻的学习笔记,希望能够用尽可能萌新的语言来帮到大家。
个人理解:二分是一个很灵活的思想,打模板的难度并不大,难在思考二分什么东西和处理特殊情况。
定义(引用OI WIKI):
二分查找(英语:binary search),也称折半搜索(英语:half-interval search)、对数搜索(英语:logarithmic search),是用来在一个有序数组中查找某一元素的算法。
时间复杂度:O(logN)
二分搜索的使用条件:单调/可处理为单调
(对于你想要二分的东西和问题之间的关系)
实现原理分析(以 P2249 【深基13.例1】查找 为例):
我们假设我们要查询的数字一定在所提供的数组内(这样可以简化流程到只关注二分本身)。
如果我们从头到尾一个个寻找,直到遇到我们所需的数字再停止,那么保不齐我们要找的数字会出现在数组的很后面,那么这样搜寻一次的复杂度就是O(N)。
在其庞大查询量之下,这种算法一定会TLE,那么有什么办法能简化这个搜寻的路径吗?
答案是二分。
对于数组内的每一个数,他们和所要查询的数字无非就两种关系:
小于这个数,或者大于等于这个数 ,且可以找到一个明确的分界点,使得在分界点左侧的数都小于它,右侧的数都大于或等于它。
从图像上看样例测试点,是这样的:
当我们想查找“3”时:
当我们想查找“7”时:
当我们想查询“15”时:
可以发现:对于同一个数组而言,从数组头部到某一个位置是满足(或不满足)题目条件,而剩余区间正好相反。
# 这体现了二分的单调性需求
我们可以发现,数组是单调递增的,我们没有必要搜遍整个数组,只需要根据区间的中位数和查询的数字的大小关系来做出判断并不断折半缩小范围,最终一定会找到那唯一的解。
那么,我们可以用二分来找到小于这个数的最后一个位置 l ,和大于等于这个数的第一个位置 r 。
实现代码如下:
//本代码仅在以上的假设环境内生效,不能作为实际题解
#include<bits/stdc++.h>
using namespace std;
int n,m,q,a[1000005];
bool check(int x){
if(x>=q) return 1;
else return 0;
}
int main(){
cin>>n>>m;
for (int i=1; i<=n; i++) cin>>a[i];
for (int i=1; i<=m; i++){
cin>>q;
int l = 0, r = n+1;
while (l+1<r){
int mid = (l+r)/2;
if (check(mid)) r = mid;
else l = mid;
}
cout<<r<<endl;
}
return 0;
}
考虑搜不到的特殊情况后的正解,可以参考这篇题解,写法略有不同,不再赘述。
更简洁的代码方式,可以参考这篇题解,使用了C++自带的lower_bound函数(原理也是二分查找)。
二分常见的模板:
bool check(int x){
//根据题目写对应判断式
if(判断条件)return 1;
else return 0;
}
int main(){
//...
int l = minn - 1;
//minn为所有可取值的最小值。
int r = maxn + 1;
//maxn为所有可取值的最大值。
while(l+1<r){
int mid=(l+r)/2;
if(check(mid)) {l = mid;}
else {r = mid;}
}
cout<<l;
//或者 cout<<r; 具体根据题目。
}
在一些特殊的情况下,我们可以通过对数组调用c++自带的函数来实现更加简易的二分:
int num[]={5,6,7,8,9,10,11,12};
int l = lower_bound(num, num + 6, 7) - num; //返回数组中第一个大于或等于被查数的下标
int r = upper_bound(num, num + 6, 7) - num; //返回数组中第一个大于被查数的下标
注意:
无论是lower_bound还是upper_bound,其函数内都有三个元素,分别是:
num:搜索的起始位置。
num+6:搜索的结束位置+1。(计算机语言特有的左闭右开.jpg)
7:要搜索的值。
因为lower_bound和upper_bound都是返回地址,故减去num(数组的首地址),来得到正确下标。
细节点:
1.l=minn-1;
防止因为二分而取不到最小值点的问题。
2.r=maxn+1;
防止因为二分而取不到最大值点的问题。
3.while(l+1<r)
防止退出时,l与r相等的问题。
实践帮助
根据我个人的学习经历,可以通过以下题目对二分算法进行一定的熟悉(同时也体现二分可以很难也可以很简单的特性)。
首先便是洛谷的二分题单:
此外,本人还有幸写过(或观望过)一些有意思的二分,希望能帮到大家:
P4058 [Code+#1] 木材
Travel Card
P6174 [USACO16JAN] Angry Cows S
[AGC006D] Median Pyramid Hard
一些小的见解:
如果不知道二分什么,可以试试对待求的值来进行二分(当然也不完全)。
欢迎各位在评论区留言、讨论。若有谬误,欢迎纠正。