二分法之二分查找

本文详细介绍了二分查找算法在处理有序序列中的应用,包括寻找最小值和最大值的情况。强调了二分查找的边界问题,特别是在计算中点值时可能导致的边界偏向。通过两个实例,解释了如何在不同场景下正确使用二分查找,以及在处理连续相等数值时的特殊情况。此外,还讨论了在推荐系统中,如何利用二分查找找到最小不满意度的策略。
摘要由CSDN通过智能技术生成

二分查找:在有序序列中查找元素,为了提高效率节约时间,采用二分查找的方法,与数学中的二分法类似,在给定区间内,从中点一分为二判断其位于左区间还是右区间,然后继续二分,直到最后找到答案为止
二分最重要的是边界问题
二分查找(找最大值最小值)
二分查找例题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()直接求解,
又因为平均值趋向左边界,而且只用了和前面的数的差值,
所以并没有设置无穷大,*/


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Assault boy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值