举个例子,A 心里想一个1-1000之间的数,让 B 来猜,B 可以问问题,A 只能回答是或否。怎么猜才能问的问题次数最少?
最笨的方法就是从1开始问,是1吗?是2吗?…是999吗?如果是1,问一次就得到答案了,如果A想的是1000,需要1000次才能得到答案,平均要问500次。
还有一种问法,从中间开始问,一次将范围缩小一半。大于500吗?回答是,大于750吗?回答否,大于625吗? …每次缩小猜测范围到上次的一半,只需要 10次一定可以猜到 A 心里想的数。这种方法就用到了二分的思想。
1、案例一
问题描述:写一个函数 BinarySeach,在包含 size 个元素的、从小到大排序的 int 数组 a 里查找元素 p,如果找到,则返回元素下标,如果找不到,则返回-1。要求复杂度 O(log(n))
显而易见应该用二分查找去做,代码也非常简单,直接上代码
#include<iostream>
using namespace std;
int BinarySeach(int a[], int size,int p)
{
int left = 0; //查找区间的左端点
int right = size - 1; //查找区间的右端点
int mid;
while (left<=right) //如果查找区间不为空,继续查找
{
mid = left + (right - left) / 2;//取查找区间正中元素的下标
if (p == a[mid])
return mid;
if (p > a[mid])
left = mid + 1; //设置新的查找区间的左端点
else
right = mid - 1; //设置新的查找区间的右端点
}
return -1; //找不到
}
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
int len = sizeof(a) / sizeof(a[0]);
cout << BinarySeach(a, len, 8)<<endl;
return 0;
}
2、问题变化
问题描述:写一个函数 LowerBound,在包含 size 个元素的、从小到大排序的 int 数组 a 里查找比给定整数 p 小的,下标最大的元素。找到则返回其下标,找不到则返回-1
问题分析:看到数组是从小到大排列的,就想到了二分法。每次将数组分一半,如果中间元素大于等于 p,继续在左侧区间寻找,如果中间元素小于p,记录这个位置,在 p 左侧。当区间长度变为0时,该位置就是 p 左侧的第一个元素。
#include<iostream>
using namespace std;
int LowerBound(int a[], int size, int p)
{
int left = 0; //查找区间的左端点
int right = size - 1; //查找区间的右端点
int mid,lastpos=-1; //lastpos为到目前为止找到的最优解
while (left<=right) //如果查找区间不为空,继续查找
{
mid = left + (right - left) / 2;//取查找区间正中元素的下标
if (a[mid]>=p)
right = mid - 1;
else
{
lastpos = mid;
left = mid + 1;
}
}
return lastpos;
}
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9,10,11,12};
int len = sizeof(a) / sizeof(a[0]);
cout << LowerBound(a, len, 8) << endl;
return 0;
}
有一个细节需要注意,在取中间元素的时候,最好使用 int mid = L+(R-L)/2; 如果使用 int mid = (L+R)/2; 在计算 (L+R) 时可能结果过大,超过了 int 的表示范围,溢出了。
3、总结
理解二分法的思想,二分法的变形考法。