二分法 · 最笨,但绝不失手
解 f(x)=0 的第一个方法:二分法。只要你能给出一个「一正一负」的区间把根夹住,它就保证收敛——每步把区间对半砍。这一章手写它、打印收敛表,并讲清它的取舍:极其可靠,但慢(线性收敛)。
二分法 · 最笨,但绝不失手
进入第二站:求根——解方程 。很多问题最后都归结到这: 是 的根,某个方程的平衡点、某条曲线和 x 轴的交点,都是求根。
第一个方法,是最朴素、也最可靠的二分法(bisection)。它的思想小学生都懂,但它揭示了求根的核心张力:可靠 vs 快。
思想:夹住,然后对半砍
二分法基于一个直觉(严格说是介值定理):如果 是负的、 是正的,那么 和 之间一定有个点让 。 这叫用区间 夹住(bracket) 了一个根。
夹住之后,就不断对半砍:取中点 ,看 的符号——它和哪一端异号,根就在那半边,把区间缩到那半边。每砍一刀,区间宽度减半,根被越夹越紧。
手写 + 收敛表
用二分法求 (即 在 上的根):
import math
def bisection(f, a, b, tol=1e-12):
fa = f(a)
steps = 0
while (b - a) / 2 > tol:
c = (a + b) / 2
fc = f(c)
if fc == 0:
return c
if (fa < 0) != (fc < 0): # fa 和 fc 异号 → 根在左半
b = c
else: # 同号 → 根在右半
a, fa = c, fc
steps += 1
return (a + b) / 2
root = bisection(lambda x: x*x - 2, 1, 2)
print(root, abs(root - math.sqrt(2)))
# 1.4142135623731... 误差 ~1e-13打印每一步的「区间宽度 = 误差上界」,看它怎么稳稳地对半缩:
step 中点 c 误差上界(区间半宽)
1 1.500000 2.5e-01
2 1.250000 1.2e-01
3 1.375000 6.2e-02
4 1.437500 3.1e-02
... ... 每步 ×0.5
40 1.414213562373 4.5e-13每一步误差上界精确地减半——这是二分法最让人安心的地方:它的进度完全可预测,你甚至能提前算出「要多少步能到某精度」。
取舍:极可靠,但慢
二分法的最大优点是「绝不失手」。 只要初始区间真的夹住了根(端点异号),它保证收敛,不挑函数形状、不需要导数、不会发散。这在下一章那些「快但会翻车」的方法面前,是极其宝贵的稳。
它的缺点是慢。 每步只把误差减半,即「线性收敛」——大约每 步才多算准一位十进制数()。要 12 位精度,得砍约 40 刀。对比下一章牛顿法「每步翻倍正确位数」,二分法确实笨。但笨得可靠——它常被当作兜底 / 打底的方法。
两个实操细节
① 停止条件别贪。 循环条件 (b-a)/2 > tol 里,tol 别设得比机器精度还小——区间小到相邻浮点数的间隔时,中点就再也算不出新值,循环会空转甚至死循环。一个稳妥的 tol 是 左右(远大于 eps≈2e-16)。
② 用异号判断,别用 f(a)*f(c) < 0。 很多老代码用两个函数值相乘判符号——但 、 若都很大,相乘会溢出;若都很小,相乘会下溢成 0。上面代码里 (fa < 0) != (fc < 0) 只比符号、不做乘法,更稳。这又是个「等价数学、不等价数值」的小例子。
小结与下一章
二分法:夹住根(端点异号),然后每步对半砍,区间宽度就是误差上界。它绝不失手、进度可预测,但线性收敛、慢(每 3.3 步一位)。实操记住两点:tol 别小于机器精度、用异号判断别用乘积。
二分法慢在「每步只利用了 的符号」。如果我们利用 的斜率呢?顺着切线滑下去,收敛能快得惊人——每步正确位数翻倍。这就是牛顿法,快,但也更容易翻车。👉 牛顿法:切线的力量,与它翻车的时候。
参考文献
- [1] Burden & Faires.《Numerical Analysis》, §2.1 —— 二分法与介值定理。
- [2] Cleve Moler.《Numerical Computing with MATLAB》, ch. 4 —— 求根方法概览。
- [3] SciPy 文档.「scipy.optimize.bisect.」https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.bisect.html —— 库实现。