浅析upper_bound和lower_bound

本文介绍了如何使用二分查找在有序数组中查找第一个大于等于目标值v的元素的下标,详细分析了upper_bound和lower_bound的实现原理,并探讨了STL中这两个函数的源码。同时,讨论了在STL中采用半开半闭区间的好处,并给出了返回最后一个大于等于v的元素下标的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分查找又称为折半查找,就是通多对有序数列不断二分,将复杂度降至O(logn),二分查找尽管可以用递归实现,但是一般把二分写成非递归的。

问题引入

有一个有序数组,要求针对的某个元素v,如果它存在于数组中,返回他的第一次出现的位置,如果不存在,返回一个下标i,在此处插入v,其他元素后移,数组仍然有序,说白点就是找到第一个>=v的元素的下标
比如 数组a[10] = {1,2,2,2,4,5,6,7,8,9},v = 2时返回下标[1],v = 3时返回下标[4]。
所以核心代码如下:

int lower_bound(int *a,int x,int y,int v)//返回第一个出现的位置,若不存在在此处插入v后仍然有序
//x表示开始查找的开始位置,y表示结束位置即返回值 ∈【x,y】
    while(x < y)//结束循环的条件 左端点>=右端点
    {
        m = x+(y-x)/2;//根据分治思想要尽可能平均分开两部分查找范围
        if(a[m] >= v) y = m;//出现相等元素继续向前找
        else x = m + 1;//针対找过了的情况,像回退一个
    }
    return x;

进一步分析这段代码是如何放缩的:
因为我们要查找的是第一个>=v的元素,针对a[m]和v的关系可分为如下三种:
1.a[m] = v,我们要找>=v的第一个元素,所以下标可能出现在m左侧,也可能就是m(m-1下标的元素可能小于v了),所以查找区间变为[x,m],就是y = m。
2.a[m] > v,我们要找>=v的第一个元素,所以下标可能出现在m后面,但是可能是m,所以[x,m],即y = m。
3.a[m] < v,我们要找**>=v**的第一个元素,此时已将<了,只能往前面找,所以[m+1,y],即x = m+1。

#include <iostream>
using namespace std;

//找到第一个>=key的元素的下标
int lower_bound(int *a,int x,int y,int v)//返回第一个出现的位置,若不存在在此处插入v后仍然有序
{
    int m;
    while(x < y)
    {
        m = x+(y-x)/2;
        if(a[m] >= v) y = m;//出现相等元素继续向前赵
        else x = m + 1;//针対找过了的情况
    }
    return x;
}
int main()
{
    int a[10] = {1,2,2,2,3,3,7,8,9,10};
    int v = 2;
    cout << lower_bound(a,0,9,v) << endl;
    cout << upper_bound(a,0,9,v) << endl;
    return 0;
}

运行结果如下
1
4

upper_bound

实际上这里upper_bound已经实现了STL中的同名函数,下面看一下STL中的源码:
与上面思想相同,不同的是采用first+half的方法来寻找合适位置,当half = 0时候就锁定了唯一的值,为了方便理解源码下面一段是我改编的

template <class _ForwardIter, class _Tp, class _Distance>
_ForwardIter __lower_bound(_ForwardIter __first, _ForwardIter __last,
                           const _Tp& __val, _Distance*) 
{
  _Distance __len = 0;
  distance(__first, __last, __len);
  _Distance __half;
  _ForwardIter __middle;
 
  while (__len > 0) {
    __half = __len >> 1;
    __middle = __first;
    advance(__middle, __half);
    if (*__middle < __val) {
      __first = __middle;
      ++__first;
      __len = __len - __half - 1;
    }
    else
      __len = __half;
  }
  return __first;
}
int lower_bound_STL(int *a,int first,int last,int v)
{
    int half; //这个half就是查找区间的一半,就是这个二分的核心
    int len = last - first;//初始时查找区间是整个区间
    int middle;//都是通过first + 区间长度来筛选的
    while(len > 0)//当len = 0的时候就是我们要找的值了
    {
        half = len >> 1;
        middle = first+half;
        if(a[middle] < v)
        {
            first = middle + 1;
            len = len - half -1;//与上面分析类似就是在[m+1,y]中查找
        }
        else len = half;// [x,m]中查找
    }
    return first;
}

至此,lower_bound已经结束了,upper_bound与它类似,查找的是第一个大于v的元素的下标(地址),只要做如下改变

if(a[m] <= v) x = m+1;
else y = m;

思想也是一样的,根据><=确定查找区间。

延伸

1.在STL中一般采用半开半闭区间,这样的好处是只要相减就可以得到v的个数。
eg: 数组a[10] = {1,2,2,2,4,5,6,7,8,9},v = 2时返回下标[1],v = 3时返回下标[4],那么2的个数就是4-1=3。
2.类似我们还可以写出返回最后一个>=v的元素的下标:

int bigandequal(int x,int y,int *a)
{
    int m;
    while(x < y)
    {
        m = x+(y-x+1)/2;
        if(a[m] <= v) x = m;
        else y = m-1;
    }
    return x;
}

例题(二分答案)

题目

#include <iostream>
#define N 200100
using namespace std;
int t,n,k;
int judge(int m,char *light)
{
    int cnt = 0;
    for(int i = 0;i<n;)
    {
        if(light[i] == '0')
        {
            i++;
            continue;
        }
        else
        {
            cnt++;
            i += m;
        }

    }
    return cnt;
}
int binsearch(char *light)
{
    int x = 1,y = n;
    int m;
    while(x < y)
    {
        m = (x+y)/2;
        if(judge(m,light) <= k) y = m;
        else  x = m+1;
    }
    return x;
}
int main()
{
    cin >> t;
    while(t--)
    {
        char light[N];
        cin >> n >> k;
        for(int i = 0;i<n;i++)
        {
            cin >> light[i] ;
        }
        cout << binsearch(light) << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值