解非线性方程算法无论是在理论还是实际应用的角度来看,都是极为重要的。在科学和工程中,如何较好的得到一个非线性方程的数值解,是数值分析算法研究中极其重要的领域之一。我们主要讨论几个解一元非线性方程的解法,即:
f ( x ) = 0 (1) f(x) = 0 \tag{1} f(x)=0(1)
我们想要知道当 f ( x ) f(x) f(x)为非线性方程时,有什么方法求解 f ( x ) f(x) f(x)的零点,也就是上面方程的根. 例如,如何求解: n \sqrt{n} n? 首先这个问题可以转化为求非线性方程 f ( x ) = x 2 − n = 0 (2) f(x) = x^2 - n=0 \tag{2} f(x)=x2−n=0(2)的非负根。
我们可以使用迭代算法求解出 x ⋆ x^{\star} x⋆使得 ∥ f ( x ⋆ ) − n ∥ ≤ ϵ (3) \lVert f(x^{\star}) - n\rVert \le \epsilon \tag{3} ∥f(x⋆)−n∥≤ϵ(3)。
该算法基于这样一个观察结果,如果一个连续函数的图像在a点和b点取到的函数数值符号相反,那么该函数在两点之间至少要和x轴相交一次。
在使用平分法解方程时,开始有一个区间 [ a , b ] [a,b] [a,b],在其端点上, f ( x ) f(x) f(x)的符号相反,该算法计算 f ( x ) f(x) f(x)的中点 x m i d = ( a + b ) / 2 (4) x_{mid} = (a + b) /2 \tag{4} xmid=(a+b)/2(4)上的值,如果 f ( x m i d ) = 0 f(x_{mid})=0 f(xmid)=0就求得一个根,算法也终止了。否则,它继续在 [ a , x m i d ] [a,x_{mid}] [a,xmid]或者 [ x m i d , b ] [x_{mid},b] [xmid,b]上查找根,至于在哪个区间上找,取决于在哪个区间的端点上满足零点定理。
因为我们不能指望平分法能够恰好发现方程的根并终止。我们需要寻找另外一种标准来终止算法。当包含某个根 x ⋆ x^{\star} x⋆的区间 [ a n , b n ] [a_n, b_n] [an,bn]变得足够小,以至于用区间的中点 x n x_n xn逼近 x ⋆ x^{\star} x⋆的绝对误差肯定小于某些预先选定的阈值 ϵ \epsilon ϵ( ϵ > 0 \epsilon >0 ϵ>0)我们就可以停止迭代。即当:
∣ x − x ⋆ ∣ ≤ b n − a n 2 (5) |x-x^{\star}| \le \frac{b_n - a_n}{2} \tag{5} ∣x−x⋆∣≤2bn−an(5)
我们可以停止算法。但是我们注意计算机的浮点表示等因素,如果我们选择的 ϵ \epsilon ϵ**小于一个特定机器的阈值,该算法就永远不会停止!**因此在平分法的实现中,限制算法允许执行的迭代次数是一个好的办法。
def binary_sqrt ( n, eps, max_iter_nums ):
"""基于平分法的平方根求解算法
Args:
n : 求解的值
eps : 迭代精度
max_iter_nums 最大迭代次数
"""
# 端点 a < b iter_cnt : 记录迭代次数
a, b = 0, n
iter_cnt = 0
# 中点:x fx = x^2 - n
x = 0
fx = 0
while iter_cnt < max_iter_nums :
x = (a + b) / 2
# 终止条件:左端点和中点 无限接近,认为 x 已经收敛到目标值
if x - a < eps:
return x
fx = x**2 - n
if fx - n == 0:
return x
# 确定下一个搜索区域
fa = a**2 - n
if fx * fa < 0:
b = x
else:
a = x
iter_cnt += 1
print(f'iter_cnt={iter_cnt}\t x={x:.6f}\t fx={fx:.6f}\t a={a:.6f}\t b={b:.6f}')
return x
时间复杂度分析
O ( n ) = log 2 ( ( b − a ) ϵ ) O(n) = \log_2(\frac{(b-a) }{\epsilon}) O(n)=log2(ϵ(b−a))算法复杂度主要取决于 终止精度 以及搜索区间的大小。
这里计算:binary_sqrt(2, 1e-3, 1000)
平分法的收敛过程:
iter_cnt=1 x=1.000000 fx=-1.000000 a=1.000000 b=2.000000
iter_cnt=2 x=1.500000 fx=0.250000 a=1.000000 b=1.500000
iter_cnt=3 x=1.250000 fx=-0.437500 a=1.250000 b=1.500000
iter_cnt=4 x=1.375000 fx=-0.109375 a=1.375000 b=1.500000
iter_cnt=5 x=1.437500 fx=0.066406 a=1.375000 b=1.437500
iter_cnt=6 x=1.406250 fx=-0.022461 a=1.406250 b=1.437500
iter_cnt=7 x=1.421875 fx=0.021729 a=1.406250 b=1.421875
iter_cnt=8 x=1.414062 fx=-0.000427 a=1.414062 b=1.421875
iter_cnt=9 x=1.417969 fx=0.010635 a=1.414062 b=1.417969
iter_cnt=10 x=1.416016 fx=0.005100 a=1.414062 b=1.416016
result: 1.416016
平分法和折半查找非常类似,两种算法都是对查找问题的不同变化形式求解。但是平分法和折半查找法也并非等同,主要是,折半查找时严格单调的,单平分法并不要求这一点。
平方法作为解方程的通用算法的主要缺点:
- 相对其他方法收敛速度较慢,所以在实际中很少使用这个方法
- 无法扩展到解更一般的方程和方程组领域
平方法的优点:
- 平方法向根收敛时,所在区间的特性是很容易检验的。而且也不用求函数导数
由泰勒展开公式得到:
f ( x k + 1 ) ≈ f ( x ) + ( x k + 1 − x k ) f ′ ( x k ) = 0 (6) \begin {align*} f(x_{k+1}) \approx& f(x)+(x_{k+1}- x_k) f'(x_k) =0 \end {align*} \tag{6} f(xk+1)≈f(x)+(xk+1−xk)f′(xk)=0(6)
变形得到迭代式:
x k + 1 = x k − f ( x k ) f ′ ( x k ) (7) x_{k+1} = x_k- \frac{f(x_k)}{f'(x_k)} \tag{7} xk+1=xk−f′(xk)f(xk)(7)
以 f ( x ) = x 2 − n f(x) = x^2 - n f(x)=x2−n为例:
因为:
f ′ ( x ) = 2 x (8) f'(x) = 2x \tag{8} f′(x)=2x(8)
得到:
x k + 1 = x k − x k 2 − n 2 x k = x k − 1 2 ( x k − n x k ) = 1 2 ( x k + n x k ) (9) \begin{align*} x_{k+1} =& x_k - \frac{x_k^2 - n}{2x_k} \\ =&x_k - \frac{1}{2}(x_k-\frac{n}{x_k}) \\ =&\frac{1}{2}(x_k + \frac{n}{x_k}) \end {align*} \tag{9} xk+1===xk−2xkxk2−nxk−21(xk−xkn)21(xk+xkn)(9)
def newtown_sqrt(n, eps, init_x = 1e-2):
"""基于牛顿法的平方根求解算法
Args:
n : 求解的值
eps: 迭代精度
init_x : 初始值
"""
x = init_x
# |x - x1| 用于判断是否收敛,若收敛,返回解x
x1 = -1
while( abs(x - x1 ) > eps ):
x1 = x
# 迭代式
x = (x + n/x)/2
print(f'x={x:.6f}\t |x-x1|={abs(x - x1):.6f}\t')
return x
求解根号2 **newtown_sqrt(2, 1e-3, 1) **迭代结果:
x=1.500000 |x-x1|=0.500000
x=1.416667 |x-x1|=0.083333
x=1.414216 |x-x1|=0.002451
x=1.414214 |x-x1|=0.000002
result: 1.414214
牛顿法的优势:
- 在选择了合适的初始近似值之后能够快速收敛
- 能够应用于更一般类型的方程和方程组