90%的程序员都写错的算法-二分查找万能模版

新的角度看二分

在这里插入图片描述

  • 二分就是将数组分为两段
  • 因此,问题的最终目标是找出蓝红边界

朴素算法

在这里插入图片描述

  • 红色指针一开始指向最右超出范围处,随后不断向左移动,直到找到蓝红边界;或者蓝色指针…
  • 时间复杂度 O ( n ) O(n) O(n)

二分查找

在这里插入图片描述
在这里插入图片描述

  • 红蓝指针的移动其实代表着红蓝区域的拓展。那么,有什么更快拓展的方式呢?由于数组本身有序…便可以快速拓展红色 或 蓝色区域
  • 数组下标有效范围 [ 0 , n − 1 ] [0,n - 1] [0,n1],因此,初始时l = -1, r = n;
  • 循环的结束条件是l + 1 != r,因为l指向蓝色边界最右边,r指向红色边界最左边,当l + 1 == r时整个数组已经由灰色未知被划分完毕
  • 如果m是红色区域,直接r = m;同理…
  • 最后结果返回l还是r要看情况

细节1.为什么l的初始值为-1,r的初始值为n

在这里插入图片描述

  • 假如最终整个数组都该是红色区域,如果l初始化为0,l一开始就处在红色区域内,这会造成错误;同理…

细节2.m是否始终处于[0,n)以内

在这里插入图片描述

  • 只有处在这个区间以内,m才是有意义的
  • 先看m的下界,由于m = (l + r) / 2(向下),因此如果我们想让m尽可能小,就要l和r尽可能小,l的最小值是初始值-1,r如果能进入循环体,最小值是1(不是0),因此m最小值为0
  • 再看m上界,同理,r的最大值为n,l如果要进入循环体,最大值为n - 2,因此m最大值为n - 1

细节3.更新指针时,能不能写成l = m + 1或者r = m - 1

在这里插入图片描述
在这里插入图片描述

  • 如果刚好m指向蓝色区域的最后一个位置,l如果变为m + 1,就错了;同理…

细节4.会不会陷入死循环

在这里插入图片描述

  • 分别考虑l + 1 == r l + 2 == r l + 3 == r的情况
  • l + 1 == r,退出循环
  • l + 2 == r,也就是它们之间隔一个元素,当进入循环体时,m就是它们之间隔着的那个元素,接下来,要么将l设置成m,要么将r设置成m,也就是说,在循环体结束时,l的下一个元素一定是r,会回归到第一种情况并退出循环体
  • l + 3 == r,会回归到第二种情况或者回归到第一种情况

例子 与 c++库函数

在这里插入图片描述

  • lower_bound对应大于等于
  • upper_bound对应大于

一般流程

在这里插入图片描述

  • 后处理指的是 例如 只有蓝色区域/红色区域的返回值处理问题(需要特判)等

如果没有蓝色区域或者红色区域

  • 那么l或者r会不在有效范围内
  • 最后结果特判,l的初始值为-1,r的初始值为n

浮点数的二分

  • 题目 :通过二分查找的方式计算 2 \sqrt{2} 2 ,要求误差小于 1 0 − 6 10^{-6} 106
  • 退出循环的条件为r - l <= 1e-8
  • 要求误差小于 1 0 − 6 10^{-6} 106,相当于 结果保留6位小数;这样,循环退出的条件是 1 0 − 8 10^{-8} 108,而输出则是六位小数
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
    double l = -10000, r = 10000;
    while (r - l > 1e-8)
    {
        double mid = (l + r) / 2;
        if (mid * mid >= 2) r = mid;
        else l = mid;
    }
//    printf("%.6lf\n", l);
    cout << fixed << setprecision(6) << l << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值