【数值分析】拉格朗日插值法与牛顿插值法的C++实现

数值分析——拉格朗日插值法与牛顿插值法的C++实现



一、插值法

1.1 插值法定义

设函数 y = f ( x ) \displaystyle\color{red}y=f(x) y=f(x)在区间 [ a , b ] \displaystyle\color{red}[a,b] [a,b]上有定义,且 a ≤ x 0 < x 1 < ⋯ < x n ≤ b \displaystyle\color{red}a ≤x_0<x_1<\dots<x_n ≤b ax0<x1<<xnb,已知在 x 0 … x n \displaystyle\color{red}x_0\dots x_n x0xn点处的值分别为 y 0 . . . y n \displaystyle\color{red}y_0...y_n y0...yn,若存在一个简单的函数 p ( x ) \displaystyle\color{red}p(x) p(x),使得 p ( x i ) = y i \displaystyle\color{red}p(x_i)=y_i p(xi)=yi,称 p ( x ) p(x) p(x) f ( x ) f(x) f(x)插值函数,点 x 0 . . . x 1 x_0...x_1 x0...x1称为插值节点 [ a , b ] [a,b] [a,b]称为插值区间, p ( x i ) = y i p(x_i)=y_i p(xi)=yi称为插值条件,求插值函数 p ( x ) p(x) p(x)的方法称为插值法
p ( x ) p(x) p(x)为代数不超过n的代数多项式( p ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n \displaystyle\color{blue}p(x) = a_0+a_1x+a_2x^2+\dots+a_nx^n p(x)=a0+a1x+a2x2++anxn),其中 a i a_i ai为实数,称 p ( x ) p(x) p(x)插值多项式;若 p ( x ) p(x) p(x)为分段多项式,就是分段插值。若 p ( x ) p(x) p(x)为三角多项式,就称为三角插值

本质上:根据给出的一系列点 ( x i , f ( x i ) ) (x_i,f(x_i)) (xi,f(xi)),构造函数 p ( x ) p(x) p(x)来对 f ( x ) f(x) f(x)近似,其中要求 p ( x ) p(x) p(x)必须经过这些插值点。

在这里插入图片描述

1.2 插值多项式唯一性定理

满足 p ( x i ) = y i , i = 0 , . . . , n p(x_i)=y_i,i=0,...,n p(xi)=yi,i=0,...,n次数不超过 n的插值多项式是唯一存在的。

二、拉格朗日(Lagrange)插值法

2.1 拉格朗日多项式

求n次多项式 L n ( x ) = y 0 l 0 ( x ) + y 1 l 1 ( x ) + . . . + y n l n ( x ) \displaystyle\color{red}L_n(x)=y_0l_0(x)+y_1l_1(x)+...+y_nl_n(x) Ln(x)=y0l0(x)+y1l1(x)+...+ynln(x)使得 L n ( x i ) = y i , i = 0 , 1 , . . . , n \displaystyle\color{red}L_n(x_i)=y_i,i=0,1,...,n Ln(xi)=yi,i=0,1,...,n——即插值多项式一定经过过插值节点 ( x i , y i ) , i = 0 , 1 , . . . , n (x_i,y_i),i=0,1,...,n (xi,yi),i=0,1,...,n l i ( x ) l_i(x) li(x)称为拉格朗日基函数( Lagrange Basis)。
要求:无重合节点,即 i ≠ j i≠j i=j时, x i 不 等 于 x j x_i不等于x_j xixj
拉格朗日基函数的构造:
l i ( x ) = ∏ i = 0 , i ≠ j n ( x − x i ) ( x i − x j ) \displaystyle\color{blue}l_i(x)=\displaystyle\prod_{i=0 ,i≠j}^{n} \frac{(x-x_i)}{(x_i-x_j)} li(x)=i=0,i=jn(xixj)(xxi)

拉格朗日多项式:
L n ( x ) = l i ( x ) y i \displaystyle\color{blue}L_n(x)=l_i(x)y_i Ln(x)=li(x)yi


举个例子
构造三点二次插值(抛物线)多项式:

三点: ( x 0 , y 0 ) 、 ( x 1 , y 1 ) 、 ( x 2 , y 2 ) (x_0,y_0)、(x_1,y_1)、(x_2,y_2) (x0,y0)(x1,y1)(x2,y2)

l 0 ( x ) = ( x − x 1 ) ( x − x 2 ) ( x 0 − x 1 ) ( x 0 − x 2 ) l_0(x)= \frac{(x-x_1)(x-x_2)}{(x_0-x_1)(x_0-x_2)} l0(x)=(x0x1)(x0x2)(xx1)(xx2)

l 1 ( x ) = ( x − x 0 ) ( x − x 2 ) ( x 1 − x 0 ) ( x 1 − x 2 ) l_1(x)= \frac{(x-x_0)(x-x_2)}{(x_1-x_0)(x_1-x_2)} l1(x)=(x1x0)(x1x2)(xx0)(xx2)

l 2 ( x ) = ( x − x 0 ) ( x − x 1 ) ( x 2 − x 0 ) ( x 2 − x 1 ) l_2(x)= \frac{(x-x_0)(x-x_1)}{(x_2-x_0)(x_2-x_1)} l2(x)=(x2x0)(x2x1)(xx0)(xx1)

三点二次拉格朗日多项式: L 2 ( x ) = l 0 ( x ) y 0 + l 1 ( x ) y 1 + l 2 ( x ) y 2 L_2(x)=l_0(x)y_0+l_1(x)y_1+l_2(x)y_2 L2(x)=l0(x)y0+l1(x)y1+l2(x)y2

2.2 拉格朗日插值法的C++实现

拉格朗日多项式C++实现:

/*
	X:插值节点的x坐标
	Y:插值节点的y坐标
	len:使用插值节点数量(插值多项式阶数为len-1)
	x:待求节点处的x坐标
	输出:拉格朗日多项式在x处的值
	注意:X中坐标值不可重复(否则拉格朗日基函数中分母会出现0项)
*/
double lagrangePolynomial(vector<double> X, vector<double> Y, int len, double x) {
	double res = 0;
	for (int i = 0; i < len; i++) {
		double l_i = 1;//拉格朗日基函数li(x)
		for (int j = 0; j < len; j++) {
			if (j != i) {
				l_i *= (x - X[j]) / (X[i] - X[j]);
			}
		}
		res += l_i * Y[i];
	}
	return res;
}

测试代码:

使用四个点 ( π 6 , 1 2 ) (\frac{\pi}{6},\frac{1}{2}) (6π,21) ( π 4 , 1 2 ) (\frac{\pi}{4}, \frac{1}{\sqrt 2}) (4π,2 1) ( π 3 , 3 2 ) (\frac{\pi}{3},\frac{\sqrt 3}{2}) (3π,23 ) ( π 2 , 1 ) (\frac{\pi}{2}, 1) (2π,1)构造三次拉格朗日多项式 L 3 ( x ) L_3(x) L3(x)来近似原函数 f ( x ) = s i n x f(x)=sinx f(x)=sinx,并近似求出 s i n ( π 5 ) sin(\frac{\pi}{5}) sin(5π)的值

#define pi 3.14159265358979323846   //pi
int main() {

	//测试f(x) = sin(x)
	vector<double> X = {pi / 6 , pi / 4 , pi / 3, pi / 2};
	vector<double> Y = { 0.5, 1 / sqrt(2), sqrt(3) / 2, 1};
	int len = X.size();

	cout << "拉格朗日多项式sin(pi/5):" << lagrangePolynomial(X, Y, len, pi / 5) << endl;
	cout << "sin(pi/5)的准确值:" << sin(pi / 5) << endl;
	/*
		拉格朗日多项式sin(pi/5):0.587997
		sin(pi/5)的准确值:0.587785
	*/
}

三、牛顿插值法

拉格朗日插值法当增加一个节点时,所有的拉格朗日基函数 l i ( x ) l_i(x) li(x)需要重新进行计算;
使用牛顿插值法,增加一个节点时只需要在原来的牛顿插值函数的基础上加上一项即可。

3.1 差商

一阶差商:
f [ x i , x j ] = f ( x i ) − f ( x j ) x i − x j ( i ≠ j , x i ≠ x j ) f[x_i,x_j]=\frac{f(x_i)-f(x_j)}{x_i-x_j} (i≠j,x_i≠x_j) f[xi,xj]=xixjf(xi)f(xj)(i=j,xi=xj)

二阶差商:
f [ x i , x j , x k ] = f [ x i , x j ] − f [ x j , x k ] x i − x k ( i ≠ k ) f[x_i,x_j,x_k]=\frac{f[x_i,x_j]-f[x_j,x_k]}{x_i-x_k}(i≠k) f[xi,xj,xk]=xixkf[xi,xj]f[xj,xk](i=k)

k+1阶差商:
f [ x 0 , … , x n ] = f [ x 0 , … , x k ] − f [ x 1 , … , x k , x k + 1 ] x 0 − x k + 1 = f [ x 0 , … , x k − 1 , x k ] − f [ x 0 , … , x k − 1 , x k + 1 ] x k − x k + 1 \displaystyle\color{red}f[x_0,\dots,x_n]=\frac{f[x_0,\dots,x_k]-f[x_1,\dots,x_k,x_{k+1}]}{x_0-x_{k+1}}=\frac{f[x_0,\dots,x_{k-1},x_k]-f[x_0,\dots,x_{k-1},x_{k+1}]}{x_k-x_{k+1}} f[x0,,xn]=x0xk+1f[x0,,xk]f[x1,,xk,xk+1]=xkxk+1f[x0,,xk1,xk]f[x0,,xk1,xk+1]

差商表:
在这里插入图片描述

3.2 牛顿(Newton)插值法

c k ( x ) = f [ x 0 , x 1 , … , x k ] \displaystyle\color{blue}c_k(x)=f[x_0,x_1,\dots,x_k] ck(x)=f[x0,x1,,xk]

N n ( x ) = f ( x 0 ) + f [ x 0 , x 1 ] ( x − x 0 ) + f [ x 0 , x 1 , x 2 ] ( x − x 0 ) ( x − x 1 ) + ⋯ + f [ x 0 , … , x n ] ( x − x 0 ) ( x − x 1 ) … ( x − x n − 1 ) = c 0 + c 1 ( x − x 0 ) + c 2 ( x − x 0 ) ( x − x 1 ) + ⋯ + c n ( x − x 0 ) ( x − x 1 ) … ( x − x n − 1 ) \displaystyle\color{blue}N_n(x)=f(x_0)+f[x_0,x_1](x-x_0)+f[x_0,x_1,x_2](x-x_0)(x-x_1)+\dots+f[x_0,\dots,x_n](x-x_0)(x-x_1)\dots(x-x_{n-1})=c_0+c_1(x-x_0)+c_2(x-x_0)(x-x_1)+\dots+c_n(x-x_0)(x-x_1)\dots(x-x_{n-1}) Nn(x)=f(x0)+f[x0,x1](xx0)+f[x0,x1,x2](xx0)(xx1)++f[x0,,xn](xx0)(xx1)(xxn1)=c0+c1(xx0)+c2(xx0)(xx1)++cn(xx0)(xx1)(xxn1)

且由唯一性定理可知 N n ( x ) = L n ( x ) N_n(x)=L_n(x) Nn(x)=Ln(x)


举个例子:

四个点 ( 0.4 , 0.41075 ) (0.4,0.41075) (0.4,0.41075) ( 0.55 , 0.57815 ) (0.55,0.57815) (0.55,0.57815) ( 0.65 , 0.69675 ) (0.65,0.69675) (0.65,0.69675) ( 0.80 , 0.88811 ) (0.80,0.88811) (0.80,0.88811)

f [ x 0 ] = 0.41075 f[x_0]=0.41075 f[x0]=0.41075 f [ x 1 ] = 0.57815 f[x_1]=0.57815 f[x1]=0.57815 f [ x 2 ] = 0.69675 f[x_2]=0.69675 f[x2]=0.69675 f [ x 3 ] = 0.88811 f[x_3]=0.88811 f[x3]=0.88811

f [ x 0 , x 1 ] = f [ x 0 ] − f [ x 1 ] x 0 − x 1 = 1.11600 f[x_0,x_1]=\frac{f[x_0]-f[x_1]}{x_0-x_1}=1.11600 f[x0,x1]=x0x1f[x0]f[x1]=1.11600 f [ x 1 , x 2 ] = f [ x 1 ] − f [ x 2 ] x 1 − x 2 = 1.18600 f[x_1,x_2]=\frac{f[x_1]-f[x_2]}{x_1-x_2}=1.18600 f[x1,x2]=x1x2f[x1]f[x2]=1.18600 f [ x 2 , x 3 ] = f [ x 2 ] − f [ x 3 ] x 2 − x 3 = 1.27573 f[x_2,x_3]=\frac{f[x_2]-f[x_3]}{x_2-x_3}=1.27573 f[x2,x3]=x2x3f[x2]f[x3]=1.27573

f [ x 0 , x 1 , x 2 ] = f [ x 0 , x 1 ] − f [ x 1 , x 2 ] x 0 − x 2 = 0.28000 f[x_0,x_1,x_2]=\frac{f[x_0,x_1]-f[x_1,x_2]}{x_0-x_2}=0.28000 f[x0,x1,x2]=x0x2f[x0,x1]f[x1,x2]=0.28000 f [ x 1 , x 2 , x 3 ] = f [ x 1 , x 2 ] − f [ x 2 , x 3 ] x 1 − x 3 = 0.35893 f[x_1,x_2,x_3]=\frac{f[x_1,x_2]-f[x_2,x_3]}{x_1-x_3}=0.35893 f[x1,x2,x3]=x1x3f[x1,x2]f[x2,x3]=0.35893

f [ x 0 , x 1 , x 2 , x 3 ] = f [ x 0 , x 1 , x 2 ] − f [ x 1 , x 2 , x 3 ] x 0 − x 3 = 0.19733 f[x_0,x_1,x_2,x_3]=\frac{f[x_0,x_1,x_2]-f[x_1,x_2,x_3]}{x_0-x_3}=0.19733 f[x0,x1,x2,x3]=x0x3f[x0,x1,x2]f[x1,x2,x3]=0.19733

N 3 ( x ) = f ( x 0 ) + f [ x 0 , x 1 ] ( x − x 0 ) + f [ x 0 , x 1 , x 2 ] ( x − x 0 ) ( x − x 1 ) + f [ x 0 , x 1 , x 2 , x 3 ] ( x − x 0 ) ( x − x 1 ) ( x − x 2 ) = 0.41075 + 1.11600 ( x − 0.40 ) + 0.28000 ( x − 0.40 ) ( x − 0.55 ) + 0.19733 ( x − 0.40 ) ( x − 0.55 ) ( x − 0.65 ) N_3(x)=f(x_0)+f[x_0,x_1](x-x_0)+f[x_0,x_1,x_2](x-x_0)(x-x_1)+f[x_0,x_1,x_2,x_3](x-x_0)(x-x_1)(x-x_2)=0.41075+1.11600(x-0.40)+0.28000(x-0.40)(x-0.55)+0.19733(x-0.40)(x-0.55)(x-0.65) N3(x)=f(x0)+f[x0,x1](xx0)+f[x0,x1,x2](xx0)(xx1)+f[x0,x1,x2,x3](xx0)(xx1)(xx2)=0.41075+1.11600(x0.40)+0.28000(x0.40)(x0.55)+0.19733(x0.40)(x0.55)(x0.65)
在这里插入图片描述

3.2 牛顿插值法的C++实现

牛顿插值法C++实现:

/*
	X:插值节点的x坐标
	Y:插值节点的y坐标
	n:使用插值节点数量
	x:待求节点处的x坐标

	输出:牛顿多项式在x处的值

*/
double newtonInterpolation(vector<double> X, vector<double> Y, int n, double x) {
	double res = 0;
	vector<vector<double>> table(X.size(), vector<double>(X.size(), 0));//构建一个完整的差商表[X.size(), X.size()]
	for (int i = 0; i < X.size(); i++) {
		table[0][i] = Y[i];
	}

	//一阶差商
	for (int i = 1; i < X.size(); i++) {
		table[1][i] = (table[0][i] - table[0][i - 1]) / (X[i] - X[i - 1]);//注意一阶差商下(X[i] - X[i - 1])与其它高阶差商(X[i] - X[0])有些区别
	}

	//二阶及以上差商
	for (int i = 2; i < X.size(); i++) {
		for (int j = i; j < X.size(); j++) {
			table[i][j] = (table[i - 1][j] - table[i - 1][j - 1]) / (X[i] - X[0]);
		}
	}

	//计算Newton多项式在x处的近似值(使用len个节点构造len-1次多项式)
	for (int i = 0; i < n; i++) {
		double temp = 1;
		for (int j = 0; j < i; j++) {
			temp *= x - X[j];//计算(x-x_0)...(x-x_i-1)
		}
		res += temp * table[i][i];
	}

	return res;
}

测试代码:

使用四个点 ( π 6 , 1 2 ) (\frac{\pi}{6},\frac{1}{2}) (6π,21) ( π 4 , 1 2 ) (\frac{\pi}{4}, \frac{1}{\sqrt 2}) (4π,2 1) ( π 3 , 3 2 ) (\frac{\pi}{3},\frac{\sqrt 3}{2}) (3π,23 ) ( π 2 , 1 ) (\frac{\pi}{2}, 1) (2π,1)构造差商表table,再使用len个节点构造len-1阶牛顿多项式来近似原函数 f ( x ) = s i n x f(x)=sinx f(x)=sinx,并近似求出 s i n ( π 5 ) sin(\frac{\pi}{5}) sin(5π)的值

#define pi 3.14159265358979323846   //pi
int main() {

	//测试f(x) = sin(x)
	vector<double> X = {pi / 6 , pi / 4 , pi / 3, pi / 2};
	vector<double> Y = { 0.5, 1 / sqrt(2), sqrt(3) / 2, 1};
	int n = X.size();

	//使用前三个节点
	cout << "牛顿多项式sin(pi/5):" << newtonInterpolation(X, Y, n - 1, pi / 5) << endl;
	cout << "sin(pi/5)的准确值:" << sin(pi / 5) << endl;
	/*
		牛顿多项式sin(pi/5):0.588625
		sin(pi/5)的准确值:0.587785 
	*/

	//使用前四个节点
	cout << "牛顿多项式sin(pi/5):" << newtonInterpolation(X, Y, n, pi / 5) << endl;
	cout << "sin(pi/5)的准确值:" << sin(pi / 5) << endl;
	/*
		牛顿多项式sin(pi/5):0.586526
		sin(pi/5)的准确值:0.587785
	*/
	
}

总结

插值法就是利用一系列的点来构造插值函数对原函数进行近似,构造的插值函数必须满足插值节点的要求。拉格朗日插值法构造拉格朗日基函数较为简单,但是当增加或减少插值节点时拉格朗日基函数就需要重新计算;而牛顿插值法利用差商表(有点类似动态规划)来构建插值函数,当函数增加一个节点时只需要在原来的基础上增加一项即可。

  • 6
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值