浮点数二分
食用指南:
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目只有题目分析,代码实现,代码误区
题目描述:
-
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。输出格式
共一行,包含一个浮点数,表示问题的解。注意,结果保留 6 位小数。
数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000 -
题目来源:https://www.acwing.com/problem/content/792/
题目分析:
- 开方运算:典型的浮点数二分
算法原理:
- 二分查找:
目标结果将数轴划分为两个部分:3次方后大于n者 & 3次方后小于n者
浮点数二分和整数二分的区别:
浮点数由于计算机存储存在精度问题,所以浮点数二分与整数二分不同
-
整数二分:
- 终止于 l >= r
- 由于点有限,mid 需要考虑归属区间
-
浮点数二分:
- 终止于 r - l <= esp
精确度esp一般比题意要求再精准两位,而非固定1e-8 - 由于点无限,mid不需要考虑归属区间,直接暴力(l + r)/2
- 终止于 r - l <= esp
-
总结:
浮点数二分和整数二分的区别:- 终止条件不同
- 区间边界对mid的要求不同
写作步骤:
三步走:
-
估计答案所在的初始区间[l,r]
这一步非常关键,尤其开方操作的初始区间不是简单的[0,n],如n==0.001开方后是0.1
-
简单mid = (l+r)/2;
只有整型有>>运算符 -
循环迭代r & l,直到r-l <= esp
核心算法:
- 浮点数的相等衡量:
浮点数由于存储精度问题,两个不相等的浮点数可能其实是相等的
一般设置一个精度变量esp = 1e-8,fabs(a-b) <= esp则视为a == b - 浮点数的大小比较:
严谨来说,浮点数的大于应该是:a - b > esp则a > b
但是本题直接 midmidmid > n也能过 - 浮点数的存储:
能用double就不用float,因为double比float更精准,double有52位用于存储数值,而float仅有23位用于存储数值
代码注意:
- 精度esp的选取:不是一定要1e-8,1ex的x一般取要求保留位数再加2
- 初始l r的范围选取:避免小数开方变大的可能,l和r初始化一定要够大
- 浮点数相等的判断以及浮点数大小的比较
- C++的固定小数位数输出:
#include头文件
cout<<fixed<<setprecision(位数)<<数值<<endl;
代码模板:
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
const double esp = 1e-8;
double bisearch(double x){
double l = -1000, r = 1000;
while(r-l > esp){
double mid = (l + r)/2;
//if(mid*mid*mid > x)也过了
if (mid*mid*mid - x > esp) r= mid;
else l = mid;
}
return l;
}
int main(){
double x = 0;
cin >> x;
cout <<fixed <<setprecision(6) <<bisearch(x) <<endl;
return 0;
}
代码误区:
0. 本题是开立方根,开平方根有细微区别:
- 数轴上一个点的立方根肯定是唯一的,但是平方根有正负两个
- 开平方根可以将L设置为0,只求正数平方根,开出来之后加-就是负数平方根
- 当然你也可以继续用立方根源码求解,但是结果肯定只有两个中的一个;当第一次求出来mid之后,若迭代的是r,则求得负数根;若迭代的是l,则求得正数根
1. l 和 r初始应该选取多少?
- r选取最大n的立方根,l选取最大n立方根的负数
- 本题中n最大10000,则r取100,l取-100足够。
2. 是否需要fabs(r-l)>esp:
- 应该是不需要的,只要初始r > l,则每次的mid也大于l,替换的r必然大于l
- 不放心可以使用fabs()
本篇感想:
- 其实会了整数二分后,只要记住esp,浮点数的相等衡量,大小比较,以及<iomanip>头文件里的fixed和serpricision(位数)即可
- 看完本篇博客,恭喜已登《练气境-初期》
距离登仙境不远了,加油