三次样条函数三弯矩算法
- 格式:docx
- 大小:56.08 KB
- 文档页数:7
摘要
求函数在给定区间上的定积分,在微积分学中已给出了许多计算方法,但是,在实际问题计算中,往往仅给出函数在一些离散点的值,它的解析表达式没有明显的给出,或者,虽然给出解析表达式,但却很难求得其原函数。这时我们可以通过数值方法求出函数积分的近似值。
在用近似值代替真实值时,遇到的问题就是近似值的代数精度是否足够。当代数精度不足够时,很显然提高插值函数的次数是一种方法,但是考虑到数值计算的稳定性,当次数过高时,会出现龙格现象,用增大n的方法来提高数值积代数精度是不可取的。
正如我们所知道的分段线性插值,逼近程度好,但光滑性差。分段三次Hermite插值,逼近程度好,光滑性也有所提高,但是需要增加更多的条件,不太实用。因此,我们将介绍一种结合二者的优点的插值方法——三次样条插值。本实验将介绍三次样条插值的三弯矩算法。
关键词:龙格现象 三弯矩算法 代数精度 分段三次Hermite插值
1 1、实验目的
1) 通过本次实验体会并学习三次样条插值的优点。
2) 通过对三次样条插值进行编程实现,提高自己的编程能力。
3) 用实验报告的形式展现,提高自己在写论文方面的能力。
2、算法流程
如果已知函数)(xfy在节点a=𝑥0<𝑥1<⋯<𝑥𝑛=𝑏,𝑦𝑖=𝑓(𝑥𝑖),𝑖=0,1,2,⋯,𝑛处的函数值和导数值:
nixfyii,,2,1,0),(
如果)(xS满足条件:
1) S(x)是一个分段的三次多项式且iiyxs)(;
2) S(x)在[a,b]具有二阶连续导数。
则称S(x)是三次样条插值函数。S(x)的具体形式为:
其中S𝑖(𝑥)在[𝑥𝑛−1,𝑥𝑛]上是三次多项式S𝑖(x)=𝑎𝑖𝑥3+𝑏𝑖𝑥2+𝑐𝑖𝑥+𝑑𝑖
由插值条件S(𝑥𝑖)=𝑦𝑖,i=0,1,2,…,n,得n+1个条件。
边界条件一:𝑆′(𝑥0)=𝑦0′,𝑆′(𝑥𝑛)=𝑦𝑛′
边界条件二:𝑆′′(𝑥0)=𝑦0′′,𝑆′′(𝑥𝑛)=𝑦𝑛′′
边界条件三:假定函数𝑦=𝑓(𝑥)是以b-a为周期的周期函数,这时要求S(x)也是周期函数,即
{𝑆(𝑥0+0)=𝑆(𝑥𝑛−0)𝑆′(𝑥0+0)=𝑆′(𝑥𝑛−0)𝑆′′(𝑥0+0)=𝑆′′(𝑥𝑛+0)
]12,121,01,[,...............][,][,nnnxxxxsxxxxsxxxxsxs2 针对三种边界条件的求解方法的不同,可以分为三转角算法和三弯矩算法,本实验将介绍和学习三转角算法。
三弯矩算法:
在每个子区间[𝑥𝑛−1,𝑥𝑛],因为S(x)是三次多项式,所以S′′(x)是一次多项式,假设节点𝑥𝑖处
S′′(𝑥𝑖)=𝑀𝑖,𝑖=0,1,2,⋯,𝑛
则在[𝑥𝑛−1,𝑥𝑛]上
𝑆𝑖′′(x)=𝑥−𝑥𝑖𝑥𝑖−1−𝑥𝑖𝑀𝑖−1+𝑥−𝑥𝑖−1𝑥𝑖−𝑥𝑖−1𝑀𝑖=𝑥𝑖−𝑥ℎ𝑖𝑀𝑖−1+𝑥−𝑥𝑖−1ℎ𝑖𝑀𝑖
对上式积分两次得
𝑆𝑖(𝑥)=𝑀𝑖−1(𝑥𝑖−𝑥)36ℎ𝑖+𝑀𝑖(𝑥−𝑥𝑖−1)36ℎ𝑖+𝐶𝑖(𝑥𝑖−𝑥)+𝐷𝑖(𝑥−𝑥𝑖−1)
其中𝐶𝑖和𝐷𝑖的值为任意常数,由端点值可以求得。
显然只要确定出𝑀𝑖(𝑖=0,1,2,⋯,𝑛)就能求出𝑆𝑖(𝑥),进而得到三次样条函数S(x)。关于𝑀𝑖,我们利用样条函数在节点𝑥𝑖处一阶导数连续来确定。
3、算法实例
已知函数𝑦=𝑓(𝑥)的如下表的数据
i 0 1 2 3
𝑥𝑖 1 2 4 5
𝑦𝑖 1 3 4 2
求满足自然边界条件S′′(𝑥0)=S′′(𝑥3)=0的𝑓(3)的近似值
解:根据题意可知,函数在区间两端的二阶导数,因此可知应该用三弯矩算法来实现,具体程序如下:
#include "stdio.h"
#define N 4
void main()
{
int i,k;
float X,s,y0,yn;
float a[N][N+1],h[N],u[N],v[N],g[N],m[N],p[N],q[N],w[N];
printf("please input X:"); //X为未知数的大小
scanf("%f",&X); 3 printf("please input x:"); //输入x的大小
for(i=0;i
scanf("%f",&a[i][0]);
printf("please input y:"); //输入y的大小
for(i=0;i
scanf("%f",&a[i][1]);
for(i=1;i
h[i]=a[i][0]-a[i-1][0]; //计算步长
for(i=1;i
{
v[i]=h[i+1]/(h[i]+h[i+1]);
u[i]=1-v[i];
g[i]=3*u[i]*(a[i+1][1]-a[i][1])/h[i+1]+3*v[i]*(a[i][1]-a[i-1][1])/h[i];
}
printf("\t(1)已知边界条件1\n");
printf("\t(2)已知边界条件2\n");
printf("请选择边界条件序号:");
scanf("%d",&k);
if(k==1)
{
printf("请输入y0和yn的一阶导:"); //输入边界条件一
scanf("%f%f",&m[0],&m[N-1]);
p[0]=0; //用追赶法求解m[N]
q[0]=0;
g[1]=g[1]-v[1]*m[0];
g[N-2]=g[N-2]-u[N-2]*m[N-1];
for(i=1;i
{
w[i]=2-u[i]*p[i-1];
p[i]=v[i]/w[i];
q[i]=(g[i]-u[i]*q[i-1])/w[i];
}
m[N-2]=q[N-2];
for(i=N-3;i>0;i--) 4 m[i]=q[i]-p[i]*m[i+1];
printf("输出m[i]的值:\n");
for(i=0;i
printf("%f\n",m[i]);
for(i=1;i
if(X>a[i-1][0]&&X
s=(h[i]+2*(X-a[i-1][0]))*(X-a[i][0])*(X-a[i][0])*a[i-1][1]/(h[i]*h[i]*h[i])+(h[i]-2*(X-a[i][0]))*(X-a[i-1][0])*(X-a[i-1][0])*a[i][1]/(h[i]*h[i]*h[i])+(X-a[i-1][0])*(X-a[i][0])*(X-a[i][0])*m[i-1]/(h[i]*h[i])+(X-a[i-1][0])*(X-a[i-1][0])*(X-a[i][0])*m[i]/(h[i]*h[i]);
printf("s(%f)=%f\n",X,s);
}
if(k==2)
{
printf("请输入y0和yn的二阶导:"); //输入边界条件二
scanf("%f%f",&y0,&yn);
g[0]=3*(a[1][1]-a[0][1])/h[1]-h[1]*y0/2;
g[N-1]=3*(a[N-1][1]-a[N-2][1])/h[N-1]+h[N-1]*yn/2;
q[0]=g[0];
u[0]=1;
v[N-1]=1;
w[0]=2;
for(i=1;i
{
w[i]=2-v[i]*u[i-1]/w[i-1];
q[i]=g[i]-v[i]*q[i-1]/w[i-1];
}
m[N-1]=q[N-1]/w[N-1];
for(i=N-2;i>=0;i--)
m[i]=(q[i]-u[i]*m[i+1])/w[i];
printf("输出m[i]的值:\n");
for(i=0;i
printf("%f\n",m[i]);
for(i=1;i
if(X>=a[i-1][0]&&X<=a[i][0]) 5
s=(h[i]+2*(X-a[i-1][0]))*(X-a[i][0])*(X-a[i][0])*a[i-1][1]/(h[i]*h[i]*h[i])+(h[i]-2*(X-a[i][0]))*(X-a[i-1][0])*(X-a[i-1][0])*a[i][1]/(h[i]*h[i]*h[i])+(X-a[i-1][0])*(X-a[i][0])*(X-a[i][0])*m[i-1]/(h[i]*h[i])+(X-a[i-1][0])*(X-a[i-1][0])*(X-a[i][0])*m[i]/(h[i]*h[i]);
printf("s(%f)=%f\n",X,s);
}
}
运行结果:
5、对结果进行分析
通过用编程实现对上例的求解,可以看出结果较为准确,而且不仅逼近效果好,而且光滑性有所提高,但是由于在计算机上计算,会存在计算误差。
6、参考文献
[1] 秦新强.数值逼近.西安:西安理工大学出版社,2010