04二分查找(浮点二分)
# 🔍 二分查找的“陷阱”:为什么我的平方根算法对小于1的数失效?
> **作者**:一位正在深入理解算法细节的开发者
> **关键词**:二分查找、平方根、浮点数、算法边界、编程陷阱
> **发布时间**:2025年8月28日
---
在学习和实现算法的过程中,我们常常会遇到一些“看似正确,却在边界上出错”的问题。今天,我们就来剖析一个经典的例子:**用二分查找求平方根**。
你可能写过这样的代码:
```cpp
double l = 0, r = x;
while (r - l > 1e-8) {
double mid = (l + r) / 2;
if (mid * mid >= x) r = mid;
else l = mid;
}
逻辑清晰,代码简洁,测试 x = 4、x = 9 都没问题。但当你输入 x = 0.25 时,结果却是:
输入:0.25
输出:0.25(错误!应为 0.5)
这是为什么?难道二分查找失效了?其实不是算法错了,而是我们忽略了一个关键的数学事实。
🧩 问题重现:小于1的数“不生效”?
我们来手动分析一下 x = 0.25 的情况:
- 初始区间:
l = 0,r = 0.25 - 真实平方根:
sqrt(0.25) = 0.5
但 0.5 > 0.25,也就是说:真实解根本不在区间 [0, 0.25] 内!
而我们的二分查找是在这个“错误的区间”内进行的,无论怎么迭代,都无法跳出 [0, 0.25],最终只能收敛到 0.25 本身。
❌ 根本问题:初始搜索区间选择错误
📐 数学真相:平方根的大小关系
我们来观察一个有趣的数学规律:
x |
sqrt(x) |
比较结果 |
|---|---|---|
| 4 | 2 | sqrt(x) < x |
| 1 | 1 | sqrt(x) = x |
| 0.25 | 0.5 | sqrt(x) > x |
👉 结论:
- 当
x > 1时,sqrt(x) < x - 当
x = 1时,sqrt(x) = x - 当
0 < x < 1时,sqrt(x) > x
所以,如果我们统一设置 r = x,那么当 x < 1 时,真实解就被排除在搜索区间之外了!
✅ 正确解法:动态设置搜索区间
为了让二分查找适用于所有 x ≥ 0 的情况,我们必须确保搜索区间包含真实解。
✅ 解决方案:取 r = max(x, 1.0)
- 如果
x ≥ 1,sqrt(x) ≤ x→ 区间[0, x]合理 - 如果
x < 1,sqrt(x) < 1→ 区间[0, 1]包含解
这样,无论 x 多大或多小,真实平方根都在 [0, max(x, 1)] 内。
✅ 修正后的完整代码
#include <iostream>
#include <iomanip> // 用于控制输出精度
using namespace std;
int main() {
cout << fixed << setprecision(8); // 输出保留8位小数
double x;
while (cin >> x) {
if (x < 0) {
cout << "负数没有实数平方根" << endl;
continue;
}
if (x == 0) {
cout << 0 << endl;
continue;
}
// 关键修正:右边界取 max(x, 1.0)
double l = 0, r = (x >= 1) ? x : 1;
// 二分查找
while (r - l > 1e-8) {
double mid = (l + r) / 2;
if (mid * mid >= x)
r = mid;
else
l = mid;
}
cout << "sqrt(" << x << ") ≈ " << l << endl;
}
return 0;
}
🧪 测试验证
| 输入 | 输出 | 是否正确 |
|---|---|---|
| 4 | 2.00000000 | ✅ |
| 0.25 | 0.50000000 | ✅ |
| 0.01 | 0.10000000 | ✅ |
| 2 | 1.41421356 | ✅ |
全部通过!
🎯 二分查找的核心思想回顾
这个“小问题”其实揭示了二分查找的几个关键原则:
-
搜索空间必须包含解
→ 无论算法多精妙,如果初始区间错了,结果一定错。 -
边界条件决定成败
→x = 0、x = 1、x < 1都是需要单独考虑的边界。 -
单调性是二分的前提
→f(mid) = mid^2是单调递增的,所以可以二分。 -
浮点数二分需控制精度
→ 用1e-8作为收敛条件,避免无限循环。
💡 更进一步:牛顿迭代法(更快!)
虽然二分查找稳定可靠,但收敛速度是线性的(O(log(1/ε)))。
更高效的方法是牛顿迭代法:
double sqrt_newton(double x) {
if (x == 0) return 0;
double r = x;
while (abs(r * r - x) > 1e-8) {
r = (r + x / r) / 2;
}
return r;
}
牛顿法收敛速度是平方级的,更快!
✅ 总结
| 问题 | 解决方案 |
|---|---|
| 小于1的数失效 | 改 r = x 为 r = max(x, 1.0) |
| 负数输入 | 增加判断和提示 |
| 精度不足 | 使用 1e-8 并控制输出格式 |
| 收敛慢 | 可改用牛顿迭代法 |
编程不是写完就对,而是不断理解边界、修正假设的过程。
这个小小的“平方根”问题,教会我们:
算法的正确性,不仅取决于逻辑,更取决于对数学本质的理解。
📚 延伸阅读
- 《算法导论》—— 二分查找与数值计算
- LeetCode 69. Sqrt(x)
- 牛顿法求根:从几何直观理解迭代收敛

浙公网安备 33010602011771号