二分查找

我们知道,一般从n个元素中查找的时间复杂度是O(n),而二分查找每一次查找都会减少一半的元素,因此时间复杂度是O(logn),注,这里的log是以2为底的。我们现在不讨论二分查找的原理,毕竟大家已经耳熟能详了,我们只讨论它的代码、扩展和应用。

我们先来温习一下严格递增序列下二分查找的代码:

#include <bits/stdc++.h>

using namespace std;

int binarySearch(int A[],int left,int right,int x){
    int mid;
    while(left<=right){
        mid=left+(right-left)/2;//注意这里用mid=(left+right)/2在数据过大时会溢出,故采用此方法。
        if(A[mid]==x)return mid;
        else if(A[mid]>x)right=mid-1;
        else left=mid+1;
    }
    return -1;//查找不成功返回-1
}

int main(){
    int A[]={1,2,3,4,5,6,7};
    printf("%d",binarySearch(A,2,6,5));
}

这是经典的二分查找代码,只需注意我注释的部分,其余读者想必早已滚瓜烂熟,无需我多言。二分法可以递归实现,但大多数时候我们都采用的是非递归的方法。

下面讨论一个问题:
对于一个元素可重复的递增序列A[],求出序列中第一个大于等于x的元素的位置L和第一个大于x的位置R,这样元素x在序列中的存在区间就是一个左闭右开区间[L,R)。

先求第一个大于等于x的元素的位置L:
只需对二分查找的经典代码进行一下修改即可,我会在注释中标明哪里修改了:

#include <bits/stdc++.h>

using namespace std;

int binarySearch(int A[],int left,int right,int x){
    int mid;
    while(left<right){//修改
        mid=left+(right-left)/2;
        if(A[mid]>=x)right=mid;//if语句变了
        else left=mid+1;

    }
    return left;//left即L
}

int main(){
    int A[]={1,2,3,5,5,6,7};
    printf("%d",binarySearch(A,2,6,5));
}

解释几个要点:
1 关于while(left<right),因为这种情况无需考虑“元素x是否存在”,即便x不存在,返回的也是“如果x存在,它应该存在的位置”。
2 最后的返回值可以是left,也可以是right。
3 二分的初始区间是[0,n]

接下来解决如何求序列中第一个大于x的元素的位置。和上面类似,我们直接看代码。

#include <bits/stdc++.h>

using namespace std;

int upperbound(int A[],int left,int right,int x){
    int mid;
    while(left < right){
        mid=(left + right) / 2;
        if(A[mid] > x){
            right=mid;
        }
        else {
            left=mid+1;
        }
    }
    return left;
}
int main(){
    int A[]={1,2,3,5,5,6,7};
    printf("%d",upperbound(A,2,6,5));
}

上面的两个代码都在解决一个相似的问题:**寻找有序序列中满足特定条件的元素位置。**大部分二分法都是用于解决此类问题的。
下面给出固定模板:

#include <bits/stdc++.h>

using namespace std;

//解决“寻找有序序列中特定元素的位置”问题的固定模板,二分区间为闭区间[left,right]
//寻找第一个满足条件的元素的位置
int solve(int left, int right){
    int mid;
    mid = (left + right) / 2;
    if(条件成立){
        right = mid;
    }
    else {
        left = mid + 1;
    }
    return left;
}

另外,若想找到最后一个满足条件的元素的位置,只需先找到第一个不满足条件的元素的位置,再将该位置减1即可。

二分法拓展

看这样一个问题:给定一个定义在[L,R]上的单调递增函数f(x),求方程f(x)=0的根。

假设精度要求为1e-5

下面给出该问题的代码,递减函数同理,这里不再赘述。

const double eps = 1e-5;//精度
double f(double a){
    return ...;//函数
}
double solve(double L, double R){
    double left=L,right=R,mid;
    while(right-left>eps){
        mid = ( left + right) / 2;
        if(f(mid) > 0){
            right=mid;
        }
        else{
            left=mid;
        }
    }
    return mid;//mid值为根
}

装水问题、木棒切割问题也很重要,这里不多介绍,有兴趣的读者可以自行上网查阅。

快速幂

先来看一个问题:
给定三个整数a、b、m(均在10e9下),求a^b%m
这道题可以简单粗暴地用循环写出来,时间复杂度是O(b),但是我们今天要看怎么用二分法来处理b较大时的情况。

这里采用快速幂的方法。
1.如果b是奇数,那么ab = a*ab-1
2.如果b是偶数,那么ab = ab/2 *ab/2
这样时间复杂度为O(logb),下面给出递归的代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
LL binarypow(LL a,LL b,LL m){
    if(b==0)return 1;//如果b=0,返回1
    if(b&1==1)//相当于执行速度更快的b%2!=1
        return binarypow(a,b-1,m)%m;
    else{
        LL mul=binarypow(a,b/2,m);//防止时间复杂度变为O(b)
        return mul*mul%m;
    }
}

注意两点:如果a开始有可能大于等于m,那么在进入函数之前就要让a对m进行取模
如果m为1,则可直接判结果为0,无需进入函数来计算。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值