二分查找:在有序序列中查找元素,为了提高效率节约时间,采用二分查找的方法,与数学中的二分法类似,在给定区间内,从中点一分为二判断其位于左区间还是右区间,然后继续二分,直到最后找到答案为止
二分最重要的是边界问题
二分查找(找最大值最小值)
二分查找例题1
输入n(n≤10^6) 个不超过 10^9的单调不减的(就是后面的数字不小于前面的数字)非负整数 a1,a2,…,an,然后进行m(m≤10^5) 次询问。对于每次询问,给出一个整数 q(q≤10^9),要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 -1
从题目中得知递增数列中求某数第一次出现的编号换句话说就是求最小值
#include <cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define maxn 1000010
#define inf 1e12
using namespace std;
int n,m,q,a[maxn];
int find (int c){
int l=1,r=n+1;
while (l<r) //二分条件,因为最后一定会变成l=r,从而退出循环
{ int mid=l+(r-l)/2;
if(a[mid]>=c) r=mid;
else l=mid+1;
}
if(a[l]==c) //找到了就输出该位置否则输出-1
return l;
else
return -1;
}
//
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
while(m--)
{int c;
cin>>c;
int ans=find (c);
cout<<ans<<' ';
}
return 0;
}
有两个需要注意的地方:
1.
int mid=l+(r-l)/2;
*这个求中点值的地方,实际上是求平均值,
但是注意这里求平均值的数都是int型也就是说最后取得是平均值的整数部分,
小数部分会直接舍掉,那么这就会牵扯到一个边界问题,
就是取整数部分会让这个数接近左边界,
而远离右边界,这在某些题中会涉及它边界的问题
2.
if(a[mid]>=c) r=mid;
else l=mid+1;
*注意此时要注意一种情况是当有连续的相同数字时要找它第一次出现的位置,也就是最靠前的
此处查找的条件是找到大于等于目标值的数然后往左找最小值,那此时右边界r要等于中点mid,
*如果是找最大值,那么判断条件就要变成找到小于等于目标值的数往右找最大值,那此时左边界l等于mid
例题2:
例题2:
现有 m(m≤100000) 所学校,每所学校预计分数线是 ai(ai≤10^6)。有 n(n≤100000) 位学生,估分分别为 bi(bi≤10^6)。
根据n位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。
//读题我们知道是要求学校预计分数和学生估分的相差最小的值,那么学生预估的分数和学校预测分数接近肯定差值就最小,所以我们找到第一个大于或估分值的录取分数线然后再找它前面那个就是小于估分值的分数线,在比较这两个各自和估分值的差数,取最小的那个就行
但是这里出现了上面说的一个问题,因为这里是比较前后两个数就会出现以下两种情况,一是假设最后所有的录取分数线都比你的估分值高,可能录取不上,第二个是你的估分值比所有学校的录取分数线都高,你可以随便选学校,这两种情况
因此:
#include <cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define maxn 100010
#define inf 1e10 //先设一个无穷大的数用inf表示
using namespace std;
int m,n,tot=0,k=1;
long long a[maxn]; //用来存放学校录取分数线
int main() {
cin>>m>>n;
for(int i=1;i<=m;i++)
cin>>a[i];
sort(a+1,a+1+m);//因为没说是递增的可能乱序,要先排序从小到大,
a[0]=-inf; /*(1)*/
for(int i=1;i<=n;i++)
{
int c;
cin>>c;
int l=1,r=m; //左右边界
while (l<r) //最后肯定是l=r
{
int mid=l+(r-l)/2;
if(a[mid]>=c) r=mid;
else l=mid+1;
}
tot+=min(abs(a[l]-c),abs(a[l-1]-c)); / *(2)*/
cout<<tot;
return 0;
}
/*(1)问题就在这里,这里在数组的前面设了一个负无穷大就是无穷小,
就像前面说的,因为是判断左右两个数,
当所有学校分数线都大于估分值时会判断第一所学校和这个无穷小的差值,
所以才在前面设了个无穷小否则无法判断a[l-1]-c的差值,
因此结果当然是和第一所学校的差值最小,*/
/* (2) 这个地方用的是求绝对值函数abs()直接求解,
又因为平均值趋向左边界,而且只用了和前面的数的差值,
所以并没有设置无穷大,*/