自学内容网 自学内容网

分段三次样条

一、插值和样条?

给定若干已知点位,连接、逼近这些点位的方式有很多种,我们既可以给定一个足够阶数的多项式一次性穿过、逼近这些点位,这些方法可以是:牛顿插值法、拉格朗日插值法;也可以按段给出每个区间的表达式,比如说三次样条。
请添加图片描述
上面展示的就是按段给出的表达式,按段一条条给出的,所以叫做样条。当然你也可以用一个多项式穿过这些点:

请添加图片描述

红色的线就是用一个多项式连接的方法。用一个多项式拟合所有的点位不都是好的,尤其是均匀分布的插值点两端因为它会产生龙格现象:

请添加图片描述

从上图可以看出,左右两端的拟合对于原函数完全不匹配。缓解方法有很多种,可以使用切比雪夫或者分段三次样条进行处理。也就是文章的重点,分段三次多项式样条。

二、分段三次样条(自然边界条件)

样条的表达式是通过多个分段函数给出的,约束条件有:

  • 满足节点自变量递增关系;
  • 相邻多项式之间一二阶导数连续;
  • 多项式的次数是3次;

如果每相邻两个点之间的连线是三次多项式,那么对应的整段组合而成的曲线就叫做三次样条函数。假设点数是 n + 1 n+1 n+1,那么段数为: n n n

  • 三次多项式有四个未知数,一共有 n n n 个,所以待求的方程组个数为 4 n 4n 4n
  • 一共有 n + 1 n+1 n+1 个点,我已有 ( n + 1 ) (n+1) (n+1) 个方程组,这样一来我就剩下 4 n − ( n + 1 ) = 3 n − 1 4n-(n+1)=3n-1 4n(n+1)=3n1
  • 一共有 n − 1 n-1 n1 个衔接点,衔接点处函数值连续、一阶导连续,二阶导连续,提供 3 ( n − 1 ) 3(n-1) 3(n1)个方程,剩余 3 n − 1 − 3 ( n − 1 ) = 2 3n-1-3(n-1)=2 3n13(n1)=2

根据不同的约束可以将三次样条分为三类:

  • 端点处二阶导函数值约束,如果都是0,则为自然样条;
  • 端点处一阶导数约束;
  • 周期约束,略;

给定以下插值点:

x x x t 0 t_0 t0 t 1 t_1 t1 ⋯ \cdots t n t_n tn
y y y y 0 y_0 y0 y 1 y_1 y1 ⋯ \cdots y n y_n yn

根据这个表格结合三次样条多项式的定义:
S ( x ) = { S 0 ( x ) x ∈ [ t 0 , t 1 ] S 1 ( x ) x ∈ [ t 1 , t 2 ] ⋮ S n − 1 ( x ) x ∈ [ t n − 1 , t n ] S(x)=\left\{ \begin{aligned} &S_0(x)\quad x\in[t_0,t_1]\\ &S_1(x)\quad x\in[t_1,t_2]\\ &\vdots\\ &S_{n-1}(x)\quad x\in[t_{n-1},t_n]\\ \end{aligned} \right. S(x)= S0(x)x[t0,t1]S1(x)x[t1,t2]Sn1(x)x[tn1,tn]

在此之前我们先来推导区间 [ t i , t i + 1 ] [t_i,t_{i+1}] [ti,ti+1] S i ( x ) S_i(x) Si(x) 的表达式。首先我们定义一组数 z i = S ′ ′ ( t i ) z_i=S''(t_i) zi=S′′(ti),因为 S ′ ′ S'' S′′ 在每个内结点上连续,所以显然,对于 0 ≤ i ≤ n 0\le i\le n 0in 存在 z i z_i zi 满足:
lim ⁡ x → t i + S ′ ′ ( x ) = lim ⁡ x → t i − S ′ ′ ( x ) \lim_{x\rightarrow t_i^+}S''(x)=\lim_{x\rightarrow t_i^-}S''(x) xti+limS′′(x)=xtilimS′′(x)
因为 S i S_i Si 是在 [ t i , t i + 1 ] [t_i,t_{i+1}] [ti,ti+1] 上的三次多项式,因此是满足 S i ′ ′ ( t i ) = z i S''_i(t_i)=z_i Si′′(ti)=zi S i ′ ′ ( t i + 1 ) = z i + 1 S''_i(t_{i+1})=z_{i+1} Si′′(ti+1)=zi+1 的线性函数,根据根据拉格朗日插值法有:
S i ′ ′ ( x ) = z i h i ( t i + 1 − x ) + z i + 1 h i ( x − t i ) S''_i(x)=\frac{z_i}{h_i}(t_{i+1}-x)+\frac{z_{i+1}}{h_i}(x-t_i) Si′′(x)=hizi(ti+1x)+hizi+1(xti)

其中 h i = t i + 1 − t i h_i=t_{i+1}-t_i hi=ti+1ti 。为了利用插值点,我们对 ( 3 ) (3) (3) 对变量 x x x积分两次有:
S i ( x ) = z i 6 h i ( t i + 1 − x ) 3 + z i + 1 6 h i ( x − t i ) 3 + C ( x − t i ) + D ( t i + 1 − x ) S_i(x)=\frac{z_i}{6h_i}(t_{i+1}-x)^3+\frac{z_{i+1}}{6h_i}(x-t_i)^3+C(x-t_i)+D(t_{i+1}-x) Si(x)=6hizi(ti+1x)3+6hizi+1(xti)3+C(xti)+D(ti+1x)

这里直接用整体法进行积分不要直接拆开会简单一些

其中 C C C D D D 是积分常数。

代入插值条件 S i ( t i ) = y i S_i(t_i)=y_i Si(ti)=yi S i ( t i + 1 ) = y i + 1 S_i(t_{i+1})=y_{i+1} Si(ti+1)=yi+1 作用在 S i S_i Si 就可以去确定 C C C D D D 两个常数,其结果为:

S i ( x ) = z i 6 h i ( t i + 1 − x ) 3 + z i + 1 6 h i ( x − t i ) 3 + ( y i + 1 h i − z i + 1 h i 6 ) ( x − t i ) + ( y i h i − z i h i 6 ) ( t i + 1 − x ) \begin{aligned} S_i(x) = \frac{z_i}{6h_i}(t_{i+1} - x)^3 + \frac{z_{i+1}}{6h_i}(x - t_i)^3 + \left(\frac{y_{i+1}}{h_i} - \frac{z_{i+1} h_i}{6}\right)(x - t_i) + \left(\frac{y_i}{h_i} - \frac{z_i h_i}{6}\right)(t_{i+1} - x) \end{aligned} Si(x)=6hizi(ti+1x)3+6hizi+1(xti)3+(hiyi+16zi+1hi)(xti)+(hiyi6zihi)(ti+1x)

此时整个方程只有二阶导数是未知的,为了利用一阶导数的连续性,我们对 ( 5 ) (5) (5) 微分一次代入 S i − 1 ′ ( t i ) = S i ′ ( t i ) S'_{i-1}(t_i)=S_i'(t_i) Si1(ti)=Si(ti) 条件:
S i ′ ( t i ) = − h i 3 z i − h i 6 z i + 1 − y i h i + y i + 1 h i S'_i(t_i)=-\frac{h_i}{3}z_i-\frac{h_i}{6}z_{i+1}-\frac{y_i}{h_i}+\frac{y_{i+1}}{h_i} Si(ti)=3hizi6hizi+1hiyi+hiyi+1
同理,可以求得 S i − 1 ′ ( t i ) S_{i-1}'(t_i) Si1(ti)
S i − 1 ′ ( t i ) = h i − 1 6 z i − 1 + h i − 1 3 z i − y i h i − 1 + y i h i − 1 S'_{i-1}(t_i)=\frac{h_{i-1}}{6}z_{i-1}+\frac{h_{i-1}}{3}z_i-\frac{y_i}{h_{i-1}}+\frac{y_i}{h_{i-1}} Si1(ti)=6hi1zi1+3hi1zihi1yi+hi1yi
因为一阶导数连续,所以:
h i − 1 z i − 1 + 2 ( h i + h i − 1 ) z i + h i z i + 1 = 6 h i ( y i + 1 − y i ) − 6 h i − 1 ( y i − y i − 1 ) h_{i-1}z_{i-1}+2(h_i+h_{i-1})z_i+h_iz_{i+1}=\frac{6}{h_i}(y_{i+1}-y_i)-\frac{6}{h_{i-1}}(y_i-y_{i-1}) hi1zi1+2(hi+hi1)zi+hizi+1=hi6(yi+1yi)hi16(yiyi1)
上面的方程对于 i = 1 , 2 , ⋯   , n − 1 i=1,2,\cdots,n-1 i=1,2,,n1 成立。注意这个式子没有对两个端点成立,这是以为他们前面和后面都没有对应的三次多项式,所以我们给出了一个含有 n + 1 n+1 n+1 个未知量 z 0 , z 1 , ⋯   , z n z_0,z_1,\cdots,z_n z0,z1,,zn 的一个 n − 1 n-1 n1 个方程的线性方程组。只要任选 z 0 z_0 z0 z 1 z_1 z1 就可以计算出确定这个分段多项式方程。最常见的方法就是取 z 0 = z n = 0 z_0=z_n=0 z0=zn=0,由此得到的样条函数称为自然三次样条。对于 1 ≤ i ≤ n − 1 1\le i\le n-1 1in1 z 0 = z n = 0 z_0=z_n=0 z0=zn=0
h 0 z 0 + 2 ( h 1 + h 0 ) z 1 + h 1 z 2 = 6 h 1 ( y 2 − y 1 ) − 6 h 0 ( y 1 − y 0 ) h 1 z 1 + 2 ( h 2 + h 1 ) z 2 + h 2 z 3 = 6 h 2 ( y 3 − y 2 ) − 6 h 1 ( y 2 − y 1 ) h 2 z 2 + 2 ( h 3 + h 2 ) z 3 + h 3 z 4 = 6 h 3 ( y 4 − y 3 ) − 6 h 2 ( y 3 − y 2 ) ⋯ h n − 2 z n − 2 + 2 ( h n − 1 + h n − 2 ) z n − 1 + h n − 1 z n = 6 h n − 1 ( y n − y n − 1 ) − 6 h n − 2 ( y n − 1 − y n − 2 ) h_0z_0+2(h_1+h_0)z_1+h_1z_2=\frac{6}{h_1}(y_2-y_1)-\frac{6}{h_0}(y_1-y_0)\\ h_1z_1+2(h_2+h_1)z_2+h_2z_3=\frac{6}{h_2}(y_3-y_2)-\frac{6}{h_1}(y_2-y_1)\\ h_2z_2+2(h_3+h_2)z_3+h_3z_4=\frac{6}{h_3}(y_4-y_3)-\frac{6}{h_2}(y_3-y_2)\\ \cdots\\ h_{n-2}z_{n-2}+2(h_{n-1}+h_{n-2})z_{n-1}+h_{n-1}z_n=\frac{6}{h_{n-1}}(y_{n}-y_{n-1})-\frac{6}{h_{n-2}}(y_{n-1}-y_{n-2})\\ h0z0+2(h1+h0)z1+h1z2=h16(y2y1)h06(y1y0)h1z1+2(h2+h1)z2+h2z3=h26(y3y2)h16(y2y1)h2z2+2(h3+h2)z3+h3z4=h36(y4y3)h26(y3y2)hn2zn2+2(hn1+hn2)zn1+hn1zn=hn16(ynyn1)hn26(yn1yn2)
u i = 2 ( h i + h i − 1 ) u_i=2(h_i+h_{i-1}) ui=2(hi+hi1) b i = 6 h i ( y i + 1 − y i ) b_i=\frac{6}{h_i}(y_{i+1}-y_i) bi=hi6(yi+1yi) v i = b i − b i − 1 v_i=b_i-b_{i-1} vi=bibi1
h 0 z 0 + u 1 z 1 + h 1 z 2 = v 1 h 1 z 1 + u 2 z 2 + h 2 z 3 = v 2 h 2 z 2 + u 3 z 3 + h 3 z 4 = v 3 ⋯ h n − 2 z n − 2 + u n − 1 z n − 1 + h n − 1 z n = v n − 1 h_0z_0+u_1z_1+h_1z_2=v_1\\ h_1z_1+u_2z_2+h_2z_3=v_2\\ h_2z_2+u_3z_3+h_3z_4=v_3\\ \cdots\\ h_{n-2}z_{n-2}+u_{n-1}z_{n-1}+h_{n-1}z_n=v_{n-1}\\ h0z0+u1z1+h1z2=v1h1z1+u2z2+h2z3=v2h2z2+u3z3+h3z4=v3hn2zn2+un1zn1+hn1zn=vn1
写成矩阵形式

[ u 1 h 1 h 1 u 2 h 2 h 2 u 3 h 3 ⋮ h n − 3 u n − 2 h n − 2 h n − 2 u n − 1 ] [ z 1 z 2 z 3 ⋮ z n − 2 z n − 1 ] = [ v 1 v 2 v 3 ⋮ v n − 2 v n − 1 ] \begin{bmatrix} u_1&h_1& & & &\\ h_1&u_2&h_2& & &\\ &h_2&u_3&h_3 && &\\ \vdots\\ & & & h_{n-3}&u_{n-2}&h_{n-2}\\ & & & &h_{n-2}&u_{n-1} \end{bmatrix}\begin{bmatrix}z_1\\z_2\\z_3\\\vdots\\z_{n-2}\\z_{n-1}\end{bmatrix} =\begin{bmatrix}v_1\\v_2\\v_3\\\vdots\\v_{n-2}\\v_{n-1}\end{bmatrix} u1h1h1u2h2h2u3h3hn3un2hn2hn2un1 z1z2z3zn2zn1 = v1v2v3vn2vn1
其中
h i = t i + 1 − t i u i = 2 ( h i + h i − 1 ) b i = 6 h i ( y i + 1 − y i ) v i = b i − b i − 1 \begin{aligned} h_i&=t_{i+1}-t_i\\ u_i&=2(h_i+h_{i-1})\\ b_i&=\frac{6}{h_i}(y_{i+1}-y_i)\\ v_i&=b_i-b_{i-1} \end{aligned} hiuibivi=ti+1ti=2(hi+hi1)=hi6(yi+1yi)=bibi1
( 11 ) (11) (11) 的系数矩阵是一个三对角矩阵,求解出对应的二阶导数值 z i z_i zi 结合 ( 5 ) (5) (5) 便可得到任意区间对应的插补函数值。

三、实现

为了求得每段的表达式,我们需要求解 ( 11 ) (11) (11) 这个线性方程组,简单用python实现了以下并将其与标准库对比:

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline
from scipy.linalg import solve_banded

# 给定数据点
P = np.array(
    [[2, 3], [3, 5], [7, 11], [10, 20], [14, 10], [28, 34], [33, 23]],
)

x = P[:, 0]
y = P[:, 1]


def getUHV(x,y):
    h=np.zeros(len(x)-1)
    u=np.zeros(len(h)-1)
    b=np.zeros(len(y)-1)
    v=np.zeros(len(b)-1)
    
    h=np.diff(x)
    
    for i in range(len(u)):
        u[i]=2*(h[i+1]+h[i])
    
    for i in range(len(b)):
        b[i]=6*(y[i+1]-y[i])/h[i]
    
    v=np.diff(b)
    
    return u,h,v

def getZ(u,h,v):
    
    # | u_1     h_1     0       0      ...      0        0     | | z_1     |   = | v_1     |
    # | h_1     u_2     h_2     0      ...      0        0     | | z_2     |   = | v_2     |
    # | 0       h_2     u_3     h_3    ...      0        0     | | z_3     |   = | v_3     |
    # | .       .       .       .      ...      .        .     | | .       |   = | .       |
    # | .       .       .       .      ...      .        .     | | .       |   = | .       |
    # | .       .       .       .      ...      .        .     | | .       |   = | .       |
    # | 0       0       0       ...    h_{n-3} u_{n-2} h_{n-2} | | z_{n-2} |   = | v_{n-2} |
    # | 0       0       0       ...    0       h_{n-2} u_{n-1} | | z_{n-1} |   = | v_{n-1} |
    M=np.zeros((len(v),len(v)))
    np.fill_diagonal(M, u)
    np.fill_diagonal(M[1:],h[1:])
    np.fill_diagonal(M[:,1:], h[1:])
    z=np.linalg.solve(M,v)
    z=np.insert(z,0,0)
    z=np.append(z,0)
    
    return z
    
def cubicInterpolation(xx, z):
    n = len(x) - 1
    value = np.zeros_like(xx)
    count = 0

    for xi in xx:
        for k in range(n):
            if x[k] <= xi <= x[k + 1]: # chech which interval x falls into
                s1 = ((x[k + 1] - xi) ** 3) * (z[k] / (6 * h[k]))
                s2 = ((xi - x[k]) ** 3) * (z[k + 1] / (6 * h[k]))
                s3 = (y[k + 1] / h[k] - z[k + 1] * h[k] / 6) * (xi - x[k])
                s4 = (y[k] / h[k] - z[k] * h[k] / 6) * (x[k + 1] - xi)
                value[count] = s1 + s2 + s3 + s4
                count += 1
                break

    return value


# 自定义的三次样条插值
u, h, v = getUHV(x, y)
z = getZ(u, h, v)
xx = np.linspace(min(x), max(x), 50000)
yy_custom = cubicInterpolation(xx, z)

# 使用标准库进行三次样条插值
cs = CubicSpline(x, y, bc_type="natural")
yy_scipy = cs(xx)

# 绘图
plt.figure(figsize=(10, 6))
plt.plot(x, y, "o", label="Data points")
plt.plot(xx, yy_custom, "-", label="Custom cubic spline interpolation")
plt.plot(xx, yy_scipy, "--", label="Scipy cubic spline interpolation")
plt.legend()
plt.xlabel("x")
plt.ylabel("y")
plt.title("Comparison of Custom and Scipy Cubic Spline Interpolation")
plt.show()

请添加图片描述

在实际工程实现中,通常直接通过求解线性方程组的方式进行求解,利用三对角方程的性质递归求解系数比较合适。当然对于空间中的点需要进行一些参数化处理,对C++ 代码实现感兴趣,可以参考我上传的在github的代码:https://github.com/FISHFISHFISHLOVECAT/cublic_spline_curve

请添加图片描述

四、优缺点分析

分段三次多项式的缺点

  • 三次只能保证段和段之间的一阶和二阶之间的连续性;
  • 对于非常密集的点,为了准确通过这些点可能导致的过拟合(也就是龙格现象);
  • 是一种局部插补的方法,可能从整体上看不是我们想要的趋势;

可以想象,如果你有一条曲线,通过几个数据点在这条曲线附近取样,然后尝试用一段段的三次多项式将这些点连起来。如果这些点非常密集且分布不均匀,插值曲线可能会在不同点之间“剧烈地”上下摆动以适应所有点,而这种摆动可能并不代表数据的真实趋势,而只是为了准确地通过所有给定的点。这就是过拟合的核心问题——在追求高精度拟合时,失去了对整体趋势的把握。

请添加图片描述


[1]李庆扬 王能超.数值分析(第4版)[M].华中科技大学出版社(原华中理工出版社)出版社,2006.
[2]金凯德,WardCheney,金凯德,等.数值分析[M].机械工业出版社,2005.


原文地址:https://blog.csdn.net/weixin_39258979/article/details/143014207

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!