通过Python实现5个常用数值近似算法

二分法

二分法是一种简单有效的数值型迭代算法,对于一个在区间 [ a , b ] \left[a,b\right] [a,b]上的连续函数fx,若满足 f ( a ) ⋅ f ( b ) < 0 f(a)\cdot f(b)<0 f(a)f(b)<0,那么fx在 [ a , b ] \left[a,b\right] [a,b]上必有根,此时设定分割点 x 0 = ( a + b ) / 2 x_0=\left(a+b\right)/2 x0=(a+b)/2将区间等分为 [ a , x 0 ] \left[a,x_0\right] [a,x0] [ x 0 , b ] \left[x_0,b\right] [x0,b],计算 f ( a ) ⋅ f ( x 0 ) f\left(a\right)\cdot f\left(x_0\right) f(a)f(x0) f ( x 0 ) ⋅ f ( b ) f\left(x_0\right)\cdot f\left(b\right) f(x0)f(b),则根必位于其中一区间内,重复上述过程N次直至根所在的区间长度足够小时停止迭代。尽管二分法能够保证最终收敛到根附近,但它也存在着一个极端情况,即每次迭代中根都恰好位于另一区间内,因此时间复杂度为 O = log ⁡ 2 ( N ) O=\log_2{\left(N\right)} O=log2(N)。二分法的流程如下图所示请添加图片描述

  • 源码
import math
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-0.5, 4, 0.01)
y = np.cos(x)
plt.figure(figsize=(10, 8))
plt.plot(x, y,label="f(x)",linewidth=2)
plt.vlines(x=0, ymin=-1.5, ymax=1.5)
plt.hlines(y=0, xmin=-1, xmax=5)
#plt.xlabel("x")
#plt.ylabel("cos(x)")
plt.vlines(x=0.3, ymin=0, ymax=cos(0.3), linestyle="--", color="black")
plt.scatter(0.3, cos(0.3), c = 'yellow', marker = 'o',s=100, edgecolors="black",alpha = 1, linewidths = 1.5)
plt.vlines(x=3.5, ymin=0, ymax=cos(3.5), linestyle="--", color="black")
plt.scatter(3.5, cos(3.5), c = 'yellow', marker = 'o',s=100, edgecolors="black",alpha = 1, linewidths = 1.5)
plt.scatter(0.27, -0.05, c = 'black', marker = '$a$', s=80, alpha = 1, linewidths = 0.5)
plt.scatter(3.5, 0.08, c = 'black', marker = '$b$', s=120, alpha = 1, linewidths = 0.5)
plt.scatter(0.27, cos(0.3)+0.1, c = 'black', marker = '$f(a)$', s=580, alpha = 1, linewidths = 0.5)
plt.scatter(3.5, cos(3.5)-0.1, c = 'black', marker = '$f(b)$', s=580, alpha = 1, linewidths = 0.5)
plt.scatter(np.pi/2-0.05, cos(np.pi/2)-0.1, c = 'black', marker = '$x_0$', s=280, alpha = 1, linewidths = 0.5)
plt.scatter(np.pi/2, cos(np.pi/2), c = 'red', marker = 'o', s=200, edgecolors="black",alpha = 1,linewidths = 1.5)

plt.scatter((0.3+3.5)/2, 0, c = 'red', marker = 'o', s=100, edgecolors="black", alpha = 1, linewidths = 1.5)
plt.scatter((0.3+3.5)/2, 0+0.1, c = 'red', marker = '$a_1$', s=150, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter((0.3+3.5)/2-0.1, cos((0.3+3.5)/2)-0.1, c = 'black', marker = '$f(a_1)$', s=600, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter((0.3+3.5)/2, cos((0.3+3.5)/2), c = 'yellow', marker = 'o', s=100, edgecolors="black", alpha = 1, linewidths = 1.5)
plt.vlines(x=(0.3+3.5)/2, ymin=0, ymax=cos((0.3+3.5)/2), linestyle="--",color="black")
plt.scatter((0.3+3.5)/4, 0, c = 'red', marker = 'o', s=100, edgecolors="black", alpha = 1, linewidths = 1.5)
plt.scatter((0.3+3.5)/4, 0-0.1, c = 'red', marker = '$a_2$', s=150, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.vlines(x=(0.3+3.5)/4, ymin=0, ymax=cos((0.3+3.5)/4), linestyle="--", color="black")
plt.scatter((0.3+3.5)/4, cos((0.3+3.5)/4), c = 'yellow', marker = 'o', s=100, edgecolors="black", alpha = 1, linewidths = 1.5)
plt.scatter((0.3+3.5)/4+0.05, cos((0.3+3.5)/4)+0.1, c = 'black', marker = '$f(a_2)$', s=600, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(1.5, 0, c='red', marker = 'o', s=100, edgecolors="black", alpha = 1, linewidths = 1.5)
plt.scatter(1.38, 0.06, c='black', marker = '$a_3$', s=150, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(-0.1, -0.1, c='black', marker = '$0$', s=150, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(5.05, 0, c='black', marker = '>', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(0, 1.53, c='black', marker = '^', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(-0.2, 1.62, c='black', marker = '$f(x)$', s=600, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(5.0, -0.09, c='black', marker = '$x$', s=130, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.yticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.xticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
#plt.xlabel('x', fontdict=fontdict_ita)
#plt.ylabel("f (x)", fontdict=fontdict_ita)
plt.legend(prop=fontdict_ita)
plt.tight_layout()
plt.show()

牛顿迭代法

牛顿拉夫逊迭代算法又简称为牛顿法,是我们最为熟悉且应用范围最广的数值型计算方法。牛顿迭代法是求方程根的重要方法之一,其最大优点是在方程 f ( x ) = 0 f\left(x\right)=0 f(x)=0的单根附近具有平方收敛,而且该算法还可以用来求方程的重根、复根等,此时线性收敛,但是可通过一些方法变成超线性收敛。另外该方法广泛用于计算机编程中。若设鞍点方程为 f ( t ) f\left(t\right) f(t) r r r是该方程的根,选择 x 0 x_0 x0作为迭代初始值,过点 ( x 0 , f ( x 0 ) ) \left(x_0,f\left(x_0\right)\right) (x0,f(x0))做曲线 f ( t ) f\left(t\right) f(t)的切线 L L L,则根据点斜式有L的表达式为
L : y = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) L:y=f(x_0)+f'(x_0)(x-x_0) L:y=f(x0)+f(x0)(xx0)
该切线与x轴交于
x 1 = x 0 − f ( x 0 ) f ′ ( x 0 ) x_1=x_0-\frac{f(x_0)}{f'(x_0)} x1=x0f(x0)f(x0)
过点 ( x 1 , f ( x 1 ) ) \left(x_1,f\left(x_1\right)\right) (x1,f(x1))再做曲线 f ( x ) f\left(x\right) f(x)的切线得 x 2 x_2 x2,重复上述过程 N N N次直至 x n + 1 − x n ≤ ε x_{n+1}-x_n\le\varepsilon xn+1xnε结束迭代。已经有大量的文献可以证明若方程的根孤立存在,则根的附近存在一个邻域,位于该邻域内的初始迭代值最终必收敛至方程的根。下图给出了牛顿法计算过程的示意
请添加图片描述

x = np.arange(-1, 5, 0.01)
k = 0.55
def f(x):
    return np.exp(k*x)-2.5
def d_f(x):
    return k*exp(k*x)
x0 = 4.5

plt.figure(figsize=(10, 8))
plt.plot(x, f(x), label="f(x)", linewidth=2)
plt.vlines(x=0, ymin=-5, ymax=15, color="black")
plt.hlines(y=0, xmin=-1, xmax=5, color="black")
n=5
for i in range(1, n):
    #cut = -f(x1)*(x1 - x0)/(f(x1) - f(x0)) + x1
    x1 = x0 - f(x0)/d_f(x0)
    xx = np.arange(x1, 5, 0.01)
    yy = f(x0) + d_f(x0)*(xx - x0)
    plt.plot(xx, yy, color="black", linestyle="--", linewidth=0.8)
    plt.scatter(x0, f(x0), c='yellow', marker = 'o', s=size_o , edgecolors="black", alpha = 1, linewidths = 1.5)
    plt.scatter(x0 - 0.18, f(x0) + 0.8, c='black', marker=marker_fx[i-1], s=800 , edgecolors="black", alpha = 1, linewidths = 0.5)
    plt.scatter(x0, -0.5, c='black', marker = marker_x[i-1], s=size_x , edgecolors="black", alpha = 1, linewidths = 0.5)
    plt.vlines(x=x0, ymin=0, ymax=f(x0), linestyle="--")
    x0 = x1
    x1 = cut

plt.yticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.xticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.scatter(5.05, 0, c='black', marker = '>', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(0, 15.3, c='black', marker = '^', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(-0.2, 16.2, c='black', marker = '$f(x)$', s=600, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(5.0, -0.7, c='black', marker = '$x$', s=130, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.legend(prop=fontdict_ita)
plt.tight_layout()
plt.show()

割线法

割线法是牛顿法的一种改进,其思想是将牛顿法中所求方程的切线替换为割线,再将割线与x轴的交点作为根的近似解。给定函数 f ( x ) f\left(x\right) f(x)和函数上两点 ( x 1 , f ( x 1 ) ) \left(x_1,f\left(x_1\right)\right) (x1,f(x1)) ( x 2 , f ( x 2 ) ) \left(x_2,f\left(x_2\right)\right) (x2,f(x2)),则割线L由两点式给出
y − f ( x 1 ) = f ( x 1 ) − f ( x 0 ) x 1 − x 0 ( x − x 1 ) y-f(x_1)=\frac{f(x_1)-f(x_0)}{x_1-x_0}(x-x_1) yf(x1)=x1x0f(x1)f(x0)(xx1)
此时L与 x x x轴交于 x 2 x_2 x2点,重新做过 x 1 x_1 x1 x 2 x_2 x2的割线与横轴交于 x 3 x_3 x3,重复上述过程 N N N次直至 x n − x n − 1 x_n-x_{n-1} xnxn1收敛于给定的误差限。与牛顿法不同的是,割线法需要预先给定两个初始迭代点。其算法过程如下图所示
请添加图片描述

x = np.arange(-1, 5, 0.01)
k = 0.7

def f(x):
    return np.exp(k*x)-2.5
def d_f(x):
    return k * exp(k * x)


x0 = 4.8; f0 = f(x0)
x1 = 3.8; f1 = f(x1)
n = 7

plt.figure(figsize=(10, 8))
plt.plot(x, f(x), linewidth=2.5, label="f(x)")
plt.vlines(x=0, ymin=-5, ymax=f(max(x))+1, color="black")
plt.hlines(y=0, xmin=-1, xmax=5, color="black")

marker_fx = []
marker_x = []
for i in range(1, n):
    lab = "$f(x_" + str(i) + ")$"
    lab2 = "$x_" + str(i) + "$"
    marker_fx.append(lab)
    marker_x.append(lab2)

for i in range(1, n):
    cut = -f(x1)*(x1 - x0)/(f(x1) - f(x0)) + x1
    xx = np.arange(cut, 5, 0.01)
    yy = (xx - x1)*((f(x1) - f(x0))/(x1 - x0)) + f(x1)
    plt.plot(xx, yy, color="black", linestyle="--", linewidth=0.8)
    plt.scatter(x0, f(x0), c='yellow', marker = 'o', s=size_o , edgecolors="black", alpha = 1, linewidths = 1.5)
    plt.scatter(x0 - 0.25, f(x0) + 1, c='black', marker=marker_fx[i-1], s=700 , edgecolors="black", alpha = 1, linewidths = 1)
    plt.scatter(x0, -1.0, c='black', marker = marker_x[i-1], s=size_x , edgecolors="black", alpha = 1, linewidths = 0.5)
    plt.vlines(x=x0, ymin=0, ymax=f(x0), linestyle="--")
    x0 = x1
    x1 = cut
    
plt.yticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.xticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.scatter(5.05, 0, c='black', marker = '>', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(0, 31.3, c='black', marker = '^', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(-0.2, 33.2, c='black', marker = '$f(x)$', s=600, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(5.0, -1.2, c='black', marker = '$x$', s=130, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.legend(prop=fontdict_ita)
plt.tight_layout()
plt.show()

弦截法

弦截法可以看做是二分法的扩展。若 f ( t ) f\left(t\right) f(t)在区间 [ a , b ] \left[a,b\right] [a,b]上连续且f ( a ) ⋅ f ( b ) < 0 \left(a\right)\cdot f\left(b\right)<0 (a)f(b)<0,则 [ [ a , b ] [\left[a,b\right] [[a,b]是含根区间。此时做 f ( t ) f(t) f(t)过A ( a , f ( a ) ) (a,f(a)) (a,f(a)) B ( b , f ( b ) ) B\left(b,f\left(b\right)\right) B(b,f(b))点的弦AB,则根据两点式有
L : y = b − x b − a f ( a ) + x − a b − a f ( b ) L:y=\frac{b-x}{b-a}f(a)+\frac{x-a}{b-a}f(b) L:y=babxf(a)+baxaf(b)
弦AB与x轴交于
x = a − f ( a ) f ( b ) − f ( a ) ( b − a ) x=a-\frac{f(a)}{f(b)-f(a)}(b-a) x=af(b)f(a)f(a)(ba)
并作为 f ( t ) f\left(t\right) f(t) [ a , b ] \left[a,b\right] [a,b]上的根。不断重复上述过程直至含根区间足够小后停止迭代,与二分法不同的是,弦截法每次获得的新区间可能不是等分的,且通过固定b点后不断做弦,此时的搜索速度相较于二分法更快。弦截法的过程如下图所示
请添加图片描述
可见弦截法所得直线L与横轴交点总位于根的左侧,使得 f ( x n ) ⋅ f ( x 0 ) < 0 f\left(x_n\right)\cdot f\left(x_0\right)<0 f(xn)f(x0)<0总是能够被满足。

x = np.arange(-1, 5, 0.01)
k = 0.7

def f(x):
    return np.exp(k*x)-6
def d_f(x):
    return k * exp(k * x)


x0 = 5.0; f0 = f(x0)
x1 = 0.3; f1 = f(x1)
n = 7

plt.figure(figsize=(10, 8))
plt.plot(x, f(x), linewidth=2.5, label="f(x)")
plt.vlines(x=0, ymin=f(min(x))-1, ymax=f(max(x))+1, color="black")
plt.hlines(y=0, xmin=-1, xmax=x0+0.5, color="black")
plt.vlines(x=x0, ymin=0, ymax=f(x0), linestyle="--")


marker_fx = []
marker_x = []
for i in range(1, n):
    lab = "$f(x_" + str(i) + ")$"
    lab2 = "$x_" + str(i) + "$"
    marker_fx.append(lab)
    marker_x.append(lab2)

for i in range(1, n):
    cut = x1 - (x0-x1)*(f(x1))/(f(x0)-f(x1))
    xx = np.arange(x1, 5, 0.01)
    yy = f(x1)*(x0-xx)/(x0-x1) + f(x0)*(xx-x1)/(x0-x1)
    plt.plot(xx, yy, color="black", linestyle="--", linewidth=0.8)
    plt.scatter(x1, f(x1)-1.3, c='black', marker=marker_fx[i-1], s=700 , edgecolors="black", alpha = 1, linewidths = 1) #fx
    plt.scatter(x1, 1.0, c='black', marker = marker_x[i-1], s=size_x , edgecolors="black", alpha = 1, linewidths = 0.5)   #x
    plt.vlines(x=x1, ymin=0, ymax=f(x1), linestyle="--")
    #x0 = x1
    x1 = cut
    
plt.yticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.xticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.scatter(x0, f(x0), c='yellow', marker = 'o', s=size_o , edgecolors="black", alpha = 1, linewidths = 1.5)
plt.scatter(x0+0.5, 0, c='black', marker = '>', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(0, f(max(x))+1, c='black', marker = '^', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(-0.2, f(max(x))+1, c='black', marker = '$f(x)$', s=600, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(x0+0.5, -1.2, c='black', marker = '$x$', s=130, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(4.85, 1, c='black', marker = '$x_0$', s=230, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.legend(prop=fontdict_ita)
plt.tight_layout()
plt.show()

不动点法

不动点迭代法又称简单迭代。我们常用的牛顿法就是不动点迭代算法的特殊形式。为了求解方程 f ( x ) = 0 f\left(x\right)=0 f(x)=0,首先将 f ( x ) f\left(x\right) f(x)转换为 x = g ( x ) x=g\left(x\right) x=g(x)且需要满足 ∣ g ( x ) ∣ < 1 \left|g\left(x\right)\right|<1 g(x)<1,给定迭代初值 x 0 x_0 x0,循环计算 x n + 1 = g ( x n ) x_{n+1}=g\left(x_n\right) xn+1=g(xn)直到 x n + 1 − x n x_{n+1}-x_n xn+1xn小于设定误差限 ε \varepsilon ε。然而该方法并不保证一定收敛至根附近,相反如果初值距离根过远还会导致结果发散。例如当我们想要求解方程 2 x 3 − x − 1 = 0 2x^3-x-1=0 2x3x1=0的根,方程 f ( x ) = 0 f\left(x\right)=0 f(x)=0的转换方式不唯一,可以将其转化为 x = 2 x 3 − 1 x=2x^3-1 x=2x31 x = ( ( x + 1 ) / 2 ) 1 / 3 x=\left(\left(x+1\right)/2\right)^{1/3} x=((x+1)/2)1/3。从几何角度来看求解根的问题转变成为了求解 y = x y=x y=x g ( x ) g\left(x\right) g(x)交点问题。不断计算迭代式后就能得到根的近似值。下图展示不动点迭代算法的过程。
请添加图片描述

import numpy as np
x = np.arange(-5, 5, 0.01)
x0 = 2
def f(x):
    return 2*x**3-x-1

def g(x):
    return pow((x+1)/2,1/3)
def f2(x):
    return x
xk = []
for k in range(20):
    xk1 = g(x0)
    xk.append(g(x0))
    x0 = xk1
import matplotlib.pyplot as plt
fontdict_nor = {'family' : 'Times New Roman', 'size': 18, "style":"normal"}
fontdict_ita = {'family' : 'Times New Roman', 'size': 18, "style":"italic"}
plt.style.use("seaborn")
plt.style.use("seaborn")
x = np.arange(-0.5, 1.5, 0.01)

plt.figure(figsize=(10, 8))

n = len(xk)
marker_fx = []
marker_x = []
for i in range(1, n):
    lab = "$f(x_" + str(i) + ")$"
    lab2 = "$x_" + str(i) + "$"
    marker_fx.append(lab)
    marker_x.append(lab2)


plt.plot(x, f(x), label="f (x)", linewidth=2)
plt.plot(x, g(x), label="g (x)", linestyle="--")
plt.plot(x, f2(x),label="y=x", color="black")

plt.vlines(x=0, ymin=f(min(x))-1, ymax=f(max(x))+1, color="black")
plt.hlines(y=0, xmin=-0.5, xmax=1.5, color="black")

for i in range(len(xk)):
    plt.scatter(xk[i], 0, c='yellow', marker = 'o', s=80, edgecolors="black", alpha = 1, linewidths = 0.5)
    
for i in range(2):
    plt.scatter(xk[i], -0.2, marker = marker_x[i], s=200, edgecolors="black", alpha = 1, linewidths = 0.5)
    
plt.yticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)
plt.xticks(fontstyle = "normal", fontproperties = 'Times New Roman', size = 13)

plt.scatter(x0+0.5, 0, c='black', marker = '>', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(0, f(max(x))+1, c='black', marker = '^', s=100, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(-0.07, f(max(x))+1, c='black', marker = '$f(x)$', s=600, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(1.5, -0.3, c='black', marker = '$x$', s=130, edgecolors="black", alpha = 1, linewidths = 0.5)
plt.scatter(xk[-1], f2(xk[-1]), c='yellow', marker = 'o', s=130, edgecolors="black", alpha = 1, linewidths = 0.5)


plt.vlines(x=xk[-1], ymin=0, ymax=f2(xk[-1]), color="black", linestyle="--")

plt.legend(prop=fontdict_ita)
plt.tight_layout()
plt.show()
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinity343

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值