课堂笔记整理:方程求根-二分法、牛顿法、割线法。
内容来自周善贵老师的《计算物理》课程。
二分法
数学基础:对于连续函数
构成的方程:
,如果在区间
上满足:
,则区间
内至少存在一点
,使得
。
基本思想:取区间中点
,如果
,则根位于区间
内;否则根位于区间
内。对根存在的区间继续取中点,以此类推,直到当前根(cur_root)的函数值小于可接受误差为止。
对于方程
,使用二分法求根的Python代码如下:
import math
def func(cur_root):
func = math.exp(cur_root) * math.log(cur_root) - cur_root ** 2
return func
def binary(convergence, left, right):
print('current acceptable error: ' + str(convergence) + '\n')
error = convergence + 1 # 循环开始条件
cur_root = left
count = 1
while error > convergence:
if abs(func(left)) < convergence:
print('root = ' + str(left))
elif abs(func(right)) < convergence:
print('root = ' + str(left))
else:
print(str(count) + ' root = ' +str(cur_root))
middle = (left + right) / 2
if (func(left) * func(middle)) < 0:
right = middle
else:
left = middle
cur_root = left
error = abs(func(cur_root))
count += 1
convergence = float(input("your acceptable error:"))
left = float(input('left: '))
right = float(input('right: '))
binary(convergence, left, right)
要求误差不高于
,区间取
:
left: 1
right: 4
current acceptable error: 1e-06
第21步后误差满足条件:
21 root = 1.6945991516113281
牛顿法
将函数在根附近泰勒展开:
取线性阶近似,可得:
可以解得:
即为牛顿法迭代公式。
在迭代公式中,每一个
都是由
减去
得到的。
对于方程
,使用牛顿法求根的Python代码如下:
import math
def newton(convergence, ini_root):
print('current acceptable error: ' + str(convergence) + '\n')
error = convergence + 1 #循环开始条件
cur_root = ini_root
count = 1 # 迭代计数
while error > convergence:
print(str(count) + ' root = ' +str(cur_root))
func = math.exp(cur_root) * math.log(cur_root) - cur_root**2
dif_func = math.exp(cur_root) * ((1 / cur_root) + math.log(cur_root)) - 2 * cur_root
cur_root = cur_root - func / dif_func # 进行迭代
error = abs(func) # 计算误差
count += 1
convergence = float(input("your acceptable error:"))
ini_root = float(input('your initial root '))
newton(convergence, ini_root)
依然要求误差不高于
,从4开始迭代:
your initial root 4
current acceptable error: 1e-06
只需迭代7次即可结束,比二分法效率高很多:
8 root = 1.6946010436031655
割线法(分立牛顿法)
在牛顿法中,每一步都需要计算当前点的导数值,需要手动求导。如果不想人工求导,可以使用两点割线来代替切线,从而得到割线法,因此割线法又称分立牛顿法。
使用
和
两点计算的割线斜率为:
用
代替牛顿法中的
,可得割线法迭代式:
import math
def func(cur_root):
func = math.exp(cur_root) * math.log(cur_root) - cur_root ** 2
return func
def secant(convergence, x_0, x_1):
print('current acceptable error: ' + str(convergence) + '\n')
error = convergence + 1
cur_root = x_1
count = 1
while error > convergence:
print(str(count) + ' root = ' + str(cur_root))
sect = (func(x_1) - func(x_0)) / (x_1 - x_0)
cur_root = cur_root - func(x_1) / sect
error = abs(func(cur_root))
x_0 = x_1
x_1 = cur_root
count += 1
convergence = float(input("your acceptable error:"))
x_0 = float(input('x_0: '))
x_1 = float(input('x_1: '))
secant(convergence, x_0, x_1)
继续要求误差不高于
,从4开始迭代:
x_0: 4
x_1: 3
current acceptable error: 1e-06
可以在8次迭代之后结束:
9 root = 1.694602010833161
可以看到,牛顿法及其变种割线法,相对于二分法是十分高效的,但是牛顿法也有局限。牛顿法在某些情况下并不收敛,例如牛顿法求方程
的根,每一次取切线进行迭代都会使当前
更加远离方程的根,最终跑到无穷大溢出报错:
1023 root = (inf-1.2158939983623275e+294j)
OverflowError: complex exponentiation
Reference:
周善贵,《计算物理》课程讲义