浮点数二分

106 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情

浮点数二分

只要right - left 大于一个非常小的精度的时候,我们就认为我们得到了答案!

浮点数不存在整数取整导致的边界问题,但是会有误差,可以把精度设的高一点

// 浮点数二分算法模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求  1e-6:10^-6
    while (r - l > eps) 
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;//把区间从[0~r]缩小成[0~mid]
        else l = mid; //浮点数不存在整数取整导致的边界问题
    }
    return l;   
}

求一个数的平方根

假设要求x的平方根, 答案一定在0x之间,所以在0x之间浮点数二分

  • 如果 mid * mid >= x,说明再往后得到的平方就比x大了, 答案在[0,mid]区间内,把[left,right]区间更新为[0,mid]区间 , 即:right = mid
  • 否则让更新区间为:[mid,right]

当精度足够小的时候就可以停止了

int main()
{
    double x ;
    cin >> x;
 
    double left = 0, right = x;
    while (right - left > 1e-8) //计算结果保留小数的位数
    {
        double mid = (left + right) / 2;
        if (mid * mid >= x)
            right = mid;
        else
            left = mid;
    }
    printf("%lf\n", right);
    return 0;
}

写法2:直接循环100次

int main()
{
    double x ;
    cin >> x;
    
    double left = 0, right = x;
    for(int i = 0;i<100;i++)
    {
        double mid = (left + right) / 2;
        if (mid * mid >= x)
            right = mid;
        else
            left = mid;
    }
    printf("%lf\n", right);
    return 0;
}

因为没进行一次二分,都是把区间缩小一半,所以循环100次,本质是进行100次二分,把区间长度缩小到原来的 1/2^100


上面的代码是有Bug的, x>0 ,我们是在[0,x]进行二分, 当x是小于1的时候, 比如x = 0.01, 那么就是在0~0.01里面去找答案,但是0.01求平方根的值是0.1 并不在区间里面.所以就是bug

所以答案的范围不能取在0~x 可以取成 0~ max(1,x) 也就是右边界不能<1 0~1之间的数开方之后变大了

int sqrt(int x) {
    double left = 0,right = x;
    double mid = 0;
    while(right - left > 1e-8)
    {
        mid = (left+right)/2;
        if(mid*mid >= x)
            right = mid;
        else
            left = mid;
    }
    return right;
}

数的三次方根

给定一个浮点数 n,它的三次方根,n的范围是-10000~10000,结果保留 6 位小数

  • 假设要保留n位小数,那么精度就是:1e(-n-2) 比如要保留4位小数,精度就是1e-6 也就是 10^(-6)
#include<iostream>
using namespace std;
int main()
{
    double x;
    cin >> x;
    
    double left = -10000;
    double right = 10000;
    while(right - left > 1e-8)
    {
        double mid = (left+right)/2;
        if(mid*mid*mid >= x) //要去左边二分
            right = mid;
        else
            left = mid;
    }
    printf("%lf\n",left); //默认保留的就是6位小数
    return 0;
}