数据结构与算法入门指南
二分指在有序的数组中快速查找某个值,这里不得不提到我们生活中经常遇到的一个小游戏:猜数字,给定一个范围,让你猜某个数字,每次猜错就告诉你是大了还是小了。
按照二分的思想,猜数字时我们会直接猜中间的数字,比如给出范围1~1000,我们会直接猜500,再看是大了还是小了再折半缩小需要猜的范围,这样就可以用最少的次数猜到数字。
例如我们要在1~1000的范围内猜799,注意:默认下取整
猜(1 + 1000) / 2 = 500:小了
猜(500 + 1000) / 2 = 750:小了
猜(750 + 1000) / 2 = 875:大了
猜(750 + 875) / 2 = 812:大了
猜(750 + 812) / 2 = 781:小了
猜(781 + 812) / 2 = 796:小了
猜(796 + 812) / 2 = 804:大了
猜(796 + 804) / 2 = 800:大了
猜(796 + 800) / 2 = 798:小了
猜(798 + 800) / 2 = 799:完毕
虽然例子比较长,但应该能换来清晰直观的理解。
可以发现每次都是(左端点 + 右端点) / 2
,再看是大了还是小了去限制左端点和右端点,如果是大了就缩小右端点的值,如果是小了就扩大左端点的值。
lower_bound
与upper_bound
C++自带的lower_bound
与upper_bound
这两个函数,可以帮我们用二分在数组中快速查找某个值。
注意:数组必须是有序的(这两个函数默认数组是从小到大排序的),如果要在无序数组中查找则需要先排序,可以看看 数据结构与算法入门指南 - 排序
lower_bound(begin, end, x)
返回在(begin, end-1)区间中第一个大于等于x的值的地址。
upper_bound(begin, end, x)
返回在(begin, end-1)区间中第一个大于x的值的地址。
可以看到lower_bound
与upper_bound
的区别就是一个可以等于x,可以只能大于x。
如果x不在数组中,这两个函数会返回什么呢?
- 如果x小于数组中的最小值,会返回begin,因为一直往左找,找到边界就停下来了。
- 如果x大于数组中的最大值,会返回end,因为一直往右找,找到边界就停下来了。
- 如果x的范围在(begin, end)中,
lower_bound
会返回与upper_bound
一样的结果,因为找不到相等的值了。
那么我们来看看实际用法吧
int A[10] = { 1,3,6,9,12,14,18,22,34,54 };
int index = lower_bound(A, A + 10, 9) - A;//获取符合值的下标 用返回元素的地址减去首地址就是下标了
int value = A[index]; //获取值 也可以一步到位 value = *lower_bound(A, A + 10, 9)
如何判断x是否在数组内呢?
int A[10] = { 1,3,6,9,12,14,18,22,34,54 };
int index = lower_bound(A, A + 10, 11) - A; //获取符合值的下标
//判断下标是否出界和值是否相同即可
if (index < 10 && A[index] == 11) puts("11在A中");
else puts("11不在A中");
如果数组是从大到小排序的呢?就不能用lower_bound
与upper_bound
了嘛?当然不是,这两个方法还有第四个参数,传入方法或匿名函数,可以自定义排序。
int A[10] = { 99,71,66,62,54,52,38,35,26,1 };
int value = *lower_bound(A, A + 10, 66, greater<int>()); //value = 66
整数二分模板
有些可以用二分法来快速查找值的情况,但无法直接用lower_bound
与upper_bound
怎么办呢?就例如结构体数组中要查找某个元素的属性符合该值,那么我们就只能手写二分了。
由于边界问题会导致死循环,所以某些情况下需要+1
或-1
。
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用
int bsearch(int l, int r)
{
while (l < r)
{
int mid = (l + r) / 2;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用
int bsearch(int l, int r)
{
while (l < r)
{
int mid = (l + r + 1) / 2;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
看个小栗子🌰,其实就是把lower_bound
实现了,动动手,看能不能写出upper_bound
的效果(查找大于x的第一个值)?
int A[10] = { 5,6,13,16,25,31,35,46,58,77 };
int x = 35; // 要查找的值
int l = 0, r = 9;
while (l < r)
{
int mid = (l + r) / 2;
if (A[mid] >= x) r = mid;
else l = mid + 1;
}
cout << l << ' ' << A[l]; // 6 35
浮点数二分模板
这里需要提一下的就是,判断两个浮点数是否相等的方法一般是不会用==
来判断的,会有误差,那么我们只需要判断两个浮点数差的绝对值非常小就行了。1e-6 = 0.000001
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
再来看个栗子🌰,数的三次方根。
double x = 99; //要求99的三次方根
double l = -10e5, r = 10e5; //可以取大一点范围,二分的效率很高的
while (r - l > 10e-8) //如果两个浮点数不相等
{
double mid = (l + r) / 2;
if (mid * mid * mid > x) r = mid; // mid^3 > x
else l = mid;
}
cout << l; // 4.62606
题目
待增加,还是放上洛谷的官方题单。