# 数值分析:数据插值方法 http://blog.csdn.net/pipisorry/article/details/62227459 插值、拟合和逼近的区别 据维基百科,科学和工程问题可以通过诸如采样、实验等方法获得若干离散的数据,根据这些数据,我们往往希望得到一个连续的函数(也就是曲线)或者更加密集的离散方程与已知数据相吻合,这过程就叫做拟合。通过拟合得到的函数获得未知点的数据的方法,叫做插值。其中,拟合函数经过所有已知点的插值方法,叫做内插。 拟合是已知点列,从整体上靠近它们;插值是已知点列并且完全经过点列;逼近是已知曲线,或者点列,通过逼近使得构造的函数无限靠近它们。 最小二乘意义下的拟合,是要求拟合函数与原始数据的均方误差达到极小,是一种整体意义的逼近,对局部性质没有要求。而所谓“插值”,就是要在原有离散数据之间“插入”一些值,这就要求插值函数必须通过所有的离散点,插值函数在离散点之外的那些点都相当于“插入”的值。插值有全局插值,也有局部插值(比如分段线性插值),插值误差通常考虑的是逐点误差或最大模误差,插值的好坏往往通过某些局部的性质来体现,比如龙格现象或吉布斯振荡。 [知乎 拟合与插值的区别?] 皮皮blog 插值方法 多项式插值 对于大部分多项式插值函数,插值点的高度值可以视为所有(或某些)节点高度值的线性组合,而线性组合的系数一般是x坐标的多项式函数,称作基函数。对于一个节点的基函数,它在x等于该节点的x时等于1,在x等于其他节点的x时等于0。这就保证曲线必定经过所有节点,所以属于内插方法。 在本小节,均以一组随机数作为已知的高度值,使它们对应于间隔固定的x坐标,使用不同的插值函数获得各已知点(称为插值函数的节点)之外其它x坐标所对应的高度值,画出这些点所对应的曲线。再把所有高度值转换成灰度值,以颜色的变化比较各插值函数。 原点列如图:(假定横向为x,纵向为y。各点x坐标的间隔是固定的,但y坐标是随机的) 线性插值 File:Linear interpolation.png 线性插值是用一系列首尾相连的线段依次连接相邻各点,每条线段内的点的高度作为插值获得的高度值。 以(xi,yi)表示某条线段的前一个端点,(x(i+1),y(i+1))表示该线段的后一个端点,则对于在[xi,x(i+1)]范围内的横坐标为x的点,其高度y为: p(x) = f(x_0) + \frac{f(x_1)-f(x_0)}{x_1-x_0}(x-x_0). \,\! 为便于与后面各函数比较,写成比较对称的形式: 其中,yi和y(i+1)的两个参数称为基函数,二者之和为1,分别代表yi和y(i+1)对插值点高度的权值。 [wikipedia线性插值] 插值图像如下: 将高度转化为灰度,得到如下条带: 线性插值的特点是计算简便,但光滑性很差。如果用线性插值拟合一条光滑曲线,对每一段线段,原曲线在该段内二阶导数绝对值的最大值越大,拟合的误差越大。 二次插值 如果按照线性插值的形式,以每3个相邻点做插值,就得到了二次插值: OpenGL实现代码如下: void quadratic(float p[20][2]) { float x,y; int i; float x01,x02,x12; glColor3f(0.0,0.0,1.0); glBegin(GL_LINE_STRIP); for(i=0;i<20;i+=2) { x01=p[i][0]-p[i+1][0]; x02=p[i][0]-p[i+2][0]; x12=p[i+1][0]-p[i+2][0]; for(x=p[i][0];x<=p[i+2][0];x+=1.0) { y=(x-p[i+1][0])*(x-p[i+2][0])/x01/x02*p[i][1]-(x-p[i][0])*(x-p[i+2][0])/x01/x12*p[i+1][1]+(x-p[i][0])*(x-p[i+1][0])/x02/x12*p[i+2][1]; glVertex2f(x,y); } } glEnd(); } 二次(分段)插值图像如下: 转换成灰度值如图: 二次插值在每段二次曲线内是光滑的,但在每条曲线的连接处其光滑性可能甚至比线性插值还差。二次插值只适合3个节点的情形,当节点数超过3个时,就需要分段插值了。 Cubic插值 如果想要保证各段曲线连接处光滑(一阶导数相同),并且不想使用除法运算,可以考虑Cubic插值函数: 其中,v代表插值点,v0、v1、v2、v3代表4个连续的节点。t取值为[0,1],将会产生一段连接v1和v2的曲线。也就是说,如果有n个节点,Cubic插值函数将会产生(n-2)段曲线,位于首尾两端的节点不会纳入曲线。 实现代码如下: float cubic(float v0,float v1,float v2,float v3,float x) { float v32=v3-v2; float v01=v0-v1; float v20=v2-v0; float temp=(v32-v01)*x; temp=(temp+v01+v01-v32)*x; temp=(temp+v20)*x+v1; return temp; } void drawCubic(float p[20]) { float x,y; int step; float delta; glColor3f(0.0,0.0,1.0); glBegin(GL_LINE_STRIP); for(step=0;step<17;step++) { for(delta=0.0;delta<=1.0;delta+=0.05) { x=delta*(p[step+1][0]-p[step][0])+p[step][0]; y=cubic(p[step],p[step+1],p[step+2],p[step+3],delta); glVertex2f(x,y); } } glEnd(); } Cuibc插值图像如下: 转化成灰度如图: Cubic插值可以产生整体上光滑的曲线,但容易产生较剧烈的波动,使得曲线的最高点比最高的节点还高、曲线的最低点比最低的节点还低。所以对颜色等取值有严格的上下界的数据进行插值时,会造成曲线的截取,破坏其光滑性。比如颜色(RGB三个分量取值范围都是[0,255]),如果最高的节点是255,最低的节点是0,那么插值后负数会被截取成0,大于255的数会被截取成255。 拉格朗日多项式插值 解决插值函数的直接构造问题以及插值误差的估计问题,但是同时也使得插值函数值的计算变得复杂。 依照线性插值和二次插值的思路,可以增加基函数分子和分母的阶数,构造拉格朗日插值多项式: 一个n次的拉格朗日插值函数可以绘制经过(n+1)个节点的曲线,但运算量非常大。而且在次数比较高时,容易产生剧烈的震荡(龙格现象)。所以要选择位置特殊的节点(比如切比雪夫多项式的零点)进行插值,或使用多个次数较低的拉格朗日函数分段插值。(关于拉格朗日多项式和龙格现象,详见维基百科链接) 分段插值实现代码如下: //n为次数,nmax为节点的总数。n不大于nmax void lagrange(float p[][2],int n,int nmax) { float x,y; int i,j,t; float temp; glColor3f(0.0,0.0,1.0); for(i=0;i<=(nmax-1);i+=(n-1)) { glBegin(GL_LINE_STRIP); for(x=p[i][0];x<=p[i+n-1][0];x+=1.0) { y=0.0; for(j=0;j