复化求积法(C++)

复化求积法是一种数值积分的方法, 它通过将积分区间进行分段, 并在各分段子区间上采用低阶的Newton-Cotes求积公式, 对各个小区间上的积分值进行近似, 最后再累加起来. 这种方法避免了随着插值点增多导致的高阶Newton-Cotes公式不稳定的缺陷, 常用的复化求积法有复化梯形求积和复化Simpson求积.

复化梯形公式

复化梯形公式是一种常用的复化求积法, 它通过将积分区间分成若干小区间, 并在每个小区间上使用梯形公式进行近似积分, 最后将所有小区间的积分值相加得到近似积分值.

具体地, 对于一个在 [ a , b ] [a,b] [a,b]上的定积分 ∫ a b f ( x ) d x \displaystyle\int_{a}^{b}f(x){\rm d}x abf(x)dx, 我们可以将其分成 n n n个小区间 [ x i − 1 , x i ] [x_{i-1},x_i] [xi1,xi], 其中 i = 1 , 2 , . . . , n i=1,2,...,n i=1,2,...,n, 并设 f ( x ) f(x) f(x)在每个小区间的端点处取值分别为 f ( x i − 1 ) f(x_{i-1}) f(xi1) f ( x i ) f(x_i) f(xi). 在每个小区间上, 我们使用梯形公式进行近似积分, 得到 Δ x i \Delta x_i Δxi 1 2 [ f ( x i − 1 ) + f ( x i ) ] \dfrac12[f(x_{i-1})+f(x_i)] 21[f(xi1)+f(xi)], 其中 Δ x i = x i − x i − 1 \Delta x_i=x_i-x_{i-1} Δxi=xixi1. 然后我们将所有小区间的积分值相加, 得到近似积分值

∫ a b f ( x ) d x ≈ ∑ i = 1 n 1 2 [ f ( x i − 1 ) + f ( x i ) ] Δ x i \int_{a}^{b}f(x)dx\approx\sum_{i=1}^{n}\frac12[f(x_{i-1})+f(x_i)]\Delta x_i abf(x)dxi=1n21[f(xi1)+f(xi)]Δxi

若记 T n ( f ) T_n(f) Tn(f)为将区间 [ a , b ] [a,b] [a,b]进行 n n n等分割所得的积分值, 则上式可进一步化简得
T n ( f ) = ∑ k = 0 n − 1 h 2 [ f ( x k ) + f ( x k + 1 ) ] = h 2 [ f ( a ) + f ( b ) + 2 ∑ k = 1 n − 1 f ( x k ) ] T_n(f)=\sum_{k=0}^{n-1}\frac{h}{2}\Big[f(x_k)+f(x_{k+1})\Big]=\frac{h}{2}\Bigg[f(a)+f(b)+2\sum_{k=1}^{n-1}f(x_k)\Bigg] Tn(f)=k=0n12h[f(xk)+f(xk+1)]=2h[f(a)+f(b)+2k=1n1f(xk)]

其中, h : = b − a n h:=\dfrac{b-a}n h:=nba为每个小区间的测度. 特别地, 根据上式, 我们可以得到如下递推式:
T 2 n ( f ) = 1 2 T n ( f ) + h 2 ∑ k = 0 n − 1 f ( x k + 1 2 ) T_{2n}(f)=\frac{1}{2}T_n(f)+\frac{h}{2}\sum\limits_{k=0}^{n-1}f(x_{k+\frac{1}{2}}) T2n(f)=21Tn(f)+2hk=0n1f(xk+21)

根据中值定理, 复化梯形公式的余项可表示如下:
∃ η ∈ ( a , b ) , R T n ( f ) = I − T n ( f ) = − b − a 12 h 2 f ′ ′ ( η ) \exists\eta\in(a,b),R_{T_n}(f)=I-T_n(f)=-\dfrac{b-a}{12}h^2f^{\prime\prime}(\eta) η(a,b),RTn(f)=ITn(f)=12bah2f′′(η)

f ′ ′ ( x ) f^{\prime\prime}(x) f′′(x)在区间 [ a , b ] [a,b] [a,b]上连续, 且其函数值变化不大时, 即有 f ′ ′ ( η n ) ≈ f ′ ′ ( η 2 n ) f^{\prime\prime}(\eta_n)\approx f^{\prime\prime}(\eta_{2n}) f′′(ηn)f′′(η2n), 则有
I − T n ( f ) I − T 2 n ( f ) ≈ 4 \frac{I-T_n(f)}{I-T_{2n}(f)}\approx4 IT2n(f)ITn(f)4


I − T 2 n ( f ) ≈ 1 3 ( T 2 n ( f ) − T n ( f ) ) I-T_{2n}(f)\approx\frac13(T_{2n}(f)-T_n(f)) IT2n(f)31(T2n(f)Tn(f))

因此, 对于某个允许误差 ε \varepsilon ε, 可以使用 ∣ T 2 n ( f ) − T n ( f ) ∣ < ε |T_{2n}(f)-T_n(f)|<\varepsilon T2n(f)Tn(f)<ε来判断 T 2 n ( f ) T_{2n}(f) T2n(f)是否达到了精度要求.

当被积函数的函数值变化较大时, 为了提高计算精度, 可以在每个小区间的中点处再构造一个梯形公式进行近似积分, 即利用递推式将分割逐渐加细, 从而得到更高精度的积分.

复化Simpson公式

复化Simpson公式与复化梯形公式类似, 但每个小区间上要取3个点, 因此观测点数量需为大于1的奇数. 下面仅考虑分割为等分分割的情况.

将积分区间分为 n n n等分, 分点为 x k = a + k h , k = 0 , 1 , ⋯   , n x_k=a+kh,k=0,1,\cdots,n xk=a+kh,k=0,1,,n, 其中, h : = b − a n h:=\dfrac{b-a}n h:=nba. 记区间 [ x k , x k + 1 ] [x_k,x_{k+1}] [xk,xk+1]的中点为 x k + 1 2 x_{k+\frac12} xk+21, 在每个小区间上应用Simpson公式, 有
S n ( f ) = h 6 [ f ( a ) + 2 ∑ k = 1 n − 1 f ( x k ) + 4 ∑ k = 0 n − 1 f ( x k + 1 2 ) + f ( b ) ] S_n(f)=\frac{h}{6}\bigg[f(a)+2\sum_{k=1}^{n-1}f(x_k)+4\sum_{k=0}^{n-1}f(x_{k+\frac{1}{2}})+f(b)\bigg] Sn(f)=6h[f(a)+2k=1n1f(xk)+4k=0n1f(xk+21)+f(b)]

与复化梯形公式类似, 复化Simpson公式也有如下递推公式, 余项估计及允许误差估计如下:
S 2 n ( f ) = 1 2 S n ( f ) + h 6 ∑ k = 0 n − 1 [ 4 f ( x k + 1 4 ) − 2 f ( x k + 1 2 ) + 4 f ( x k + 3 4 ) ] S_{2n}(f)=\frac12S_n(f)+\frac h6\sum_{k=0}^{n-1}\left[4f(x_{k+\frac14})-2f(x_{k+\frac12})+4f(x_{k+\frac34})\right] S2n(f)=21Sn(f)+6hk=0n1[4f(xk+41)2f(xk+21)+4f(xk+43)]
∃ η ∈ ( a , b ) , R S n ( f ) = I − S n ( f ) = − b − a 2880 h 4 f ( 4 ) ( η ) \exists\eta\in(a,b),R_{S_n}(f)=I-S_n(f)=-\dfrac{b-a}{2880}h^4f^{(4)}(\eta) η(a,b),RSn(f)=ISn(f)=2880bah4f(4)(η)
I − S 2 n ( f ) ≈ 1 15 ( S 2 n ( f ) − S n ( f ) ) I-S_{2n}(f)\approx\frac1{15}(S_{2n}(f)-S_n(f)) IS2n(f)151(S2n(f)Sn(f))

因此同样可以利用上式逐步对区间的分割进行加细, 从而得到更高精度的积分.

算法实现

首先需要包含头文件:

#include <functional>
#include <math.h>
using namespace std;

复化梯形公式

首先实现了一般积分的复化梯形公式如下:

/*
 * 复化梯形公式
 * f :被积函数
 * a :积分下限
 * b :积分上限
 * e :精度
 */
double composite_trapezoid(function<double(double)> f, const double &a, const double &b, const double &e = 1e-6)
{
    if (a >= b)
        throw "区间上限小于区间下限!";
    double I(f(a));
    if (isnan(I) || isnan(I += f(b)))
        throw "区间内存在奇点!";
    double step(b - a), pre_I(I *= step /= 2);
    if (isnan((I /= 2) += step * f(a + step)))
        throw "区间内存在奇点!";
    while (abs(I - pre_I) > e)
    {
        double h(step), x(a + (step /= 2)), s(f(x));
        if (isnan(s))
            throw "区间内存在奇点!";
        while ((x += h) < b)
            if (isnan(s += f(x)))
                throw "区间内存在奇点!";
        pre_I = I;
        (I /= 2) += step * s;
    }
    return I;
}

考虑到某些积分上下限可能为奇点, 在上面程序的基础上提供了手动输入边界函数值的实现:

/*
 * 复化梯形公式(端点可能为奇点)
 * f :被积函数
 * a :积分下限
 * b :积分上限
 * fa:积分下限处的函数值
 * fb:积分上限处的函数值
 * e :精度
 */
double composite_trapezoid(function<double(double)> f, const double &a, const double &b, const double &fa, const double &fb, const double &e = 1e-6)
{
    if (a >= b)
        throw "区间上限小于区间下限!";
    double step(b - a), I(fa + fb), pre_I(I *= step /= 2);
    if (isnan((I /= 2) += step * f(a + step)))
        throw "区间内存在奇点!";
    while (abs(I - pre_I) > e)
    {
        double h(step), x(a + (step /= 2)), s(f(x));
        if (isnan(s))
            throw "区间内存在奇点!";
        while ((x += h) < b)
            if (isnan(s += f(x)))
                throw "区间内存在奇点!";
        pre_I = I;
        (I /= 2) += step * s;
    }
    return I;
}

复化Simpson公式

/*
 * 复化Simpson公式(端点可能为奇点)
 * f :被积函数
 * a :积分下限
 * b :积分上限
 * fa:积分下限处的函数值
 * fb:积分上限处的函数值
 * e :精度
 */
double composite_Simpson(function<double(double)> f, const double &a, const double &b, const double &fa, const double &fb, const double &e = 1e-6)
{
    if (a >= b)
        throw "区间上限小于区间下限!";
    double step(b - a), m(a + (step /= 2)), fx(f(m));
    if (isnan(fx))
        throw "区间内存在奇点!";
    double I((fx * 4 + fa + fb) / 6), pre_I(I), pre_fx(fx), h(step);
    fx = f(a + (step /= 2));
    if (isnan((I /= 2) += ((fx += f(b - step)) * 2 - pre_fx) * h / 3))
        throw "区间内存在奇点!";
    while (abs(I - pre_I) > e)
    {
        pre_fx = fx, pre_I = I, h = step;
        if (isnan(fx = f(m = a + (step /= 2))))
            throw "区间内存在奇点!";
        while ((m += h) < b)
            if (isnan(fx += f(m)))
                throw "区间内存在奇点!";
        (I /= 2) += (fx * 2 - pre_fx) * h / 3;
    }
    return I;
}
/*
 * 复化Simpson公式
 * f :被积函数
 * a :积分下限
 * b :积分上限
 * e :精度
 */
inline double composite_Simpson(function<double(double)> f, const double &a, const double &b, const double &e = 1e-6)
{
    double fa(f(a));
    if (isnan(fa))
        throw "区间内存在奇点!";
    double fb(f(b));
    if (isnan(fb))
        throw "区间内存在奇点!";
    return composite_Simpson(f, a, b, fa, fb, e);
}

测试案例

用复化求积法计算 I = ∫ 0 1 sin ⁡ x x d x \displaystyle I=\int_0^1\frac{\sin x}x{\rm d}x I=01xsinxdx的近似值.

#include <stdio.h>
// 算法实现代码略
int main()
{
    auto f = [](double x) -> double
    { return sin(x) / x; };
    try
    {
        double I1(composite_trapezoid(f, 0., 1., 1., sin(1.), 1e-8)), I2(composite_Simpson(f, 0., 1., 1., sin(1.), 1e-8));
        printf("%f\t%f", I1, I2);
    }
    catch (const char *s)
    {
        printf(s);
    }
    return 0;
}

最终求得 I ≈ 0.946083 I\approx0.946083 I0.946083. 另外, 对程序运行进行计时得复化梯形求积法耗时0.172913 μ \mu μs, 而复化Simpson求积法耗时仅为0.00302124 μ \mu μs, 可见复化Simpson求积法在本例中效率较高.

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zsc_118

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

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

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

打赏作者

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

抵扣说明:

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

余额充值