Night关于数学的杂谈-插值法

本文详细介绍了插值法的基本概念及几种常见的插值方法,包括牛顿插值法、拉格朗日插值法和重心插值法等。通过对比不同方法的优缺点,帮助读者理解各种插值法的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

插值法是什么

插值,就是给定一定的离散数据点,范围内估计新数据点的过程或方法。在这个过程中,我们当然希望得到一个连续的光滑曲线同时经过所有的 (xi,yi)(xi,yi) ,并求得该曲线在需要求值的点上的值。
具体定义如下:
给定 nn 个离散数据点 (xi,yi) (i[1,n]),对于 x (xxi,i[1,n])x (x≠xi,i∈[1,n]),求 xx 所对应的 y 值称之为 内插
f(x)f(x) 为定义在区间 [a,b][a,b] 上的函数。x1,x2,,xnx1,x2,⋯,xn[a,b][a,b]nn 个互不相同的点,G 为给定的某一函数类。若 GG 上有函数 g(x) 满足:

g(xi)=f(xi) , i[1,n]g(xi)=f(xi) , i∈[1,n]
则称 g(x)g(x)f(x)f(x) 关于节点 x1,x2,,xnx1,x2,⋯,xnGG 上的插值函数
(摘自维基百科)


牛顿插值法

差商

给定函数 f(x) 和插值节点 x0,x1,,xnx0,x1,⋯,xn,用 f[x0,x1,,xk]f[x0,x1,⋯,xk] 表示 f(x)f(x) 关于节点 x0,x1,,xkx0,x1,⋯,xkkk 阶差商(k-th Difference Quotient) (k=1,2,,n) ,它们可递归定义为

f[x0,x1,,xk]=f[x1,x2,,xk]f[x0,x1,,xk1]xkx0f[x0,x1,⋯,xk]=f[x1,x2,⋯,xk]−f[x0,x1,⋯,xk−1]xk−x0

其中 f(x)f(x) 关于 xixi00 阶差商为 f(xi)
根据差商的定义,差商具有如下性质:
1、
f[x0,x1,,xk]=i=0kf(xi)kj=0,ji(xixj)f[x0,x1,⋯,xk]=∑i=0kf(xi)∏j=0,j≠ik(xi−xj)

证明的话可以用数学归纳法直接爆肝就行了,此处省略(其实这个性质下面并不会用到,主要是引入对称这个性质)。

2、由上面那个公式可以显然看出,xixi 顺序的改变并不会影响差商的值,因此我们称差商具有对称性。

牛顿插值公式

于是我们利用差商的性质,可以推出牛顿插值公式。方法如下:
首先由差商的定义,以及其对称的性质,可以得到:

f[x,x0]=f(x)f(x0)xx0    f(x)=f(x0)+f[x,x0](xx0)    (a)f[x,x0]=f(x)−f(x0)x−x0  →  f(x)=f(x0)+f[x,x0](x−x0)    (a)
f[x,x0,x1]=f[x,x0]f[x0,x1]xx1f[x,x0,x1]=f[x,x0]−f[x0,x1]x−x1
  f[x,x0]=f[x0,x1]+f[x,x0,x1](xx1)    (b)→  f[x,x0]=f[x0,x1]+f[x,x0,x1](x−x1)    (b)
f[x,x0,,xn]=f[x,x0,,xn1]f[x0,x1,,xn]xxn    f[x,x0,,xn1]=f[x0,x1,,xn]+f[x,x0,,xn](xxn)    (c)f[x,x0,⋯,xn]=f[x,x0,⋯,xn−1]−f[x0,x1,⋯,xn]x−xn  →  f[x,x0,⋯,xn−1]=f[x0,x1,⋯,xn]+f[x,x0,⋯,xn](x−xn)    (c)

我们发现,对于式子 (b)(b),我们将其乘上 (xx0)(x−x0),那么就会有
(a)+(b)×(xx0)    f(x)+f[x,x0](xx0)=f(x0)+f[x,x0](xx0)+f[x0,x1](xx0)+f[x,x0,x1](xx0)(xx1)(a)+(b)×(x−x0) :   f(x)+f[x,x0](x−x0)=f(x0)+f[x,x0](x−x0)+f[x0,x1](x−x0)+f[x,x0,x1](x−x0)(x−x1)

递推地看,我们发现一个规律,每个式子 (c)(c) 如果乘上一个 (xx0)(xx1)(xxn1)(x−x0)(x−x1)⋯(x−xn−1) 以后可以上下相加来抵消一些项。
因此对于每一个 (c)(c) 式,我们将其乘上 (xx0)(xx1)(xxn1)(x−x0)(x−x1)⋯(x−xn−1) 后求和,可以得到这样的式子
f(x)=f(x0)+f[x0,x1](xx0)+f[x0,x1,x2](xx0)(xx1)++f[x0,x1,,xn](xx0)(xx1)(xxn1)+f[x,x0,,xn](xx0)(xx1)(xxn1)(xxn)f(x)=f(x0)+f[x0,x1](x−x0)+f[x0,x1,x2](x−x0)(x−x1)+⋯+f[x0,x1,⋯,xn](x−x0)(x−x1)⋯(x−xn−1)+f[x,x0,⋯,xn](x−x0)(x−x1)⋯(x−xn−1)(x−xn)

这就是牛顿插值公式
形式化地,有 f(x)=Pn(x)+Rn(x)f(x)=Pn(x)+Rn(x) ,并记
Pn(x)=f(x0)+f[x0,x1](xx0)+f[x0,x1,x2](xx0)(xx1)++f[x0,x1,,xn](xx0)(xx1)(xxn1)Pn(x)=f(x0)+f[x0,x1](x−x0)+f[x0,x1,x2](x−x0)(x−x1)+⋯+f[x0,x1,⋯,xn](x−x0)(x−x1)⋯(x−xn−1)
Rn(x)=f[x,x0,,xn](xx0)(xx1)(xxn1)(xxn)Rn(x)=f[x,x0,⋯,xn](x−x0)(x−x1)⋯(x−xn−1)(x−xn)

并称 Pn(x)Pn(x)牛顿插值多项式Rn(x)Rn(x)牛顿插值余项
复杂度

显然我们预处理差商的时候需要 O(n2)O(n2) 的复杂度,而单次计算的时候只需要用秦九韶算法 O(n)O(n) 求出。

优缺点

牛顿插值法可以在 O(n)O(n) 的复杂度计算单次的值, 并且当插值点增加的时候,我们也只需要 O(n)O(n) 的复杂度刷新一遍差商即可,这就是牛顿插值法的优越性。缺点的话,就是计算较为复杂,式子也不好背。
(其实缺点是这个算法无法在特殊情况下优化到 O(n)O(n),如何这样请看下文)

实现

在插值这个过程中,我们只需要求出 P(x)P(x) 的值即可,余项只是用来判误差的。
观察这个式子,我们发现对于 11 这个因式,所有的项都有(这不是废话);对于 (xx0) 这个因式,从第二项到最后一项都有;对于 (xx0)(xx1)(x−x0)(x−x1) 这个因式,从第三项到最后一项都有
发现什么没有?这玩意非常像那个叫秦九韶算法的玩意。因此我们考虑预处理差商,然后利用秦九韶算法从内往外求出 P(x)P(x) 的值。代码如下:

//Night's template
#include <bits/stdc++.h>
#define R register
#define LL long long
template<class TT>inline TT Max(R TT a,R TT b){return a<b?b:a;}
template<class TT>inline TT Min(R TT a,R TT b){return a<b?a:b;}
using namespace std;
template<class TT>inline void read(R TT &x){
    x=0;R bool f=false;R char c=getchar();
    for(;c<48||c>57;c=getchar())f|=(c=='-');
    for(;c>47&&c<58;c=getchar())x=(x<<1)+(x<<3)+(c^48);
    (f)&&(x=-x);
}
//end template
const int maxn = 2020;
struct node{
    double x,y;
}P[maxn];//储存点的结构题体
int n;
double a[maxn];//差商,作为系数
double x;//求值点
int main(){
    read(n);
    for(R int i=0;i<n;++i){
        scanf("%lf%lf",&P[i].x,&P[i].y);
    }//输入数据点
    scanf("%lf",&x);//求值点
    for(R int i=0;i<n;++i)a[i]=P[i].y;
    for(R int i=0;i<n;++i){
        for(R int j=n-1;j>i;j--){
            a[j]=(a[j]-a[j-1])/(P[j].x-P[j-1-i].x);
        }
    }//处理差商

    R double tmp=1,ans=a[0];
    for(R int i=0;i<n;++i){
        tmp*=(x-P[i].x);
        ans+=tmp*a[i+1];
    }
    printf("%.5lf\n",ans);
    return 0;
}

拉格朗日插值法

推导分析

由于我们要构造的多项式 L(x)L(x) 需要满足 L(xi)=yiL(xi)=yi,因此我们考虑构造这样一个式子,使得 fi(xi)=yifi(xi)=yi 且其余的 fi(xj) (ji)fi(xj) (j≠i) 都为 00
显然如果取 fi(x)(xxj) (ji)(x−xj) (j≠i) 这些因式,那么就可以保证这一点。
因此取

fi(x)=j=0, jinxxjxixjfi(x)=∏j=0, j≠inx−xjxi−xj

那么我们要构造的多项式即是
L(x)=i=0nfi(x) yiL(x)=∑i=0nfi(x) yi

那么 L(x)L(x) 称为拉格朗日插值多项式fi(x)fi(x) 称为拉格朗日基本多项式(或者插值基函数)
复杂度

显然求每一个 fi(x)fi(x)O(n)O(n) 的,于是求 L(x)L(x)O(n×n)=O(n2)O(n×n)=O(n2) 的。因此对于每个插值点,拉格朗日插值法的复杂度是 O(n2)O(n2) 的。

优缺点

拉格朗日插值多项式结构整齐,计算十分方便(比牛顿插值法好记多了)。但是在实际计算中,当插值点有所修改时,所对应的整个多项式就需要全部重新计算,复杂度是 O(n2)O(n2) 的(相比之下牛顿插值法只需要 O(n)O(n)),因此我们就要用重心拉格朗日插值法或牛顿插值法来代替。此外,当插值点比较多的时候,拉格朗日插值多项式的次数可能会很高,因此具有数值不稳定的特点,也就是说尽管在已知的几个点取到给定的数值,但在附近却会和“实际上”的值之间有很大的偏差。这类现象也被称为龙格现象,相同地,牛顿插值法由于也是一个 nn 次的多项式,也会出现同样的问题。解决的办法是分段用较低次数的插值多项式。

重心插值法

再观察一下这个式子

fi(x)=j=0, jinxxjxixj

L(x)=i=0n(fi(x)×yi)L(x)=∑i=0n(fi(x)×yi)

我们会发现我们每个 fi(x)fi(x) 都重复计算了这样一个东西
j=0,jin(xxj)∏j=0,j≠in(x−xj)

于是乎,我们记
g(x)=i=0n(xxi)g(x)=∏i=0n(x−xi)

来解决这个问题,这样一来 L(x)L(x) 就会变成这样:
L(x)=g(x)i=0nyi(ji(xixj))×(xxi)L(x)=g(x)∑i=0nyi(∏j≠i(xi−xj))×(x−xi)

为了解决拉格朗日插值法每次加入插值点都需要重新计算 fi(x)fi(x) 的问题,对于每一个 fi(fi( 我们记重心权
wi=yiji(xixj)wi=yi∏j≠i(xi−xj)


L(x)=g(x)i=0nwixxiL(x)=g(x)∑i=0nwix−xi

这样,求预处理 wiwi 的复杂度为 O(n2)O(n2),单次求值 L(x)L(x) 的复杂度为 O(n)O(n)
而对于动态加点的问题,我们只需要 O(n)O(n) 地修改 wiwi 就可以了,避免了 O(n2)O(n2) 的复杂运算,具体做法呢,就是把所有的 wiwi 除以 xixn+1xi−xn+1 得到新的 wn+1wn+1,这样一来我们的添加插值点的复杂度就变成 O(n)O(n) 的了。

线性插值法

所谓线性插值法,并不是指你把插值点两两直线连起来的那个插值法,而是能在保证插值点 xi=x0+ixi=x0+i 的情况下将复杂度优化到 O(n)O(n) 的重心拉格朗日插值法。

推导如下

由上面讲的重心插值法,我们有

L(x)=g(x)i=0nwixxiL(x)=g(x)∑i=0nwix−xi

其中的 g(x)g(x) 显然可以 O(n)O(n) 预处理,因此我们要想办法 O(n)O(n) 地计算
i=0nwixxi∑i=0nwix−xi

显然前面那个 是省不掉的啊,所以我们考虑如何 O(1)O(1)
wi=yiji(xixj)wi=yi∏j≠i(xi−xj)

我们考虑把下面的 分成 j<ij<ij>ij>i 两类,那么
wi=yii1j=0(xixj)×nj=i+1(xixj)wi=yi∏j=0i−1(xi−xj)×∏j=i+1n(xi−xj)

由于 xi=x0+ixi=x0+i,那么
wi=yiij=1j×nij=1jwi=yi∏j=1ij×∏j=1n−i−j

这样一来,我们可以预处理
vi=j=1ijvi=∏j=1ij

那么
wi=yivj×vniwi=yivj×−vn−i

于是我们就可以 O(n)O(n) 地预处理 vivi,然后 O(1)O(1) 地求出 wiwi,然后 O(n)O(n) 地计算
L(x)=g(x)i=0nwixxiL(x)=g(x)∑i=0nwix−xi

于是此时,我们的插值法就变成线性的啦(耶)
(代码与例题未完待续…
(可能会没空写…
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值