二分

引入


大家小时候应该玩过这样一个游戏:小伙伴心中想一个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;
        */
    }
posted @ 2019-01-29 17:00  MrEdge  阅读(341)  评论(0编辑  收藏  举报