二分查找(蒟蒻篇)
二分查找定义:
二分查找法,又称折半查找法。它是一个很高的查找方法。时间复杂度是log(N)。
我感觉就是枚举答案的方法,只是比暴力枚举快了很多
但该方法是建立在有序的前提下的,基本思路就是:先找到答案范围 [ l , r ] 的中间值mid,然后用根据题意写的check函数判断更新 l or r 。
反例
有一天小明到图书馆借了 N 本书,出图书馆的时候,警报响了,于是保安把小明拦下,要检查一下哪本书没有登记出借。小明正准备把每一本书在报警器下过一下,以找出引发警报的书,但是保安露出不屑的眼神:你连二分查找都不会吗?于是保安把书分成两堆,让第一堆过一下报警器,报警器响;于是再把这堆书分成两堆…… 最终,检测了 log(N) 次之后,保安成功的找到了那本引起警报的书,露出了得意和嘲讽的笑容。于是小明背着剩下的书走了。 从此,图书馆丢了 N - 1 本书。
所以这个故事告诉我们,二分查找的条件
- 用于查找的内容逻辑上来说是需要有序的
- 在[l,r]的范围内,答案只有一个
模板
Y总的三个模板
两个整数模板
第一个
作用是:找符合条件的最小值
while (l<r)
{
auto mid=l+r>>1;
if (check(mid)) r=mid;
else l=mid+1;
}
第二个
作用是:找符合条件的最大值
while (l<r)
{
auto mid=l+r+1>>1;
if (check(mid)) l=mid;
else r=mid-1;
}
这么说可能有点抽象,找个题来形象说明
这个题是acwing基础课的题 数的范围
这个题的意思:给你一个数列,查询一个数的起始和终止位置,如果没有输出-1 -1.
给你一个例子:1 2 3 3 3 4 4 5
让你查询 3 的起始和终止位置,起始位置就相当于满足>=3的最小位置,而终止位置就是<=3的最大位置。用两个模板跑一下就行
#include <iostream>
using namespace std;
int p[100010];
int main ()
{
int n,m;
cin>>n>>m;
for (int i=0;i<n;i++) scanf ("%d",&p[i]);
for (int i=0;i<m;i++)
{
int k;
scanf ("%d",&k);
int l=0,r=n-1;
while (l<r)
{
int mid=l+r>>1;
if (p[mid]>=k) r=mid;
else l=mid+1;
}
if (p[l]!=k)
{
printf("-1 -1\n");
}
else
{
printf("%d ",l);
int l=0,r=n-1;
while (l<r)
{
int mid=l+r+1>>1;
if (p[mid]<=k) l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
}
}
浮点数模板
假设题目取到小数点后三位,那么r-l到1e-5就够了
while(r-l>1e-5)
{
auto mid=(l+r)/2; //浮点数就不能用向右移来除二
if(check(l)) l=mid;//(r=mid) 这还是要看题的
else r=mid;//(l=mid)
}
相信仔细看的都发现了,整数两个模板的区别,一个为啥第二个mid=l+r+1>>1
,为啥要加一呢,因为当l=r-1的时候,如果check返回的是true,那么如果mid=l+r>>1
,c++整数除法是向下取整,所以l还是=r-1,然后就T了,所以mid=l+r+1>>1
。
例题
给你一个有序的数列,n个数,m个查询,查询一个数是否出现在数列中,如果存在就输出
第一次出现的编号,否则输出-1
板中板的题
#include <iostream>
using namespace std;
int p[1000010];
int main ()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++) scanf ("%d",&p[i]);
while (m--)
{
int l=0,r=n-1;
int k;
scanf ("%d",&k);
while (l<r)
{
int mid=l+r>>1;
if (p[mid]>=k) r=mid;
else l=mid+1;
}
if (p[r]==k) printf("%d ",r+1);
else printf("-1 ");
}
}
STL
c++自带一个简单的二分查找函数 lower_bound()和upper_bound
lower_bound(p,p+n,k)
返的是在p数组从下标0开始后n个数,第一个≥k的第一个数的地址
upper_bound(p,p+n,k)
第一个>k的第一个数的地址
如果你想要下标,以lower_bound
为例,就是lower_bound(p,p+n,k)-p
,这就是第一个≥k的第一个数的下标
题单
这都是一些我做完的题,不理解的地方可以和我讨论
洛谷题
二分查找题单
atcoder上的一个题
E - Dist Max 2