引入
大家小时候应该玩过这样一个游戏:小伙伴心中想一个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;
*/
}