Hermite插值法(C++)

Hermite插值法是一种常用的数值方法, 主要用于在给定的数据点集上构造一个可微的函数来近似描述这些数据点的趋势和特征. 它可以通过已知的函数值和导数值来推断出未知的函数值和导数值, 并且可以用于多元插值问题. Hermite插值法构造的多项式是可微的, 这意味着它可以用来近似描述数据点的导数和曲率等特征, 对于需要对数据进行微积分分析的问题非常有用, 例如优化、控制和工程设计等领域. 此外, Hermite插值法可以通过增加数据点的函数值和导数来扩展到高次多项式, 从而提高插值的精度和可微性.

问题描述

给定函数 f ( x ) f(x) f(x)在区间 [ a , b ] [a,b] [a,b] n + 1 n+1 n+1个互异节点 x 0 , x 1 , ⋯   , x n x_0,x_1,\cdots,x_n x0,x1,,xn处的函数值, 以及直到 m i m_i mi阶的导数值 f ( x i ) , f ′ ( x i ) , ⋯   , f ( m i ) ( x i ) f(x_i),f^\prime(x_i),\cdots,f^{(m_i)}(x_i) f(xi),f(xi),,f(mi)(xi). 令 m = ∑ i = 0 n ( m i + 1 ) − 1 m=\sum\limits_{i=0}^n(m_i+1)-1 m=i=0n(mi+1)1, 求一个次数不超过 m m m的多项式 H m ( x ) H_m(x) Hm(x), 使得
H m ( j ) ( x i ) = f ( j ) ( x i ) , i = 0 , 1 , ⋯   , n , j = 0 , ⋯   , m i H_m^{(j)}(x_i)=f^{(j)}(x_i),i=0,1,\cdots,n,j=0,\cdots,m_i Hm(j)(xi)=f(j)(xi),i=0,1,,n,j=0,,mi

求解方法

Lagrange型Hermite插值法

m m m次Lagrange插值多项式可以表示为:
H m ( x ) = ∑ i = 0 n ∑ k = 0 m i f ( k ) ( x i ) l i k ( x ) H_m(x)=\sum_{i=0}^n\sum_{k=0}^{m_i}f^{(k)}(x_i)l_{ik}(x) Hm(x)=i=0nk=0mif(k)(xi)lik(x)

其中,
l i k ( x ) = 1 k ! { 1 ∏ j = 0 j ≠ i n ( x − x j ) m j + 1 } x i m i − k ( x − x i ) k ∏ j = 0 j ≠ i n ( x − x j ) m j + 1 l_{ik}(x)=\frac1{k!}\left\{\frac1{\prod\limits_{j=0\atop j\neq i}^n(x-x_j)^{m_j+1}}\right\}^{m_i-k}_{x_i}(x-x_i)^k\prod_{j=0\atop j\neq i}^n(x-x_j)^{m_j+1} lik(x)=k!1 j=ij=0n(xxj)mj+11 ximik(xxi)kj=ij=0n(xxj)mj+1
为Lagrange插值基函数, { φ ( x ) } x 0 k \{\varphi(x)\}_{x_0}^k {φ(x)}x0k表示函数 φ ( x ) \varphi(x) φ(x) x 0 x_0 x0处的 k k k次泰勒展开.

由于Lagrange型Hermite插值法的表达式中出现了泰勒展开, 无法化简为一般的初等函数形式, 因此不便于实现一般的Lagrange型Hermite插值法.

Newton型Hermite插值法

Newton型Hermite插值法是一种更常用和易于实现的Hermite插值方法. 它使用Newton插值多项式的形式, 并考虑已知函数值和导数值来构造插值多项式. 以下是Newton型Hermite插值法的描述:

给定函数 f ( x ) f(x) f(x)在区间 [ a , b ] [a, b] [a,b] n + 1 n+1 n+1个互异节点 x 0 , x 1 , … , x n x_0, x_1, \ldots, x_n x0,x1,,xn处的函数值, 以及直到 m i m_i mi阶的导数值 f ( x i ) , f ′ ( x i ) , … , f ( m i ) ( x i ) f(x_i), f^\prime(x_i), \ldots, f^{(m_i)}(x_i) f(xi),f(xi),,f(mi)(xi). 令 m = ∑ i = 0 n ( m i + 1 ) − 1 m = \sum\limits_{i=0}^n (m_i+1) - 1 m=i=0n(mi+1)1, 我们希望找到一个次数不超过 m m m的插值多项式 H m ( x ) H_m(x) Hm(x), 满足以下条件:
H m ( j ) ( x i ) = f ( j ) ( x i ) , i = 0 , 1 , … , n , j = 0 , 1 , … , m i H_m^{(j)}(x_i) = f^{(j)}(x_i), \quad i = 0, 1, \ldots, n, \quad j = 0, 1, \ldots, m_i Hm(j)(xi)=f(j)(xi),i=0,1,,n,j=0,1,,mi

Newton型Hermite插值法的多项式表示如下:
H m ( x ) = f ( x 0 ) + f [ x 0 , x 0 ] ( x − x 0 ) + ⋯ + f [ x 0 , x 0 , ⋯   , x 0 ⏟ m 0 + 1 ] ( x − x 0 ) m 0 + f [ x 0 , x 0 , ⋯   , x 0 ⏟ m 0 + 1 , x 1 ] ( x − x 0 ) m 0 + 1 + ⋯ + f [ x 0 , x 0 , ⋯   , x 0 ⏟ m 0 + 1 , x 1 , x 1 , ⋯   , x 1 ⏟ m 1 + 1 ] ( x − x 0 ) m 0 + 1 ( x − x 0 ) m 1 + ⋯ + f [ x 0 , x 0 , ⋯   , x 0 ⏟ m 0 + 1 , ⋯   , x n − 1 , x n − 1 , ⋯   , x n − 1 ⏟ m n − 1 + 1 , x n ] ∏ i = 0 n − 1 ( x − x i ) m i + 1 + ⋯ + f [ x 0 , x 0 , ⋯   , x 0 ⏟ m 0 + 1 , ⋯   , x n − 1 , x n − 1 , ⋯   , x n − 1 ⏟ m n − 1 + 1 , x n , x n , ⋯   , x n ⏟ m n + 1 ] ( x − x n ) m n ∏ i = 0 n − 1 ( x − x i ) m i + 1 \begin{aligned} H_{m}(x)=&f(x_{0})+f[x_{0},x_{0}](x-x_{0})+\cdots+f[\underbrace{x_{0},x_{0},\cdots,x_{0}}_{m_{0}+1}](x-x_{0})^{m_{0}}+ \\ &f[\underbrace{x_{0},x_{0},\cdots,x_{0}}_{m_{0}+1},x_{1}](x-x_{0})^{m_{0}+1}+\cdots+ \\ &f[\underbrace{x_{0},x_{0},\cdots,x_{0}}_{m_{0}+1},\underbrace{x_{1},x_{1},\cdots,x_{1}}_{m_{1}+1}](x-x_{0})^{m_{0}+1}(x-x_{0})^{m_{1}}+\cdots+ \\ &f[\underbrace{x_{0},x_{0},\cdots,x_{0}}_{m_0+1},\cdots,\underbrace{x_{n-1},x_{n-1},\cdots,x_{n-1}}_{m_{n-1}+1},x_{n}]\prod_{i=0}^{n-1}(x-x_{i})^{m_{i}+1}+\cdots+ \\ &f[\underbrace{x_{0},x_{0},\cdots,x_{0}}_{m_{0}+1},\cdots,\underbrace{x_{n-1},x_{n-1},\cdots,x_{n-1}}_{m_{n-1}+1},\underbrace{x_{n},x_{n},\cdots,x_{n}}_{m_{n}+1}](x-x_{n})^{m_{n}}\prod_{i=0}^{n-1}(x-x_{i})^{m_{i}+1} \end{aligned} Hm(x)=f(x0)+f[x0,x0](xx0)++f[m0+1 x0,x0,,x0](xx0)m0+f[m0+1 x0,x0,,x0,x1](xx0)m0+1++f[m0+1 x0,x0,,x0,m1+1 x1,x1,,x1](xx0)m0+1(xx0)m1++f[m0+1 x0,x0,,x0,,mn1+1 xn1,xn1,,xn1,xn]i=0n1(xxi)mi+1++f[m0+1 x0,x0,,x0,,mn1+1 xn1,xn1,,xn1,mn+1 xn,xn,,xn](xxn)mni=0n1(xxi)mi+1
其中, 上式中出现了重节点的差商, 对于这类特殊差商, 可以用如下极限的思想得出其值:
f [ x 0 , x 0 ] = lim ⁡ x → x 0 f [ x , x 0 ] = lim ⁡ x → x 0 f ( x 0 ) − f ( x ) x 0 − x = f ′ ( x 0 ) f\left[x_0,x_0\right]=\lim_{x\to x_0}f\left[x,x_0\right]=\lim_{x\to x_0}\frac{f(x_0)-f(x)}{x_0-x}=f'(x_0) f[x0,x0]=xx0limf[x,x0]=xx0limx0xf(x0)f(x)=f(x0)
f [ x 0 , x 0 , ⋯   , x 0 ⏟ k + 1 ] = lim ⁡ x i → x 0 f [ x 0 , x 1 , ⋯   , x k ] = lim ⁡ x i → x 0 f ( k ) ( η ) k ! = f ( k ) ( x 0 ) k ! f[\underbrace{x_{0},x_{0},\cdots,x_{0}}_{k+1}]=\lim_{x_{i}\rightarrow x_{0}}f[x_{0},x_{1},\cdots,x_{k}]=\lim_{x_{i}\rightarrow x_{0}}\frac{f^{(k)}(\eta)}{k!}=\frac{f^{(k)}(x_{0})}{k!} f[k+1 x0,x0,,x0]=xix0limf[x0,x1,,xk]=xix0limk!f(k)(η)=k!f(k)(x0)
f [ x 0 , x 0 , x 1 ] = lim ⁡ x → x 0 f [ x , x 0 , x 1 ] = lim ⁡ x → x 0 f [ x 0 , x 1 ] − f [ x , x 0 ] x 1 − x = f [ x 0 , x 1 ] − f [ x 0 , x 0 ] x 1 − x 0 f\left[x_0,x_0,x_1\right]=\lim_{x\to x_0}f\left[x,x_0,x_1\right]=\lim_{x\to x_0}\frac{f\left[x_0,x_1\right]-f\left[x,x_0\right]}{x_1-x}=\frac{f\left[x_0,x_1\right]-f\left[x_0,x_0\right]}{x_1-x_0} f[x0,x0,x1]=xx0limf[x,x0,x1]=xx0limx1xf[x0,x1]f[x,x0]=x1x0f[x0,x1]f[x0,x0]

代码实现

本文使用Newton型Hermite插值多项式实现Hermite插值, 首先需要包含头文件:

#include <armadillo>
#include <vector>
using namespace arma;
using namespace std;

另外需要引入检查向量是否包含重复元素的函数, 见多项式插值法(基于c++,armadillo库):

namespace _MYFUNCTION {
    extern bool check_unique(const vec&);
}

首先定义差商表类:

// 差商表
class Difference_quotient_table
{
    double *x;                 // 观测值
    double *y;                 // 差商值
    unsigned *m;               // 当前导数阶数
    const mat *Y;              // 观测数据
    const vector<unsigned> *M; // 导数阶数
    unsigned k;                // 差商数
    unsigned step;             // 观测点的步长
    unsigned *pre_m;           // 当前导数阶数的前一个位置
    unsigned fact;             // 阶乘
    double *x_out;             // 输出当前x
    //    static constexpr double eps=1e-100;
public:
    Difference_quotient_table(const vec *x0, const mat *y0, const vector<unsigned> *M) : Y(y0), step(0), fact(1)
    {
        unsigned i(k = (this->M = M)->size()), *m_p(m = new unsigned[k] + k);
        auto M_p(M->cend());
        do
        {
            if ((*--m = *--M_p) > y0->n_cols)
            {
                delete[] (m -= --i);
                throw "导数数据缺失!";
            }
            k += *m;
        } while (--i);
        y = new double[k] + k;
        x = new double[k] + k;
        const double *y_p(&y0->at(M->size())), *x_p(&x0->at(M->size())); // armadillo库的矩阵是按列存储的
        do
        {
            *--x = *--x_p, *--y = *--y_p;
            if (i = *--m_p)
                do
                    *--x = *x_p, *--y = *y_p;
                while (--i);
        } while (m_p != m);
        pre_m = m - 1;
        --(x_out = x);
    }
    ~Difference_quotient_table() noexcept
    {
        delete[] y;
        delete[] x;
        delete[] m;
    }
    // 进行差商
    bool diff()
    {
        if (!--k)
            return false; // 只有1个元素, 不需要计算
        unsigned d_k(++step);
        fact *= step;
        unsigned *m_p(pre_m), i(0);
        double *p(y), *q(x), *q1(q + step);
        do
            if (*q != *q1) // abs(*p-=*(p+1))>eps
                (*p -= *(p + 1)) /= *q - *q1;
            else
            {
                while (!*++m_p)
                    ; // 找到第1个未完全计算的重节点差商
                unsigned j(*m_p);
                i += --*m_p;
                q += *m_p;
                q1 += *m_p;
                const double t(Y->at(m_p - m, d_k) / fact);
                *p = t;
                while (--j)
                    *++p = t;
            }
        while (++q, ++q1, ++p, ++i != k);
        return true;
    }
    double &get_diff() noexcept
    {
        return *y;
    }
    double &get_x() noexcept
    {
        return *++x_out;
    }
};

利用差商表可以很容易实现Newton型Hermite插值法如下:

/*
 * Hermite插值法
 * x0:观测点
 * y0:观测数据(第i列为i阶导数)
 * m :每个节点有效的导数阶数
 * x1:插值点
 * y1:预测值
 */
void Hermite(const vec &x0, const mat &y0, const vector<unsigned> &m, const vec &x1, vec &y1)
{
    if (x1.empty())
    {
        y1.clear();
        return;
    }
    if (x0.empty() || y0.empty())
        throw "观测集为空!";
    if (m.size() != x0.n_elem)
        throw "导数阶数向量与观测点向量大小不一致!";
    if (x0.n_elem != y0.n_rows)
        throw "观测点与观测数据向量大小不一致!";
    if (_MYFUNCTION::check_unique(x0))
        throw "观测集包含重复元素!";
    Difference_quotient_table D(&x0, &y0, &m);
    y1.set_size(x1.n_elem).fill(D.get_diff());
    vec x(ones(x1.n_elem));
    while (D.diff())
        y1 += D.get_diff() * (x %= x1 - D.get_x());
}

注意: 在此程序中观测点矩阵的第 i i i列代表第 i − 1 i-1 i1阶导数, 若输入的导数阶数大于等于观测点矩阵将弹出错误提示.

测试案例

求4次Newton型Hermite插值多项式 H 4 ( x ) H_4(x) H4(x), 使得 H 4 ( 0 ) = 1 , H 4 ′ ( 0 ) = 2 , H 4 ′ ′ ( 0 ) = 3 , H 4 ( 1 ) = 4 , H 4 ′ ( 1 ) = 5 H_4(0)=1,H_4^\prime(0)=2,H_4^{\prime\prime}(0)=3,H_4(1)=4,H_4^\prime(1)=5 H4(0)=1,H4(0)=2,H4′′(0)=3,H4(1)=4,H4(1)=5.

使用Newton型Hermite插值计算, 将观测点取为 ( 0 , 1 ) T (0,1)^T (0,1)T, 观测数据取为
( 1 2 3 4 5 0 ) \begin{pmatrix} 1&2&3\\4&5&0 \end{pmatrix} (142530)

其中, 观测数据的第2行第3列的数据是任取的.

#include <stdio.h>

// Hermite插值代码略

int main()
{
    // 正确答案
    auto ans = [](double x) -> double
    {
        return (((1.5 * x - 2) * x + 1.5) * x + 2) * x + 1;
    };
    vec x0{0., 1.}, x1(linspace(0, 1)), f_x1(x1), y1;
    f_x1.transform(ans);
    mat y0{{1, 2, 3}, {4, 5, 0}};
    vector<unsigned> m;
    m.push_back(2);
    m.push_back(1);
    try
    {
        Hermite(x0, y0, m, x1, y1);
        FILE *file;
        if (fopen_s(&file, "data.csv", "w"))
            throw "文件保存失败!";
        for (unsigned i(0); i != 101; ++i)
            fprintf(file, "%f,%f,%f\n", x1.at(i), y1.at(i), f_x1.at(i));
        fclose(file);
    }
    catch (const char *s)
    {
        printf(s);
    }
    return 0;
}

最终, 我们得到了运行结果, 并利用Origin画图如下:Hermite插值

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Hermite插值法是数值分析中一种经典的插值方法,旨在通过已有的数据点来构建一个多项式模型,以预测其他未知点的数值。相比于其他插值方法,Hermite插值法更加高效和精确,可以在较少的数据点条件下实现高精度的插值。 在Python中,可以使用SciPy库中的interp1d函数来实现Hermite插值。该函数提供了一种简单的方式来构建多项式函数,并对新的数据点进行预测。具体步骤如下: 1. 导入SciPy库。使用以下代码将SciPy库导入你的Python程序中: ```python import scipy.interpolate as interp ``` 2. 定义已有的数据点。假设有N个已知的数据点,将它们按照x轴升序排列,存储为两个数组x和y(长度均为N)。 3. 构建Hermite插值函数。使用以下代码构建Hermite插值函数h: ```python h = interp.interp1d(x, y, kind='cubic') ``` 其中,kind参数表示使用的插值方法,cubic表示采用三次Hermite插值方法。也可以使用其他插值方法,如linear表示线性插值方法。 4. 在新的数据点上进行预测。使用以下代码,对新的数据点x_new进行预测: ```python y_new = h(x_new) ``` 其中,y_new为预测得到的新数据点的y值。 总之,Hermite插值法是数值分析中一种重要的插值方法。在Python中,可以使用SciPy库的interp1d函数进行实现,并且可以轻松预测新的数据点。 ### 回答2: Hermite插值法是一种用于曲线和表面拟合的方法,它通过利用数据点和其导数来进行插值。在数值分析中,Hermite插值法是通过对已知函数的某些导数和函数值进行插值,来得到一个多项式函数,以近似表示这个函数。 在Python中,可以使用SciPy的插值函数来实现Hermite插值法。具体实现过程如下: 1.导入相关模块 ``` from scipy.interpolate import Hermite import numpy as np ``` 2.定义数据点和导数 ``` x = np.array([1, 2, 3, 4, 5]) y = np.array([1, 3, 8, 10, 12]) dydx = np.array([0, 2, 6, 4, 2]) ``` 其中,x和y为已知数据点,dydx为已知导数。 3.进行插值 ``` hermite = Hermite(x, y, dydx) ``` 4.绘制插值曲线 ``` import matplotlib.pyplot as plt plt.plot(x, y, 'o', label='Data') xn = np.linspace(x.min(), x.max(), 100) plt.plot(xn, hermite(xn), label='Hermite') plt.legend(loc='best') plt.show() ``` 运行后即可得到插值曲线。 总的来说,Hermite插值法是一种重要的数值分析方法,可以在数据点和其导数不充分的情况下,通过插值得到一个近似的数据曲线。在Python中,利用SciPy的插值函数可以轻松实现Hermite插值法。 ### 回答3: Hermite插值法是一种高阶插值方法,通过已知的数据点来构造一个多项式来近似曲线。它可以高效地在给出的数据点处进行插值,并且可以处理一些曲线在某些点上的一阶和二阶导数值不确定的情况。 在Python中,可以使用scipy库中的hermite插值函数来进行计算。该函数名为scipy.interpolate.hermite_interp1d。接受的参数包括已知的数据点、相应的一阶导数和二阶导数值。我们可以使用这个函数来计算曲线在给定点的插值值。 具体操作如下: 首先,引入必要的库和数据: ```python import numpy as np from scipy.interpolate import hermite_interp1d # 构造数据点 x = np.linspace(-1,1,9) y = np.cos(x*np.pi) ``` 接下来,计算数据点处的一阶导数和二阶导数。可以使用numpy库中的diff函数来计算一阶导数,使用scipy库中的ndimage函数来计算二阶导数。 ```python # 计算一阶导数 dydx = np.diff(y)/np.diff(x) # 计算二阶导数 from scipy import ndimage dy2dx2 = ndimage.sobel(dydx, axis=0, mode='constant') ``` 使用hermite_interp1d函数来进行插值: ```python f = hermite_interp1d(x,y,dydx,dy2dx2) # 计算插值值 x_new = np.linspace(-1,1,101) y_new = f(x_new) ``` 最后,我们可以使用matplotlib库来绘制原始数据点和计算出的插值曲线。 完整代码如下: ```python import numpy as np from scipy.interpolate import hermite_interp1d import matplotlib.pyplot as plt # 构造数据点 x = np.linspace(-1,1,9) y = np.cos(x*np.pi) # 计算一阶导数 dydx = np.diff(y)/np.diff(x) # 计算二阶导数 from scipy import ndimage dy2dx2 = ndimage.sobel(dydx, axis=0, mode='constant') # 插值 f = hermite_interp1d(x,y,dydx,dy2dx2) # 计算插值值 x_new = np.linspace(-1,1,101) y_new = f(x_new) # 绘制原始数据和插值曲线 plt.plot(x,y,'o',label='data') plt.plot(x_new,y_new,label='hermite interp') plt.legend(loc='best') plt.show() ``` 注意到hermite_interp1d函数要求一阶和二阶导数值在端点处是已知的。因此,当数据点不够多时,需使用其他技术进行外推,来计算边界点的导数值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zsc_118

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值