zoukankan      html  css  js  c++  java
  • 二分

    引入


    大家小时候应该玩过这样一个游戏:小伙伴心中想一个1-1000的数,然后另一个小伙伴猜这个数是多少,每次小伙伴回应这个猜的数是比实际的数大还是小,最后肯定能在十次以内找到这个数。在这里就是用到了二分的思想:折半查找,每次找那个数时都取区间的一半,比实际的数要大,就取左边的区间,否则取右边的区间,用代码实现就是:

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int l = 1, r = 1000;  //l表示左端点,r表示右端点,即这个区间可以表示为(0, 1000]
        int res = 45;  //实际的数
        int guess;   //猜测的数
        while(r > l){  //左端点大于等于右端点就停止猜测
            guess = (r+l)/2;   //取中间区间的数,向下取整
            if(guess < res) l = guess+1;  //如果guess比实际的数要小,左端点变为guess的开区间,即变成了(guess, r] 
            else r = guess;
        }
        cout << l << endl;  //输出结果(输出l或r都是一样的)
        return 0;
    }
    

    这段代码有两个值得注意和思考的地方,第一:为什么要用左开右闭去表示这个区间,第二:如果判断条件guess < res改为guess <= res会怎么样?第三:关于计算猜的数,要选向上取整还是向下取整?

    第二个问题比较简单,因为我们把区间设为了(l, r]这种左闭右开的区间,也就是说,当guess == res时,l = guess+1这步操作跳过了实际的我们要的数res,然后就不能找到答案了。可是第一个问题:为什么要用左开右闭呢?我们可以想象一下这种情况:把l = guess+1改成l = guess后(就是把左开右闭改成左闭右闭的区间),res为1000时,也就是这样:

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int l = 1, r = 1000;  
        int res = 1000;  
        int guess;   
        while(r > l){  
            guess = (r+l)/2;   
            if(guess < res) l = guess;  
            else r = guess;
        }
        cout << l << endl;  
        return 0;
    }
    

    运行了一下这个代码,可以发现程序进入了死循环跳不出来,我们查找一下原因:
    假设中间的循环到了这一步:l == 999, r == 1000,然后执行第一步,发现guess == 999,然后符合guess < res(res为1000),执行第二步l = guess;,这时终于发现原因了:l == 999,说明又跳回到第一步,区间根本没有被更新。有人可能会说:这是循环第一行代码,就是guess = (r+l)/2;惹的祸,因为这导致了guess向下取整,不能取到1000,把这个代码改成guess = (r+l+1)/2向上取整,然后再把guess < res改成guess <= res这个代码就能运行了。修改代码之后,运行了一下,对于res == 1000是可以的,但是当res == 1时又不行了,这样改行不通。还有一种方案,有的人可能这样改:

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int l = 1, r = 1000;
        int res;
        cin >> res;
        int guess;
        while(r > l){
            guess = (r+l)/2;
            if(guess == l) break;
            if(guess < res) l = guess;
            else r = guess;
        }
        cout << l+1 << endl;
        return 0;
    }
    

    这种改法在res == 1时答案错误,并不能根本解决上述的问题。所以最好的方法是把区间改成左开右闭的区间。改成左开右闭的区间后,取整方向是否任意的?不是,这里改成左开右闭时,取整一定要向下取整,也就是guess = (r+l)/2,如果是向上取整,即guess = (r+l+1)/2的话就会造成死循环。原因:当res == 1时,中间的while循环有这样的状态:l == 1, r == 2时,guess == (1+2+1)/2 == 2,因为guess < res,所以r = guess,即r == 2,又变回到之前的状态,所以在左开右闭的区间要改成向下取整。相反,如果是左闭右开的区间就要改成向上取整,代码如下:

        while(r > l){
            guess = (r+l+1)/2;
            if(guess > res) r = guess-1;
            else l = guess; 
            //这个代码还可以改成  
            /*
            if(guess <= res) l = guess;
            else r = guess-1;
            */
        }
    
  • 相关阅读:
    ubuntu mint 开机启动项管理
    ubuntu mint 15 编译安装PHP开发环境
    cakephp recursive -1,0,1,2 速查
    git revert all changes
    windows环境变量修改立刻生效的办法
    windows7 mysql install
    ubuntu ll命令
    [Matlab]算法工匠视频1:数字信号处理仿真及实现 第一讲 信号源的产生和滤波1、2
    [Maltab]线性卷积、周期卷积及循环(圆周)卷积
    [C++]自编FFT(递归形式)
  • 原文地址:https://www.cnblogs.com/happy-MEdge/p/10334817.html
Copyright © 2011-2022 走看看