二分算法(小白整理)

二分是什么?

咱们先思考一个问题,如果让你猜一个整数,每次猜会告诉你猜大了还是猜小了,范围是1~100,最坏情况下需要多少次呢?

没错,是100次,但是从1猜到100进度是不是非常的慢?这时候我们发现还有一个条件,每次猜完能知道大小,那能不能减少我们猜的次数呢?

很多人喜欢折中的解决办法,因为这样对两边都公平。这里我们也可以尝试一下折中。

第一次,我们猜50,猜小了,剩下的范围为51~100

第二次,我们猜75,猜小了,剩下的范围为76~100

第三次,我们猜88,猜小了,剩下的范围为89~100

第四次,我们猜94,猜小了,剩下的范围为95~100

第五次,我们猜98,猜小了,剩下的范围为99~100

第六次,我们猜99,猜小了,剩下的范围为100~100

可以看到,我们一直折中的猜,无论遇到的是大的情况还是小的情况,很少的次数就可以猜到结果,不信的同学可以尝试一下哦~

这种折中的方法就是二分!

二分能干啥?

通过刚刚的计算,我们明显感觉到,二分和暴力相比,计算量大幅下降,节约时间。

但是它也有限制条件,如果猜数字时不告诉我们是大是小,那么我们便无法使用二分方法,所以用二分解决的问题需要有顺序和范围,可以是大小、高低、远近等等。

我们能感觉到二分节约了时间,但是并不清晰,那我们扩大一下猜数字的范围再试试

比如1~10000

暴力枚举的时间复杂度是O(n) 也就是从1猜到n 猜10000次

通过二分方法,每次可以去掉当前一半的范围,那就是10000一直除2,log₂10000次 = 大概14次

一对比,我们的复杂度从O(n)级别降到O(logn)级别,有着巨大的优化啦

二分怎么实现?

第一步:取中值

在我们的思考中,折中就是取最大值和最小值中间的数,那该如何告诉计算机呢?

我们设左边界为 left = 1,右边界 right = 10000,中间值 mid = (left + right) / 2

这样计算便取到中间值了。

第二步:验证结果

取到中间值后,我们得去判断是猜大了还是猜小了,我们可以写一个 check(mid) 函数

对于我们猜测的 mid,我们可以把它代入原环境,看这个值是否符合我们的目标区间。

第三步:缩小范围

习惯上我们只分两个方向,即 包含答案的区间 和 不包含答案的区间。

对于猜数字,我们设 左边不包含猜中的情况,右边包含猜中的情况

如果猜小了,那么我们让 left = mid +1,舍去左半边,舍去边界。

猜大了或者猜中了 right = mid,舍去右半边,不舍去边界。

重复这三步,直到获取答案:

这样操作,我们便能得到新的一个新的区间范围,也满足有序条件,可以继续使用二分方法

最终我们会获得 left = right = mid 这样的情况,即是我们要的答案。

在此贴上c++的代码:

#include<bits/stdc++.h>
using namespace std;

int guess = rand()%10000+1;

bool check(int mid){
    if(mid < guess) return false;
    else if(mid >= guess) return true;
}

int main(){
    int left = 1, right = 10000;
    while(left < right){
        int mid = (left + right) / 2;
        if(check(mid)) right = mid;
        else left = mid + 1;
    }
    cout<<left;
}

二分的其他使用方法

更新问题 猜数字=>猜数字区间:

区间1~10000,每次问数字会回复三种情况:猜大了,猜小了,在所猜区间中。

二分有很强的灵活性,假设让我们猜一个区间呢?是否也可以使用二分?

答案是可以的,我们可以灵活的改变 第二步中的 check条件 和 第三步中的变化条件。

比如先猜区间的左端点,猜小了为不满足的情况,猜大了和猜中区间为满足的情况,是否能使用二分操作呢? check 检查后,left 和 right 该如何变化呢?猜右端点又有什么变化呢?

这里留给大家思考,我在这里贴出我的一种处理方法:

#include<bits/stdc++.h>
using namespace std;

int guess_left = rand() % 10000 + 1;
int guess_right = guess_left + rand() % (10000 - guess_left);

bool check_left(int mid){    //判断左端点
    if(mid < guess_left) return false;
    else if(mid >= guess_left) return true;
}

bool check_right(int mid){    //判断右端点
    if(mid > guess_right) return false;
    else if(mid <= guess_right) return true;
}

int main(){
    int ans_left, ans_right;
    int left = 1, right = 10000;
    //猜左端点
    while(left < right){
        int mid = (left + right) >> 1;
        if(check_left(mid)) right = mid;
        else left = mid + 1;
    }
    ans_left = left;
    //猜右端点,初始化左右区间
    left = 1, right = 10000;
    while(left < right){
        int mid = (left + right) >> 1;
        if(check_right(mid)) left = mid;
        else right = mid - 1;
    }
    ans_right = left;
    cout<<"数字区间为:["<<ans_left<<","<<ans_right<<"]"<<endl;
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哑笙

如果觉得有帮助,就给我个赞叭。

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

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

打赏作者

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

抵扣说明:

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

余额充值