插值(二)——拉格朗日插值(C++)

问题的提出

假设只有两个插值点: x 0 , x 1 ; y 0 , y 1 x_0,x_1;y_0,y_1 x0,x1;y0,y1,求 P 1 ( x ) = a 0 + a 1 x P_1(x)=a_0+a_1x P1(x)=a0+a1x使得 P 1 ( x 0 ) = y 0 , P 1 ( x 1 ) = y 1 P_1(x_0)=y_0,P_1(x_1)=y_1 P1(x0)=y0,P1(x1)=y1,由此可以得到 P 1 ( x ) = y 0 + y 1 − y 0 x 1 − x 0 ( x − x 0 ) = x − x 1 x 0 − x 1 y 0 + x − x 0 x 1 − x 0 y 1 = ∑ i = 0 1 l i y i \begin{align} P_1(x)&=y_0+\frac{y_1-y_0}{x_1-x_0}(x-x_0)\\ &=\frac{x-x_1}{x_0-x_1}y_0+\frac{x-x_0}{x_1-x_0}y_1\\ &=\sum_{i=0}^1l_iy_i\\ \end{align} P1(x)=y0+x1x0y1y0(xx0)=x0x1xx1y0+x1x0xx0y1=i=01liyi
这是对于只有两个插值点的情况,现在对其拓展到任意个插值点。

拉格朗日插值法

对于给出的n个插值坐标: x 0 , x 1 ⋯ x n ; y 0 , y 1 ⋯ y n x_0,x_1\cdots x_n;y_0,y_1\cdots y_n x0,x1xn;y0,y1yn,构造插值函数 P n ( x ) = ∑ i = 0 n l i ( x ) y i P_n(x)=\sum_{i=0}^nl_i(x)y_i Pn(x)=i=0nli(x)yi,显然 P n ( x i ) = y i P_n(x_i)=y_i Pn(xi)=yi,所以有 { l i ( x i ) = 1 l i ( x j ) = 0 ( i ≠ j ) \begin{cases} l_i(x_i)=1\\ l_i(x_j)=0(i\neq j) \end{cases} {li(xi)=1li(xj)=0(i=j)
因此构造出 l i ( x ) = ∏ j = 0 , j ≠ i n x − x j x i − x j l_i(x)=\prod_{j=0,j\neq i}^n\frac{x-x_j}{x_i-x_j} li(x)=j=0,j=inxixjxxj
代入原式,得到 L n ( x ) = ∑ i = 0 n l i ( x ) y i L_n(x)=\sum_{i=0}^nl_i(x)y_i Ln(x)=i=0nli(x)yi
对应的 l i ( x ) l_i(x) li(x)被称为拉格朗日插值基函数。

代码实现

//拉格朗日插值法
#include<iostream>
#include<cmath>
//拉格朗日插值法,输入插值点坐标、需要求值的x和插值点数n
double Lagrange(double *xi,double *yi,double x,int n)
{
    double Ln = 0;
    for (int i = 0; i < n;i++)
    {
        double li = 1.0;
        for (int j = 0; j < n;j++)
        {
            if(i!=j)
            {
                li*=((x-xi[j])/(xi[i]-xi[j]));    
            }
        }
        Ln+=yi[i]*li;
    }
    return Ln;
}
double fx(double x)
{
    return sqrt(x + 3);
}
int main()
{
    int n;
    std::cout << "输入插值点数:";
    std::cin>>n;
    double *x = new double[n];
    double *y = new double[n];
    double error[3]={0.0f,0.0f,0.0f};//误差评价
    double a = 3, b = 10;//插值区间
    for(int i=0;i<n;i++)
    {
        x[i] = a + i*(b-a)/(n-1);
        y[i] = fx(x[i]);
    }
    //std::cout<<Lagrange(x, y, 3.4, n)<<"-"<<fx(3.4)<<std::endl;
    for(int i = 0;i < 100;i++)
    {
        double x_temp = a + (b-a)*i/(100-1);
        double y_temp = fx(x_temp);
        double y_temp2 = Lagrange(x, y, x_temp, n);
        if(fabs(y_temp-y_temp2)>error[0])
        {
            error[0] = fabs(y_temp-y_temp2);
            //std::cout<<y_temp<<"-"<<y_temp2<<"="<<y_temp-y_temp2<<std::endl;
        }
        error[1] += fabs((y_temp-y_temp2)/y_temp);
        error[2] += ((y_temp-y_temp2))*((y_temp-y_temp2));
    }
    //err[0]得到的是在区间内最大的误差
    //err[1]得到的是平均最大相对误差
    //err[2]是均方根误差
    error[1] = error[1]/100;
    error[2] = sqrt(error[2]/100);
    for(int i = 0;i < 3;i++)
    {
        std::cout<<"误差"<<i+1<<":"<<error[i]<<std::endl;
    }
    delete[] x;
    delete[] y;
    return 0;
}

结果分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

与直接的多项式插值法结果比较,其速度更快,在阶数相同时保持一致的误差,并且随着插值点数的增加,误差还可以进一步减小,对于测试函数 x + 3 \sqrt{x+3} x+3 其龙格现象没那么明显。

误差分析

为了评估插值函数在计算某个x时的误差,计 R n ( x ) = f ( x ) − L n ( x ) R_n(x)=f(x)-L_n(x) Rn(x)=f(x)Ln(x),可以得到 R n ( x ) = f ( n + 1 ) ( ξ x ) ( n + 1 ) ! ∏ i = 0 n ( x − x i ) ( min ⁡ ( x 0 , x 1 , ⋯ x n ) ≤ ξ ≤ max ⁡ ( x 0 , x 1 , ⋯ x n ) ) R_n(x)=\frac{f^{(n+1)(\xi _x)}}{(n+1)!}\prod_{i=0}^n{(x-x_i)}(\min{(x_0,x_1,\cdots x_n)\le \xi \le \max{(x_0,x_1,\cdots x_n)}}) Rn(x)=(n+1)!f(n+1)(ξx)i=0n(xxi)(min(x0,x1,xn)ξmax(x0,x1,xn)),但是用插值函数的时候,意味着原函数在特定的平台较难计算,因此为了方便估计这个误差,使用事后估计法:设 L n ( x ) L_n(x) Ln(x) f ( x ) f(x) f(x) x 0 , x 1 , ⋯ x n x_0,x_1,\cdots x_n x0,x1,xn为节点的插值多项式,另外取一个节点 x n + 1 x_{n+1} xn+1,记 L n ( 1 ) ( x ) L_n^{(1)}(x) Ln(1)(x) f ( x ) f(x) f(x) x 1 , x 2 ⋯ x n , x n + 1 x_1,x_2\cdots x_n,x_{n+1} x1,x2xn,xn+1为节点的插值多项式,则有
f ( x ) − L n ( x ) = f ( n + 1 ) ( ξ 1 ) ( n + 1 ) ! ∏ i = 0 n ( x − x i ) f(x)-L_n(x)=\frac{f^{(n+1)(\xi _1)}}{(n+1)!}\prod_{i=0}^n{(x-x_i)} f(x)Ln(x)=(n+1)!f(n+1)(ξ1)i=0n(xxi)
f ( x ) − L n ( 1 ) ( x ) = f ( n + 1 ) ( ξ 2 ) ( n + 1 ) ! ∏ i = 1 n + 1 ( x − x i ) f(x)-L^{(1)}_n(x)=\frac{f^{(n+1)(\xi _2)}}{(n+1)!}\prod_{i=1}^{n+1}{(x-x_i)} f(x)Ln(1)(x)=(n+1)!f(n+1)(ξ2)i=1n+1(xxi)
由于 f ( n + 1 ) ( x ) f^{(n+1)}(x) f(n+1)(x)在插值区间上变化不大,因此可以认为 f ( x ) − L n ( x ) f ( x ) − L n ( 1 ) ( x ) ≈ x − x 0 x − x n + 1 \frac{f(x)-L_n(x)}{f(x)-L^{(1)}_n(x)}\approx \frac{x-x_0}{x-x_{n+1}} f(x)Ln(1)(x)f(x)Ln(x)xxn+1xx0
转化一下可以得到
f ( x ) − L n ( x ) ≈ x − x 0 x n + 1 − x 0 ( L n ( x ) − L n ( 1 ) ( x ) ) f(x)-L_n(x)\approx \frac{x-x_0}{x_{n+1}-x_0}(L_n(x)-L^{(1)}_n(x)) f(x)Ln(x)xn+1x0xx0(Ln(x)Ln(1)(x))

这个估计误差的方法就被称为时候估计法,当需要计算某次插值的计算误差时,就可以直接使用该方法进行误差估计。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是分段插值、分段插值拉格朗日插值C++代码示例: 分段插值: ```cpp #include <iostream> using namespace std; int main() { int x[] = {1, 2, 3, 4, 5}; // x 值 int y[] = {1, 4, 9, 16, 25}; // y 值 int n = 5; int x0 = 3; // 待插值点 int i; for (i = 0; i < n-1; i++) { if (x[i] <= x0 && x0 <= x[i+1]) { // 找到待插值点所在区间 break; } } double k = (y[i+1] - y[i]) / (double)(x[i+1] - x[i]); // 斜率 double b = y[i] - k * x[i]; // 截距 double y0 = k * x0 + b; // 插值结果 cout << "f(" << x0 << ") = " << y0 << endl; // 输出结果 return 0; } ``` 分段插值: ```cpp #include <iostream> using namespace std; int main() { int x[] = {1, 2, 3, 4, 5}; // x 值 int y[] = {1, 4, 9, 16, 25}; // y 值 int n = 5; int x0 = 3; // 待插值点 int i; for (i = 0; i < n-1; i++) { if (x[i] <= x0 && x0 <= x[i+1]) { // 找到待插值点所在区间 break; } } double k1, k2, b1, b2; if (i == 0) { // 特判第一段 k1 = (y[i+1] - y[i]) / (double)(x[i+1] - x[i]); b1 = y[i] - k1 * x[i]; k2 = (y[i+2] - y[i+1]) / (double)(x[i+2] - x[i+1]); b2 = y[i+1] - k2 * x[i+1]; } else if (i == n-2) { // 特判最后一段 k1 = (y[i-1] - y[i-2]) / (double)(x[i-1] - x[i-2]); b1 = y[i-1] - k1 * x[i-1]; k2 = (y[i] - y[i-1]) / (double)(x[i] - x[i-1]); b2 = y[i] - k2 * x[i]; } else { // 一般情况 k1 = (y[i] - y[i-1]) / (double)(x[i] - x[i-1]); b1 = y[i] - k1 * x[i]; k2 = (y[i+2] - y[i+1]) / (double)(x[i+2] - x[i+1]); b2 = y[i+1] - k2 * x[i+1]; } double a = (k2 - k1) / (double)(2 * (x[i+1] - x[i])); // 次项系数 double b = k1 - 2 * a * x[i]; // 一次项系数 double c = y[i] - a * x[i] * x[i] - b * x[i]; // 常数项系数 double y0 = a * x0 * x0 + b * x0 + c; // 插值结果 cout << "f(" << x0 << ") = " << y0 << endl; // 输出结果 return 0; } ``` 拉格朗日插值: ```cpp #include <iostream> using namespace std; int main() { int x[] = {1, 2, 3, 4, 5}; // x 值 int y[] = {1, 4, 9, 16, 25}; // y 值 int n = 5; int x0 = 3; // 待插值点 double y0 = 0; for (int i = 0; i < n; i++) { double li = 1; for (int j = 0; j < n; j++) { if (i != j) { li *= (x0 - x[j]) / (double)(x[i] - x[j]); } } y0 += y[i] * li; } cout << "f(" << x0 << ") = " << y0 << endl; // 输出结果 return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值