9.3 分段立方插值

主观认识

  分段立方是一种多项式插值,是将离散的原始数据还原为一个分段的立方函数(三次函数),英文为Cubic Spline Interpolation。其算法叫立方曲线插值算法Cubic spline interpolation algorithm。其基本思想是生成分段的三次多项式,在连接点处可导,也就是多个分段函数组成的整个函数是连续、可导的。
  我举个例子,以下是9个点组成的数据集,组成了9个小向量:
v 0 = ( 1 , 1 ) v 1 = ( 2 , 2 ) v 2 = ( 3 , 1 ) v 3 = ( 4 , − 2 ) v 4 = ( 5 , 1 ) v 5 = ( 6 , 2 ) v 6 = ( 7 , 1 ) v 7 = ( 8 , − 2 ) v 8 = ( 9 , 1 ) v_0=(1,1)\\ v_1=(2,2)\\ v_2=(3,1)\\ v_3=(4,-2)\\ v_4=(5,1)\\ v_5=(6,2)\\ v_6=(7,1)\\ v_7=(8,-2)\\ v_8=(9,1)\\ v0=(1,1)v1=(2,2)v2=(3,1)v3=(4,2)v4=(5,1)v5=(6,2)v6=(7,1)v7=(8,2)v8=(9,1)
  在坐标系上是这个样子:
在这里插入图片描述
  这个数据,凭直觉看,是一个周期函数,但是计算机不知道啊。如果我们用牛顿插值会是什么样子呢?会得到这样一个函数(近似值,用下面这个方程在绘图软件误差特别大):
f ( x ) = 0.0032 x 8 − 0.130 x 7 + 2.2444 x 6 − 21.11 x 5 + 117.378 x 4 − 390.378 x 3 + 747.3746 x 2 − 740.381 x + 286.0 f(x)=0.0032x^8-0.130x^7+2.2444x^6\\ -21.11x^5+117.378x^4 -390.378x^3\\ +747.3746x^2-740.381x+286.0 f(x)=0.0032x80.130x7+2.2444x621.11x5+117.378x4390.378x3+747.3746x2740.381x+286.0
  算了,我给出精确方程吧:
f ( x ) = 0.0031746031746031746 x 8 − 0.13015873015873017 x 7 + 2.244444444444444 x 6 − 21.11111111111111 x 5 + 117.3777777777778 x 4 − 390.37777777777785 x 3 + 747.3746031746031 x 2 − 740.3809523809525 x + 286.0 f(x)=0.0031746031746031746x^8\\ -0.13015873015873017x^7\\ +2.244444444444444x^6\\ -21.11111111111111x^5\\ +117.3777777777778x^4\\ -390.37777777777785x^3\\ +747.3746031746031x^2\\ -740.3809523809525x+286.0 f(x)=0.0031746031746031746x80.13015873015873017x7+2.244444444444444x621.11111111111111x5+117.3777777777778x4390.37777777777785x3+747.3746031746031x2740.3809523809525x+286.0
  在绘图软件(Gnuplot)中得到的图形是这样的:
在这里插入图片描述
  可见偏离特别大,比如1.5,得到的是 − 3.1679687500005684 -3.1679687500005684 3.1679687500005684,而我们预期的是一个在 ( 1 , 2 ) (1,2) (1,2)区间内的数字。
  我们再试试分段立方插值。分段立方插值得到的是 1.6005154639175259 1.6005154639175259 1.6005154639175259,与真实值比较接近。分段立方插值的图像是什么样子的呢?就是下图这个样子:
在这里插入图片描述
  与牛顿/拉格朗日插值对比可以更明显看出差异:
在这里插入图片描述  主观上看,分段立方插值更接近原始数据,更精确。

样本方程

  分段立方插值获取的是分段立方(三次)函数,假设样本有 n n n对数据,那么函数要分成 n − 1 n-1 n1,这些样本点就是边界,首先要保证分段函数要经过样本点。
  为了简便,我们定义 h i = x i + 1 − x i h_i=x_{i+1}-x_{i} hi=xi+1xi
  分段函数的形式是 y = a ( x − x i ) 3 + b ( x − x i ) 2 + c ( x − x i ) + d y=a(x-x_i)^3+b(x-x_i)^2+c(x-x_i)+d y=a(xxi)3+b(xxi)2+c(xxi)+d,定义域为 [ x i , x i + 1 ] [x_i,x_{i+1}] [xi,xi+1]。要穿过样本点,那么意味着当 x = x i x=x_i x=xi时, y = y i y=y_i y=yi,那么我们代进去算一下:
y i = f ( x i ) y i = a i ( x i − x i ) 3 + b i ( x i − x i ) 2 + c i ( x i − x i ) + d i y i = d i y_i=f(x_i)\\ y_i=a_i(x_i-x_i)^3+b_i(x_i-x_i)^2+c_i(x_i-x_i)+d_i\\ y_i=d_i yi=f(xi)yi=ai(xixi)3+bi(xixi)2+ci(xixi)+diyi=di
  所以第一个未知参数 d d d确定了,就等于样本的值 y i y_i yi。那么还剩下三个未知数 a , b , c a,b,c a,b,c。我们主观上看,插值函数形成的曲线是丝滑流畅的,那么在数学上,就是连续、可导的。因为每段函数经过两个点,左边界点是 ( x i , y i ) (x_i,y_i) (xi,yi),右边界点是 ( x i + 1 , y i + 1 ) (x_{i+1},y_{i+1}) (xi+1,yi+1)
  知道这个条件后,我们得到了第二个方程:
y i + 1 = a i ( x i + 1 − x i ) 3 + b i ( x i + 1 − x i ) 2 + c i ( x i + 1 − x i ) + d i y i + 1 = a i ( x i + 1 − x i ) 3 + b i ( x i + 1 − x i ) 2 + c i ( x i + 1 − x i ) + y i y i + 1 = a i h i 3 + b i h i 2 + c i h i + y i y_{i+1}=a_i(x_{i+1}-x_i)^3+b_i(x_{i+1}-x_i)^2+c_i(x_{i+1}-x_i)+d_i\\ y_{i+1}=a_i(x_{i+1}-x_i)^3+b_i(x_{i+1}-x_i)^2+c_i(x_{i+1}-x_i)+y_i\\ y_{i+1}=a_ih_i^3+b_ih_i^2+c_ih_i+y_i yi+1=ai(xi+1xi)3+bi(xi+1xi)2+ci(xi+1xi)+diyi+1=ai(xi+1xi)3+bi(xi+1xi)2+ci(xi+1xi)+yiyi+1=aihi3+bihi2+cihi+yi
  这只是连续,在连接处还需要可导。分段函数的形式是一个复合函数所以我们要用链式法则求导:
u ( x ) = x − x i f ( u ) = a u 3 + b u 2 + c u + d u ′ ( x ) = 1 f ′ ( x ) = f ′ ( u ) ⋅ u ′ ( x ) = 3 a ( x − x i ) 2 + 2 b ( x − x i ) + c u(x)=x-x_i\\ f(u)=au^3+bu^2+cu+d\\ u'(x)=1\\ f'(x)=f'(u)\cdot u'(x)=3a(x-x_{i})^2+2b(x-x_{i})+c u(x)=xxif(u)=au3+bu2+cu+du(x)=1f(x)=f(u)u(x)=3a(xxi)2+2b(xxi)+c
  对于中间的分段函数,左连接点要与前一段在连接处的导数相等,也要与后一段在连接处导数相等。假设前一段的分段函数已经解出来了,成为已知数,定义为g(x),那么我们就得到了第三个方程,起点处的导数等于前一段函数在终点的导数:
f i ′ ( x i ) = f i − 1 ′ ( x i ) 3 a i ( x i − x i ) 2 + 2 b i ( x i − x i ) + c i = 3 a i − 1 ( x i − x i − 1 ) 2 + 2 b i − 1 ( x i − x i − 1 ) + c i − 1 c i = 3 a i − 1 ( x i − x i − 1 ) 2 + 2 b i − 1 ( x i − x i − 1 ) + c i − 1 c i = 3 a i − 1 h i − 1 2 + 2 b i − 1 h i − 1 + c i − 1 f_i'(x_i)=f_{i-1}'(x_i)\\ 3a_i(x_i-x_{i})^2+2b_i(x_i-x_{i})+c_i=3a_{i-1}(x_{i}-x_{i-1})^2+2b_{i-1}(x_i-x_{i-1})+c_{i-1}\\ c_i=3a_{i-1}(x_{i}-x_{i-1})^2+2b_{i-1}(x_i-x_{i-1})+c_{i-1}\\ c_i=3a_{i-1}h_{i-1}^2+2b_{i-1}h_{i-1}+c_{i-1} fi(xi)=fi1(xi)3ai(xixi)2+2bi(xixi)+ci=3ai1(xixi1)2+2bi1(xixi1)+ci1ci=3ai1(xixi1)2+2bi1(xixi1)+ci1ci=3ai1hi12+2bi1hi1+ci1
  此外,在每个分段右边界,也是连续可导的,也就是后一段函数的一阶导相等,又可以得到一个方程:
f i ′ ( x i + 1 ) = f i + 1 ′ ( x i + 1 ) 3 a i ( x i + 1 − x i ) 2 + 2 b i ( x i + 1 − x i ) + c i = 3 a i + 1 ( x i + 1 − x i + 1 ) 2 + 2 b i − 1 ( x i + 1 − x i + 1 ) + c i + 1 3 a i h i 2 + 2 b i h i + c i = c i + 1 f_i'(x_{i+1})=f_{i+1}'(x_{i+1})\\ 3a_i(x_{i+1}-x_{i})^2+2b_i(x_{i+1}-x_{i})+c_i=3a_{i+1}(x_{i+1}-x_{i+1})^2+2b_{i-1}(x_{i+1}-x_{i+1})+c_{i+1}\\ 3a_ih_{i}^2+2b_ih_{i}+c_i=c_{i+1} fi(xi+1)=fi+1(xi+1)3ai(xi+1xi)2+2bi(xi+1xi)+ci=3ai+1(xi+1xi+1)2+2bi1(xi+1xi+1)+ci+13aihi2+2bihi+ci=ci+1
  这样, c c c这个未知数也出来了。分段立方插值还有个设定,分段函数的一阶导必须连续可导,丝滑流畅。其实就是二阶导要相等。为了直观展示,我把上面例子的一阶导用绿色线条画出来:
在这里插入图片描述
  设前一段函数的二阶导为 f i − 1 ′ ′ ( x ) f_{i-1}''(x) fi1(x),因为二阶导的一般方程是 f ′ ′ ( x ) = 6 a ( x − x i ) + 2 b f''(x)=6a(x-x_{i})+2b f(x)=6a(xxi)+2b,连接点处二阶导相等,我们又得到一个方程:
f i ′ ′ ( x ) = 6 a i ( x − x i ) + 2 b i f i ′ ′ ( x i ) = f i − 1 ′ ′ ( x i ) 2 b i = 6 a i − 1 ( x i − x i − 1 ) + 2 b i − 1 b i = 3 a i − 1 h i − 1 + b i − 1 f_i''(x)=6a_i(x-x_{i})+2b_i\\ f_i''(x_i)=f_{i-1}''(x_i)\\ 2b_i=6a_{i-1}(x_i-x_{i-1})+2b_{i-1}\\ b_i=3a_{i-1}h_{i-1}+b_{i-1}\\ fi(x)=6ai(xxi)+2bifi(xi)=fi1(xi)2bi=6ai1(xixi1)+2bi1bi=3ai1hi1+bi1
  也等同于:
a i − 1 = b i − b i − 1 3 h i − 1 a_{i-1}=\frac{b_i-b_{i-1}}{3h_{i-1}} ai1=3hi1bibi1
  与后一段在连接处二阶导相等,我们有:
f i ′ ′ ( x i + 1 ) = f i + 1 ′ ′ ( x i + 1 ) 6 a i ( x i + 1 − x i ) + 2 b i = 6 a i + 1 ( x i + 1 − x i + 1 ) + 2 b i + 1 6 a i h i + 2 b i = 2 b i + 1 3 a i h i + b i = b i + 1 f_{i}''(x_{i+1})=f_{i+1}''(x_{i+1})\\ 6a_{i}(x_{i+1}-x_{i})+2b_{i}=6a_{i+1}(x_{i+1}-x_{i+1})+2b_{i+1}\\ 6a_{i}h_{i}+2b_{i}=2b_{i+1}\\ 3a_{i}h_{i}+b_{i}=b_{i+1} fi(xi+1)=fi+1(xi+1)6ai(xi+1xi)+2bi=6ai+1(xi+1xi+1)+2bi+16aihi+2bi=2bi+13aihi+bi=bi+1
 也等同于:
a i = b i + 1 − b i 3 h i a_{i}=\frac{b_{i+1}-b_{i}}{3h_{i}} ai=3hibi+1bi
  总结下就是四个方程:
d i = y i h i 3 a i + h i 2 b i + h i c i = y i + 1 − y i 3 h i 2 a i + 2 h i b i + c i = c i + 1 3 h i a i + b i = b i + 1 d_i=y_i\\ h_i^3a_i+h_i^2b_i+h_ic_i=y_{i+1}-y_i\\ 3h_{i}^2a_i+2h_{i}b_i+c_i=c_{i+1}\\ 3h_{i}a_{i}+b_{i}=b_{i+1} di=yihi3ai+hi2bi+hici=yi+1yi3hi2ai+2hibi+ci=ci+13hiai+bi=bi+1
  除去已经有解的 d i d_i di,还剩三个方程,却有5个未知数,所以还差一些方程。

首末分段

  这么依赖前一段函数,也就是要求出前段函数的 a , b , c a,b,c a,b,c三个未知数,但是对于第一段函数,是没有前一段函数的,怎么办呢?
  这就需要讲讲分段立方插值的两个重要设定了,起点二阶导为0与终点二阶导为0。为什么要这两个设定呢?我们可以用物理学的概念来理解,二阶导相当于加速度,而加速度源于力,这其实假设起点和终点是受力平衡的状态。在有些文章里是写错了的,我举个例子,国外著名的网站,就把首末端二阶导为0写成了首末端一阶导为0,这就不够严谨了,哈哈:
在这里插入图片描述
  但是对于其他的英文网站,我推荐这篇加州伯克利写的文章
  对于第一段函数,二阶导方程如下:
f ′ ′ ( x 0 ) = 0 6 a 0 ( x 0 − x 0 ) + 2 b 0 = 0 2 b 0 = 0 f''(x_0)=0\\ 6a_0(x_0-x_0)+2b_0=0\\ 2b_0=0 f(x0)=06a0(x0x0)+2b0=02b0=0
  所以第一段函数 b 0 = 0 b_0=0 b0=0,这就减少一个了一个未知数。对于终点的二阶导为0,假设终点为 ( x n , y n ) (x_n,y_n) (xn,yn)我们计算一下:
f ′ ′ ( x n ) = 6 a n − 1 ( x n − x n − 1 ) + 2 b n − 1 = 0 3 a n − 1 ( x n − x n − 1 ) + b n − 1 = 0 f''(x_{n})=6a_{n-1}(x_{n}-{x_{n-1}})+2b_{n-1}=0\\ 3a_{n-1}(x_{n}-{x_{n-1}})+b_{n-1}=0\\ \\ f(xn)=6an1(xnxn1)+2bn1=03an1(xnxn1)+bn1=0
  哈哈,但是知道了这个方程,怎么与第一分段二阶导联系起来呢?
  因为n个数据点,那么有 n − 1 n-1 n1个分段函数,除去 b 0 = 0 b_0=0 b0=0总共有 3 n − 4 3n-4 3n4个未知数。每一个段都有这个方程:
h i 3 a i + h i 2 b i + h i c i = y i + 1 − y i h_i^3a_i+h_i^2b_i+h_ic_i=y_{i+1}-y_i hi3ai+hi2bi+hici=yi+1yi
  那么就现在就有了 n − 1 n-1 n1个方程。每一段和后一段的关系,有以下两个方程:
3 h i 2 a i + 2 h i b i + c i = c i + 1 3 h i a i + b i = b i + 1 3h_{i}^2a_i+2h_{i}b_i+c_i=c_{i+1}\\ 3h_{i}a_{i}+b_{i}=b_{i+1} 3hi2ai+2hibi+ci=ci+13hiai+bi=bi+1
  那么就有 2 n − 4 2n-4 2n4个方程,加起来有 3 n − 5 3n-5 3n5个方程。再加上 3 a n − 1 ( x n − x n − 1 ) + b n − 1 = 0 3a_{n-1}(x_{n}-{x_{n-1}})+b_{n-1}=0 3an1(xnxn1)+bn1=0,这个方程,总共有 3 n − 4 3n-4 3n4个方程。未知数与方程的数量恰好相等,然后就可以解方程求出所有系数了。

泰勒级数

  大学时微积分最后一课就是泰勒级数和麦克劳林级数。大部分时候我们觉得这除了用来计算sin(x)和cos(x)的近似值外没什么用,而且真正到了计算机数值计算领域,已经是用切比雪夫近似值来计算了,不用泰勒级数了。原因很简单,泰勒级数一不精确,二性能低下。
  我们对任意一段函数进行泰勒级数展开:
f i ( x ) = f i ( x i ) + f i ′ ( x i ) ( x − x i ) + f i ′ ′ ( x i ) 2 ! ( x − x i ) 2 + f i ′ ′ ′ ( x i ) 3 ! ( x − x i ) 3 f_i(x)=f_i(x_i)+f'_i(x_i)(x-x_i)+\frac{f''_i(x_i)}{2!}(x-x_i)^2+\frac{f'''_i(x_i)}{3!}(x-x_i)^3 fi(x)=fi(xi)+fi(xi)(xxi)+2!fi(xi)(xxi)2+3!fi(xi)(xxi)3
  再看看我们的公式:
f i ( x ) = a i ( x − x i ) 3 + b i ( x − x i ) 2 + c i ( x − x i ) + d i f_i(x)=a_i(x-x_i)^3+b_i(x-x_i)^2+c_i(x-x_i)+d_i fi(x)=ai(xxi)3+bi(xxi)2+ci(xxi)+di
  对比一下,换个顺序,就会发现:
a i = f i ′ ′ ′ ( x i ) 3 ! b i = f i ′ ′ ( x i ) 2 ! c i = f i ′ ( x i ) d i = f i ( x i ) a_i=\frac{f'''_i(x_i)}{3!}\\ b_i=\frac{f''_i(x_i)}{2!}\\ c_i=f'_i(x_i)\\ d_i=f_i(x_i) ai=3!fi(xi)bi=2!fi(xi)ci=fi(xi)di=fi(xi)
  哈哈,数学是不是特别有意思?

μ \mu μ数列与z数列

  直接这样解方程,程序性能比较低,在实际生产中,我们使用 μ \mu μ数列与z数列法。我们换个思路,找到一种更简便的方法,而不是强解 3 n − 4 3n-4 3n4元一次方程组。
   μ \mu μ数列的定义是:
μ i = { x i + 1 − x i 2 ( x i + 1 − x i − 1 ) − μ i − 1 ( x i − x i − 1 ) , i ∈ Z + 0 , i = 0 \mu_i=\left\{ \begin{aligned} \frac{x_{i+1}-x_{i}}{2(x_{i+1}-x_{i-1})-\mu_{i-1}(x_i-x_{i-1})},i \in \mathbb{Z^+}\\ 0,i=0 \end{aligned} \right. μi=2(xi+1xi1)μi1(xixi1)xi+1xi,iZ+0,i=0
   z z z数列的定义是:
z i = { 3 [ y i + 1 ( x i − x i − 1 ) − y i ( x i + 1 − x i − 1 ) + y i − 1 ( x i + 1 − x i ) ] ( x i − x i − 1 ) ( x i + 1 − x i ) − ( x i − x i − 1 ) z i − 1 2 ( x i + 1 − x i − 1 ) − μ i − 1 ( x i − x i − 1 ) , i ∈ Z + 0 , i = 0 z_i=\left\{ \begin{aligned} \frac { \frac{3[y_{i+1}(x_{i}-x_{i-1})-y_i(x_{i+1}-x_{i-1})+y_{i-1}(x_{i+1}-x_i)]} {(x_i-x_{i-1})(x_{i+1}-x_i)} -(x_i-x_{i-1})z_{i-1}}{2(x_{i+1}-x_{i-1})-\mu_{i-1}(x_i-x_{i-1})},i \in \mathbb{Z^+}\\ 0,i=0 \end{aligned} \right. zi=2(xi+1xi1)μi1(xixi1)(xixi1)(xi+1xi)3[yi+1(xixi1)yi(xi+1xi1)+yi1(xi+1xi)](xixi1)zi1,iZ+0,i=0
  因为公式过于复杂,所以使用我们之前定义的 h i = x i + 1 − x i h_i=x_{i+1}-x_{i} hi=xi+1xi,将公式改写一下:
μ i = { h i 2 ( x i + 1 − x i − 1 ) − μ i − 1 h i − 1 , i ∈ Z + 0 , i = 0 z i = { 3 [ y i + 1 h i − 1 − y i ( x i + 1 − x i − 1 ) + y i − 1 h i ] h i − 1 h i − h i − 1 z i − 1 2 ( x i + 1 − x i − 1 ) − μ i − 1 h i − 1 , i ∈ Z + 0 , i = 0 \mu_i=\left\{ \begin{aligned} \frac{h_{i}}{2(x_{i+1}-x_{i-1})-\mu_{i-1}h_{i-1}},i \in \mathbb{Z^+}\\ 0,i=0 \end{aligned} \right.\\ z_i=\left\{ \begin{aligned} \frac { \frac{3[y_{i+1}h_{i-1}-y_i(x_{i+1}-x_{i-1})+y_{i-1}h_i]} {h_{i-1}h_i} -h_{i-1}z_{i-1}}{2(x_{i+1}-x_{i-1})-\mu_{i-1}h_{i-1}},i \in \mathbb{Z^+}\\ 0,i=0 \end{aligned} \right. μi=2(xi+1xi1)μi1hi1hi,iZ+0,i=0zi=2(xi+1xi1)μi1hi1hi1hi3[yi+1hi1yi(xi+1xi1)+yi1hi]hi1zi1,iZ+0,i=0
  搞这么复杂有什么用?哈哈,因为:
b n − 1 = z n − 1 b_{n-1}=z_{n-1} bn1=zn1
  并且有:
b i = z i − μ i b i + 1 , i < n − 1 b_i=z_i-\mu_ib_{i+1},i<n-1 bi=ziμibi+1,i<n1
  得出了系数b,其他的a和c两个系数就可以很快求出来了。具体推导过程我就不去证明了,使用这两个数列,计算过程比直接解方程快多了。 μ \mu μ数列与z数列的公式特别复杂,我强烈建议收藏本文!

Python实现( μ \mu μz数列法)

  首先简单实现一个霍纳多项式

# _*_ coding:utf-8 _*_

class HornerPolynomial:

    def __init__(self, items: list):
        self.__polynomial = items

    @property
    def polynomial(self):
        return self.__polynomial

    # 本身是个函数
    def __call__(self, *args, **kwargs):
        result = 0
        for coefficient in self.polynomial:
            result = result * args[0] + coefficient
        return result

    def __str__(self):
        result = ''
        first = True
        n= len(self.polynomial)
        for i, coefficient in enumerate(self.polynomial):
            if coefficient == 0:
                continue
            if first:
                first = False
            else:
                result += '+'
            if coefficient != 1:
                result += str(coefficient) + '*'
            order = n - i - 1
            if order > 0:
                result += 'x'
            else:
                result += '1'
            if order > 1:
                result += '**' + str(order)
        return result

  再实现立方曲线插值:

# _*_ coding:utf-8 _*_
from com.youngthing.mathalgorithm.interpolation.horner_polynominal import HornerPolynomial


class PeacewisePolynomials:

    def __init__(self, konts, polynomials):
        self.__konts = konts
        self.__polynomials = polynomials

    def __call__(self, *args, **kwargs):
        x = args[0]
        for i, e in enumerate(self.__polynomials):
            if x >= self.__konts[i] and x <= self.__konts[i + 1]:
                return e(x - self.__konts[i])
        return None

    def __str__(self):
        x = ''
        for i, e in enumerate(self.__polynomials):
            s = '' + str(e) + ',[' + str(self.__konts[i]) + ',' + str(self.__konts[i + 1]) + ']\n'
            x += s
        return x


def getx(value):
    return value[0]


def interpolate(values):
    values = sorted(values, key=getx)
    n = len(values) - 1

    h = [0] * n
    for i in range(0, n):
        h[i] = getx(values[i + 1]) - getx(values[i])

    mu = [0] * n
    z = [0] * (n + 1)

    x = [getx(e) for e in values]
    y = [e[1] for e in values]
    for i in range(1, n):
        g = 2 * (x[i + 1] - x[i - 1]) - h[i - 1] * mu[i - 1]
        mu[i] = h[i] / g
        temp = 3 * (y[i + 1] * h[i - 1] - y[i] * (x[i + 1] - x[i - 1]) + y[i - 1] * h[i])
        z[i] = (temp / (h[i - 1] * h[i]) - h[i - 1] * z[i - 1]) / g

    c = [0] * n
    b = [0] * (n + 1)
    a = [0] * n

    for j in range(n - 1, -1, -1):
        b[j] = z[j] - mu[j] * b[j + 1];
        c[j] = (y[j + 1] - y[j]) / h[j] - h[j] * (b[j + 1] + 2 * b[j]) / 3
        a[j] = (b[j + 1] - b[j]) / (3 * h[j])

    polys = [HornerPolynomial([a[i], b[i], c[i], y[i]]) for i in range(0, n)]
    return PeacewisePolynomials(x, polys)

代码测试

from com.youngthing.mathalgorithm.interpolation.cubic_spline import *


if __name__ == '__main__':
    values=[
        [1,1],
        [2,2],
        [3,1],
        [4, -2],
        [5, 1],
        [6, 2],
        [7, 1],
        [8, -2],
        [9, 1],
    ]
    #测试数据
    cubic = interpolate(values)

    print(cubic(1.5))
    print(cubic)

  测试结果完全正确:

1.6005154639175259
-0.26804123711340205*x**3+1.268041237113402*x+1,[1,2]
-0.6597938144329897*x**3+-0.8041237113402062*x**2+0.463917525773196*x+2*1,[2,3]
2.9072164948453607*x**3+-2.783505154639175*x**2+-3.1237113402061856*x+1,[3,4]
-2.9690721649484533*x**3+5.9381443298969065*x**2+0.030927835051546726*x+-2*1,[4,5]
0.9690721649484534*x**3+-2.9690721649484533*x**2+3.0*x+1,[5,6]
-0.9072164948453607*x**3+-0.06185567010309301*x**2+-0.030927835051546282*x+2*1,[6,7]
2.6597938144329896*x**3+-2.783505154639175*x**2+-2.8762886597938144*x+1,[7,8]
-1.731958762886598*x**3+5.195876288659794*x**2+-0.463917525773196*x+-2*1,[8,9]
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

醒过来摸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值