数据结构与算法入门指南 - 二分

数据结构与算法入门指南


二分指在有序的数组中快速查找某个值,这里不得不提到我们生活中经常遇到的一个小游戏:猜数字,给定一个范围,让你猜某个数字,每次猜错就告诉你是大了还是小了。

按照二分的思想,猜数字时我们会直接猜中间的数字,比如给出范围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_boundupper_bound

C++自带的lower_boundupper_bound这两个函数,可以帮我们用二分在数组中快速查找某个值。

注意:数组必须是有序的(这两个函数默认数组是从小到大排序的),如果要在无序数组中查找则需要先排序,可以看看 数据结构与算法入门指南 - 排序

lower_bound(begin, end, x)返回在(begin, end-1)区间中第一个大于等于x的值的地址。

upper_bound(begin, end, x)返回在(begin, end-1)区间中第一个大于x的值的地址。

可以看到lower_boundupper_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_boundupper_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_boundupper_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

题目

待增加,还是放上洛谷的官方题单。

【算法1-6】二分查找与二分答案 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值