今日分享 浮点数二分
二分查找的核心思想是"一分为二,排除一半"。对于浮点数,我们不再寻找精确的整数下标,而是要在一个连续的区间内,找到一个与目标值足够接近的数值。
核心奥义
1. 逼近而非命中:浮点数问题通常允许存在微小误差。我们的目标是让答案的精度满足题目要求。
2. 循环条件是精度:与整数二分的"left <= right"不同,浮点数二分的终止条件是区间长度小于某个极小值(如 1e-8)。当区间足够小时,区间内任意值都可作为答案。
3. 不需要 +1 或 -1:由于处理的是连续区间,不存在跳过答案的问题。直接更新 left = mid 或 right = mid 即可。
关键技巧
确定初始区间:找到一个能百分百包含答案的区间 [L, R]。例如开方问题中,区间可设为 [0, max(1, x)]。
设计判断函数:构造一个单调函数 check(mid) 。该函数能判断 mid 与目标值的大小关系,从而决定舍弃哪一半区间。
设置合理精度:精度值通常比题目要求高 2-3 个数量级。例如题目要求保留 6 位小数,精度可设为 1e-8,避免因精度不足导致答案错误。
实战例题:求一个数的平方根
问题:给定一个非负整数 x,计算并返回 x 的平方根,结果保留 6 位小数。
分析:
初始区间:当 x >= 1 时,平方根在 [1, x] 之间;当 x < 1 时,平方根在 [x, 1] 之间。统一设为 [0, max(1, x)]。
判断函数:对于 mid,计算 mid*mid。若大于 x,说明平方根小于 mid,应向左查找;否则向右查找。
下面是四种语言的实现:
C 语言实现
#include <stdio.h>
#include <math.h>
double mySqrt(double x) {
double left = 0.0, right = fmax(1.0, x);
// 精度设置为 1e-8,确保输出 6 位小数正确
while (right - left > 1e-8) {
double mid = left + (right - left) / 2;
if (mid * mid > x) {
right = mid;
} else {
left = mid;
}
}
return left;
}
int main() {
double x;
scanf("%lf", &x);
printf("%.6f\n", mySqrt(x));
return 0;
}
C++ 实现
#include <iostream>
#include <algorithm> // for max
#include <<iomanip> // for setprecision
using namespace std;
double mySqrt(double x) {
double left = 0.0, right = max(1.0, x);
while (right - left > 1e-8) {
double mid = left + (right - left) / 2;
if (mid * mid > x) {
right = mid;
} else {
left = mid;
}
}
return left;
}
int main() {
double x;
cin >> x;
cout << fixed << setprecision(6) << mySqrt(x) << endl;
return 0;
}
Python 实现
def my_sqrt(x):
left = 0.0
right = max(1.0, x)
while right - left > 1e-8:
mid = left + (right - left) / 2
if mid * mid > x:
right = mid
else:
left = mid
return left
x = float(input())
print("{0:.6f}".format(my_sqrt(x)))
Java 实现
import java.util.Scanner;
public class Main {
public static double mySqrt(double x) {
double left = 0.0;
double right = Math.max(1.0, x);
while (right - left > 1e-8) {
double mid = left + (right - left) / 2;
if (mid * mid > x) {
right = mid;
} else {
left = mid;
}
}
return left;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double x = scanner.nextDouble();
System.out.printf("%.6f\n", mySqrt(x));
scanner.close();
}
}