二分算法详解:整数二分及浮点数二分算法(Binary Search)(含算法模板)

一、二分算法简介

当我们要从一个序列中查找一个元素的时候,最简单无脑的方法就是顺序查找法,但由于在大数据情况下爆炸的时间复杂度而舍弃。

最常见的方法是二分查找,也称折半查找(Binary Search),它是一种效率较高的查找方法。

最近偶然看到 『LeetCode』 讨论中的大佬总结的 二分查找从入门到入睡 ,虽然文章巨长,但总结的很全,一些边界问题讲的也很细,其中包括了Y总的二分思路,非常推荐看一看!!


二、 算法基本思想和流程(时间复杂度 O ( l o g n ) O(logn) O(logn)

  • 算法思想: 假设在闭区间 [l, r] 中寻找目标值 x ,二分的思想是每次将区间长度缩小一半,当l = r时,我们就找到了目标值。
  • 注意:
    • 二分的本质并不是单调性,二者并没有必要的关系。 因为数据有单调性一定可以二分,但可以二分的题目不一定非要有单调性。
      在这里插入图片描述
    • 二分的本质在于:在区间 [l, r] 中有性质,使得区间可以一分为二,一半边区间满足该性质而另一半区间不满足性质, 这样的话二分算法可以寻找这个性质的边界(红色和绿色的边界都行,因为是整数二分所以两边界不重合)
  • 整数二分: 文章中的两个模板分别对应着二分红色边界和二分绿色边界。
    • 【模板一】二分红色边界
      在这里插入图片描述
      • Step1——确定中间点mid = (l + r + 1) / 2
      • Step2——判断
        • mid 满足性质 check(mid) ,即 if(check(mid)) = True,则更新区间为 [mid, r],即令l = mid 即可;
        • mid 不满足性质 check(mid) ,即 if(check(mid)) = False,则更新区间为 [l, mid - 1],即令r = mid - 1 即可;
      • Step3——循环前两步,不断缩短空间,直到 l >= r,边界值为 l
    • 【模板二】二分绿色边界
      在这里插入图片描述
      • Step1——确定中间点mid = (l + r) / 2 (上取整是为了防止死循环);
      • Step2——判断
        • mid 满足性质 check(mid) ,即 if(check(mid)) = True,则更新区间为 [l, mid],即令r = mid 即可;
        • mid 不满足性质 check(mid) ,即 if(check(mid)) = False,则更新区间为 [mid + 1, r],即令l = mid + 1 即可;
      • Step3——循环前两步,不断缩短空间,直到 l >= r,边界值为 l
  • 浮点数二分 :相对于整数二分更简单,无需考虑边界问题,理解了整数二分后,浮点数二分不成问题。

三、 整数二分模板(背诵)

【模板一】

  • 循环条件:l < r
  • 划分区间:[l, r][l, mid - 1][mid, r]
  • 更新操作:r = mid - 1或者l = mid
  • 注意:计算mid时为了避免死循环需要加1,即mid = l + r + 1 >> 1
bool check(int x) {/* ... */} // 检查x是否满足某种性质

int bsearch(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

【模板二】

  • 循环条件:l < r
  • 划分区间:[l, r][l, mid][mid + 1, r]
  • 更新操作:r = mid或者l = mid + 1
  • 注意:计算mid时不需要加1,即mid = l + r >> 1
bool check(int x) {/* ... */} // 检查x是否满足某种性质

int bsearch(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

3. 浮点数二分模板(背诵)

bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)  // 两种写法:此时是用精度控制循环次数,直接控制循环100次也是OK的!
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

四、 使用模板的几个关键问题

① 如何选择用哪个模板?

做题的顺序首先是确定 check() 函数,再进行区间划分的分析,再确定使用哪个模板。

可以看到:当 l = mid 时,我们使用模板一,且 mid = (l + r) / 2 为下取整;当 r = mid 时,我们使用模板二,且 mid = (l + r + 1) / 2 需要上取整;

② 为什么模板一需要加上 ‘1’ ,即 ‘mid = (l + r + 1) / 2’,而模板二又不需要了 ?
  • 使用模板一时,若 l = r - 1,即 lr 只差 1 的时候,如果 mid = (l + r) / 2 下取整的话,结果是等于 l 的,则一旦 if(check(mid)) = True ,更新区间会一直陷入到 [mid, r] = [l, r] 死循环中。
  • 同理,使用模板二时,当 l = r - 1,即 lr 只差 1 的时候,如果 mid = (l + r + 1) / 2 下上取整的话,结果是等于 r 的,则一旦 if(check(mid)) = True ,更新区间会一直陷入到 [l, mid] = [l, r] 死循环中。

五、 应用:模板题

【整数二分 - 模板题】AcWing 789. 数的范围
【思路】想要找到目标值 x 的起始坐标,可理解成找到 ≥x 的最小值,再判断找到的边界值是否与 x 相等,若不相等返回 -1;同样,想要找到目标值 x 的终止坐标,可理解成找到 ≤x 的最大值,再判断找到的边界值是否与 x 相等,若不相等返回 -1

【C++代码】

#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int q[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    while (m -- )
    {
        int x;
        scanf("%d", &x);

        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (q[mid] >= x) r = mid;
            else l = mid + 1;
        }

        if (q[l] != x) cout << "-1 -1" << endl;
        else
        {
            cout << l << ' ';

            int l = 0, r = n - 1;
            while (l < r)
            {
                int mid = l + r + 1 >> 1;
                if (q[mid] <= x) l = mid;
                else r = mid - 1;
            }

            cout << l << endl;
        }
    }

    return 0;
}

【浮点数二分 - 模板题】AcWing 790. 数的三次方根

【C++代码】

#include <iostream>

using namespace std;

int main()
{
    double x;
    cin >> x;

    double l = -100, r = 100;
    while (r - l > 1e-8)
    {
        double mid = (l + r) / 2;
        if (mid * mid * mid >= x) r = mid;
        else l = mid;
    }

    printf("%.6lf\n", l);
    return 0;
}

2022年06月26日:补充二分“相等返回”的模板

【模板三】相等返回

  • 循环条件:l <= r
  • 划分区间:[l, r][l, mid - 1][mid + 1, r]
  • 更新操作:r = mid - 1或者l = mid + 1
  • 注意:计算mid时不需要加1,即mid = l + r >> 1
int bsearch(int l, int r)
{
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (q[mid] == target) return mid;
        else if (q[mid] < target) l = mid + 1;
        else r = mid - 1;
    }
    return -1;
}

2022年06月30日:补充二分三个模板的理解

此处借鉴学习一下『 LeetCode 大佬 - yukiyama』 的总结表格,详解请参考:二分查找从入门到入睡
在这里插入图片描述

  • Y总的两个二分模板使用的循环条件是 l < r,结束循环条件必定是相等 l = r 终止,且二分范围一般来讲是 [0, n - 1]n 为数组长度。本质上在二分的过程中,mid 并没有完全覆盖整个数组,这怎么理解呢?
    • 我们先看模板一,我们考虑一种情况,当数组中所有元素都不满足性质时,这时候 r 值会一直缩小直到 l = r = 0 ,而在这个过程中 mid 不会取到 0 值就返回了,也就是说返回的 l = 0 值并没有通过性质的判断,无法确定是否满足我们所设的二分性质,因为这个性质的边界是可能存在于小于 0 上的。因此,保险起见,需要对输出的 l 再进行一次判断即可。总结来说,Y总的模板一是左开右闭的。
    • 同理我们分析模板二,同样考虑一种情况,当数组中所有元素都不满足性质时,这时候 l 值会一直扩大直到 l = r = n - 1 ,而在这个过程中 mid 不会取到 n - 1 值就返回了,也就是说返回的 l = n - 1 值并没有通过性质的判断,无法确定是否满足我们所设的二分性质,因为这个性质的边界是可能存在于大于 n - 1 上的。因此,保险起见,需要对输出的 l 再进行一次判断即可。总结来说,Y总的模板二是左闭右开的。
    • 所以,用Y总的模板一和模板二的时候需要小心一下 l 是否落到范围边界上了,此时就需要再次判定一下。或者将范围扩大到 [-1, n - 1] (模板一)/ [0, n] (模板二)。
  • 我们用同样的思想去看一看模板三(相等返回),不同于前两个模板,模板三的退出循环条件必定是相错终止,即 l - r = 1 ,按上述方法去分析的话,我们会发现模板三的 mid 会覆盖整个数组元素,因此模板三是左闭右闭的,且由于更新操作为r = mid - 1或者l = mid + 1,因此计算mid时不需要加1mid = l + r >> 1 即可。
  • 21
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
C语言可以通过以下方式以二进制方式输出整数浮点数: 对于整数: 1. 首先,使用printf函数来输出整数。例如,想要输出一个整数变量x的二进制表示,可以使用以下代码: ```c printf("%d的二进制表示为:", x); ``` 2. 然后,使用位运算和位掩码的方式,逐位判断整数变量x的二进制表示中的每一位是0还是1,并输出对应的结果。例如: ```c int bits = sizeof(x) * 8; // 获取整数变量x的位数 for (int i = bits - 1; i >= 0; i--) { int bit = (x >> i) & 1; // 获取第i位的值 printf("%d", bit); // 输出第i位的值 } printf("\n"); ``` 对于浮点数: 1. 首先,将浮点数转换成对应的二进制形式。可以使用union联合体来进行浮点数整数之间的转换。例如,假设有一个浮点数变量f,可以通过以下代码将其转换为对应的二进制整数: ```c union { float f; int i; } converter; converter.f = f; // 将浮点数赋值给联合体的浮点数部分 int bin = converter.i; // 获取联合体的整数部分,即浮点数对应的二进制表示 ``` 2. 然后,使用位运算和位掩码的方式,逐位判断浮点数变量f的二进制表示中的每一位是0还是1并输出对应的结果。例如: ```c int bits = sizeof(f) * 8; // 获取浮点数变量f的位数 for (int i = bits - 1; i >= 0; i--) { int bit = (bin >> i) & 1; // 获取第i位的值 printf("%d", bit); // 输出第i位的值 } printf("\n"); ``` 这样,就可以以二进制方式输出整数浮点数了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PanyCG_pc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值