文章目录
一、引例
1、简单函数积分
- 今天来讲下求函数定积分的程序近似方法,如果对高等数学已经忘得差不多的同学,可以跳过推导过程;
- 首先来看一个函数:
f ( x ) = 20 ∗ s i n ( x 5 ) + x 3 + 20 f(x) = 20*sin(\frac{x}{5})+\frac{x}{3}+20 f(x)=20∗sin(5x)+3x+20 - 它的函数图像如下图所示:
- 现在需要求下图中红色部分的面积,也就是求原函数在区间 [ 30 , 80 ] [30, 80] [30,80] 的定积分;
- 表示成数学式如下:
∫ 30 80 f ( x ) d x = ∫ 30 80 ( 20 ∗ s i n ( x 5 ) + x 3 + 20 ) d x = ∫ 30 80 ( − 100 ∗ c o s ( x 5 ) + x 2 6 + 20 x + C ) d x = ( − 100 ∗ c o s ( 80 5 ) + 8 0 2 6 + 20 ∗ 80 + C ) − ( − 100 ∗ c o s ( 30 5 ) + 3 0 2 6 + 20 ∗ 30 + C ) ≈ 2108.449643364 \begin{aligned} \int_{30}^{80}f(x)dx =& \int_{30}^{80} (20*sin(\frac{x}{5})+\frac{x}{3}+20) dx \\ =& \int_{30}^{80} (-100*cos(\frac{x}{5})+\frac{x^2}{6}+20x + C) dx \\ =& (-100*cos(\frac{80}{5})+\frac{80^2}{6}+20*80 + C) - (-100*cos(\frac{30}{5})+\frac{30^2}{6}+20*30 + C) \\ \approx& 2108.449643364 \end{aligned} ∫3080f(x)dx===≈∫3080(20∗sin(5x)+3x+20)dx∫3080(−100∗cos(5x)+6x2+20x+C)dx(−100∗cos(580)+6802+20∗80+C)−(−100∗cos(530)+6302+20∗30+C)2108.449643364
2、复杂函数积分
- 上面的例子能够求出原函数的积分,所以问题较为简单,但是当原函数的积分不是那么好求的时候,想要求这个面积,就没有那么简单了,比如这个函数: g ( x ) = e s i n ( e x c o s x ) g(x) = e^{sin(\frac{e^x}{cosx})} g(x)=esin(cosxex)
- 接下来介绍几种利用程序来近似求解的方法;
二、梯形法则
1、梯形法则原理
- 梯形法则就是将每一小段的曲线段近似成线段,只要足够小,就能和曲线段完全吻合;
- 如上图,蓝色部分就是一个竖着的梯形,两条平行于 y 轴 y轴 y轴 的线段就是梯形的上底和下底,和曲线看上去重合的线段就是那个小区间上的近似线段;
- 梯形法则的核心就是将区间切分成足够小的精度,每个切分的块被看成是一个一个梯形,梯形的面积等于 S = ( 上 底 + 下 底 ) ∗ 高 2 S = \frac {(上底+下底)*高 }{2} S=2(上底+下底)∗高,最后将这些梯形的面积累加即可;
2、梯形法则实现
- 来看下 C++ 代码实现:
#define ValType double
ValType _ladder(ValType u, ValType d, ValType h) {
return (u + d) * h; // 1)
}
// 梯形法则
ValType ladder(ValType s, ValType t, ValType cnt) {
ValType sum = 0;
ValType step = (t - s) / cnt; // 2)
ValType prex = s, prey = f(s);
ValType nowx, nowy;
for (ValType x = s; x < t; x += step) {
nowx = x;
nowy = f(nowx);
sum += _ladder(prey, nowy, step); // 3)
prex = nowx;
prey = nowy; // 4)
}
nowx = t;
nowy = f(nowx);
sum += _ladder(prey, nowy, nowx - prex); // 5)
return sum / 2;
}
- 1)计算面积涉及到除法,而且都是除 2,可以放在最后做,提升一丝丝效率;
- 2)根据 cnt 计算次数算出步长;
- 3)累加每一个小梯形的面积;
- 4)上一个梯形的底边就是这个梯形的另一个底边,减少一次 f(x) 的计算;
- 5)最后一块梯形可能不是整除的,所以需要额外加上最后一块梯形的面积;
- | - |
---|---|
梯形法则(分段100) | 2120.495642592 |
梯形法则(分段10000) | 2108.571685833 |
梯形法则(分段1000000) | 2108.450863877 |
实际积分 | 2108.449643364 |
- 实际跑下来,根据分段不同,误差也不同,分段越多误差越小,但是我们发现,即使分成了 1000000 段,最后的误差也有 1 e − 3 1e-3 1e−3,而且当区间变大,这个误差也会越来越大;
- 所以这种算法的时间复杂度是不接预估的,而且不具备普适性;
三、辛普森积分
1、线段拟合
- 在利用梯形法则的时候,关注梯形的那条斜边就会发现,用线段去近似曲线段的时候,总是有那么一小块面积是存在误差的;
- 如图所示,蓝色曲线为原曲线,红色线段为近似线段,灰色部分代表误差面积;
2、抛物线拟合
- 那么我们如何减少这种误差呢?来看下面这个图:
- 我们把原来用直线去拟合的方式改成抛物线(二次曲线),即图中黄色的曲线段,只要这条抛物线选择的恰到好处,误差就可以降到很小;那么原本我们可以利用梯形公式来求面积,现在斜边变成了一个抛物线段,如何求它的面积呢?
- 我们把刚才的拟合曲线放到实际的场景中,如图所示,蓝色曲线为原曲线,黄色线段为拟合后的抛物线,红色的就是近似面积,用来代替原曲线围成的面积,那么我们发现,这里的红色部分其实就是黄色抛物线在区间 [ s , t ] [s,t] [s,t] 上的定积分;
3、抛物线的定积分
-
这里引入一个二次函数(即抛物线) F(x) ,这个二次函数的表示为:
F ( x ) = a x 2 + b x + c F(x) = ax^2+bx+c F(x)=ax2+bx+c -
求出 F(x) 这个函数的积分,有:
∫ F ( x ) d x = a 3 x 3 + b 2 x 2 + c x + d \int F(x) dx = \frac {a}{3} x^3 + \frac {b}{2} x^2 + cx + d ∫F(x)dx=3ax3+2bx2+cx+d
则有定积分:
1 ) ∫ s t F ( x ) d x = ( a 3 t 3 + b 2 t 2 + c t + d ) − ( a 3 s 3 + b 2 s 2 + c s + d ) 2 ) ∫ s t F ( x ) d x = a 3 ( t 3 − s 3 ) + b 2 ( t 2 − s 2 ) + c ( t − s ) 3 ) ∫ s t F ( x ) d x = a 3 ( t − s ) ( t 2 + t s + s 2 ) + b 2 ( t − s ) ( t + s ) + c ( t − s ) 4 ) ∫ s t F ( x ) d x = ( t − s ) ( a 3 ( t 2 + t s + s 2 ) + b 2 ( t + s ) + c ) 5 ) ∫ s t F ( x ) d x = ( t − s ) 6 ( 2 a ( t 2 + t s + s 2 ) + 3 b ( t + s ) + 6 c ) 6 ) ∫ s t F ( x ) d x = ( t − s ) 6 ( 2 a t 2 + 3 b t + 2 a s 2 + 3 b s + 2 a t s + 6 c ) 7 ) ∫ s t F ( x ) d x = ( t − s ) 6 ( ( a t 2 + b t + c ) + ( a s 2 + b s + c ) + ( a t 2 + 2 b t + a s 2 + 2 b s + 2 a t s + 4 c ) ) 8 ) ∫ s t F ( x ) d x = ( t − s ) 6 ( ( a t 2 + b t + c ) + ( a s 2 + b s + c ) + 4 ( a ( ( s + t ) 2 ) 2 + b ( ( s + t ) 2 ) + c ) 9 ) ∫ s t F ( x ) d x = ( t − s ) 6 ( F ( t ) + F ( s ) + 4 F ( ( s + t ) 2 ) ) \begin{aligned} 1)\int_s^t F(x) dx =& (\frac {a}{3} t^3 + \frac {b}{2} t^2 + ct + d) - (\frac {a}{3} s^3 + \frac {b}{2} s^2 + cs + d) \\ 2)\int_s^t F(x) dx =& \frac {a}{3} (t^3 - s^3) + \frac {b}{2} (t^2-s^2) + c(t-s) \\ 3)\int_s^t F(x) dx =& \frac {a}{3} (t-s)(t^2 +ts +s^2) + \frac {b}{2} (t-s)(t+s) + c(t-s) \\ 4)\int_s^t F(x) dx =& (t-s)(\frac {a}{3} (t^2 +ts +s^2) + \frac {b}{2} (t+s) + c) \\ 5)\int_s^t F(x) dx =& \frac{(t-s)}{6}({2a} (t^2 +ts +s^2) + {3b} (t+s) + 6c) \\ 6)\int_s^t F(x) dx =& \frac{(t-s)}{6}(2at^2+3bt+ 2as^2 + 3bs + 2ats + 6c) \\ 7)\int_s^t F(x) dx =& \frac{(t-s)}{6}( (at^2+bt+ c) + (as^2+bs+c) + (at^2 + 2bt + as^2 + 2bs + 2ats + 4c) )\\ 8)\int_s^t F(x) dx =& \frac{(t-s)}{6}( (at^2+bt+ c) + (as^2+bs+c) + 4( a(\frac{(s+t)}{2})^2 + b(\frac{(s+t)}{2})+c )\\ 9)\int_s^t F(x) dx =& \frac{(t-s)}{6}( F(t) + F(s) + 4F(\frac{(s+t)}{2}))\\ \end{aligned} 1)∫stF(x)dx=2)∫stF(x)dx=3)∫stF(x)dx=4)∫stF(x)dx=5)∫stF(x)dx=6)∫stF(x)dx=7)∫stF(x)dx=8)∫stF(x)dx=9)∫stF(x)dx=(3at3+2bt2+ct+d)−(3as3+2bs2+cs+d)3a(t3−s3)+2b(t2−s2)+c(t−s)3a(t−s)(t2+ts+s2)+2b(t−s)(t+s)+c(t−s)(t−s)(3a(t2+ts+s2)+2b(t+s)+c)6(t−s)(2a(t2+ts+s2)+3b(t+s)+6c)6(t−s)(2at2+3bt+2as2+3bs+2ats+6c)6(t−s)((at2+bt+c)+(as2+bs+c)+(at2+2bt+as2+2bs+2ats+4c))6(t−s)((at2+bt+c)+(as2+bs+c)+4(a(2(s+t))2+b(2(s+t))+c)6(t−s)(F(t)+F(s)+4F(2(s+t))) -
1)根据定积分的定义,代入 s 和 t,并且将两端点的值相减;
-
2)合并同类项,按照次幂归类,3次幂归一类,2次幂归一类,1次幂归一类;
-
3)4)提取公因式 ( t − s ) (t-s) (t−s);
-
5)6)将 a 3 \frac a3 3a 和 b 2 \frac b2 2b 化成整数方便计算;
-
7)8)将 t t t 和 s s s 相关的量提取出来,凑成 F ( t ) F(t) F(t) 和 F ( s ) F(s) F(s) 的形式;
-
9)将积分全部转换成原函数的表示;
-
于是,我们可以得出结论:
二次函数的定积分可以用原函数来表示,表示如下(其中 s 和 t 为定积分的两个端点,(s+t)/2 为这两个端点在x坐标的中点):
∫ s t F ( x ) d x = ( t − s ) 6 ( F ( t ) + F ( s ) + 4 F ( ( s + t ) 2 ) ) \int_s^t F(x) dx = \frac{(t-s)}{6}( F(t) + F(s) + 4F(\frac{(s+t)}{2})) ∫stF(x)dx=6(t−s)(F(t)+F(s)+4F(2(s+t)))
- 这个公式就是辛普森积分公式;
4、辛普森积分实现
- 实现就相对比较简单了,直接套用公式即可;
ValType simpson(ValType s, ValType t) {
ValType m = (s + t) / 2;
return (t - s) / 6 * (f(s) + 4 * f(m) + f(t));
}
- 这里的函数 f 我们可以任意定义一个二次函数,来看下效果:
ValType f(ValType x) {
return 27 * x*x - 36 * x + 65;
}
- 求 这个函数在区间 [ 30 , 80 ] [30,80] [30,80] 的定积分如下:
- | - |
---|---|
梯形法则(分段100) | 4280948.750000000 |
梯形法则(分段10000) | 4269366.430625989 |
梯形法则(分段1000000) | 4269251.164108668 |
辛普森积分公式 | 4269250.000000000 |
实际积分 | 4269250.000000000 |
四、自适应辛普森积分法
- 然而,实际情况并非如此,并不是所有的曲线都能和二次曲线完美契合的,就像这个函数
f ( x ) = 20 ∗ s i n ( x 5 ) + x 3 + 20 f(x) = 20*sin(\frac{x}{5})+\frac{x}{3}+20 f(x)=20∗sin(5x)+3x+20 如果套用辛普森积分后的结果是这个样子的:
- | - |
---|---|
梯形法则(分段100) | 2120.495642592 |
梯形法则(分段10000) | 2108.571685833 |
梯形法则(分段1000000) | 2108.450863877 |
辛普森积分公式 | 1155.453393156 |
实际积分 | 2108.449643364 |
- 原因相信大家都已经知道了,参考梯形法则,我们是可以把它分段的,但是传统的分段,又会陷入和梯形法则一样的窘境:
切分太细,实现复杂度太高;
切分太粗,精度不高;
- 所以,自适应辛普森积分法就诞生了!
- 来看这么一个公式:
∫ s t F ( x ) d x = ∫ s m i d F ( x ) d x + ∫ m i d t F ( x ) d x \int_s^t F(x) dx = \int_s^{mid} F(x) dx + \int_{mid}^t F(x) dx ∫stF(x)dx=∫smidF(x)dx+∫midtF(x)dx - 利用这个公式,我们可以把区间不断的进行二分,然后把每个区间分别和不同的二次函数进行拟合来提高计算精度,来看下具体实现:
ValType self_adapt_simpson(ValType s, ValType t, ValType simpsonval) {
ValType m = (s + t) / 2;
ValType lval = simpson(s, m); // 1)
ValType rval = simpson(m, t); // 2)
ValType reval = lval + rval;
if (fabs(reval - simpsonval) < EPS) {
// 3)
return reval;
}
return self_adapt_simpson(s, m, lval) + self_adapt_simpson(m, t, rval);
}
-
1)对左半部分进行辛普森积分求解;
-
2)对右半部分进行新浦僧积分求解;
-
3)这里是精髓,也是递归函数的出口,当切分后的结果和不切分的结果的精度已经小于一定精度时,我们就没必要再进行递归切分了,而且我们知道切分的结果一定比不切分更加精确,所以这时候直接返回切分后的结果即可;EPS 的取值比较关键,当要求精度为小数点后 5 位时,保险点可以选取 1 e − 9 1e-9 1e−9 (不要选择 1 e − 5 1e-5 1e−5);
-
最后来看下实际运行结果:
- | - |
---|---|
梯形法则(分段100) | 2120.495642592 |
梯形法则(分段10000) | 2108.571685833 |
梯形法则(分段1000000) | 2108.450863877 |
辛普森积分公式 | 1155.453393156 |
自适应辛普森积分 | 2108.449643365 |
实际积分 | 2108.449643364 |
- 附上辛普森积分的源码,希望对大家理解有帮助:自适应辛普森积分类封装
五、自适应辛普森积分题讲解
- 最后,介绍几个自适应辛普森积分的例子帮助理解。
1、HDU 1724 Ellipse
-
题意:求一个椭圆被两条平行于y轴的直线围起来的面积;
-
难度:★☆☆☆☆
-
题解:自适应辛普森模板题。这块面积是关于 x 轴对称的,所以只要计算上半部分的面积,上半部分的面积就是函数 y = b 1 − ( x a ) 2 y = b\sqrt{1 - (\frac {x}{a})^2} y=b1−(ax)2 在 [ L , R ] [L, R] [L,R] 区间上的定积分,利用自适应辛普森积分求值后乘上 2 2 2即可,需要注意精度,1e-6 过不了,1e-7 可过;
2、LUOGU P4525【模板】自适应辛普森法1
- 链接:P4525 【模板】自适应辛普森法1
- 题意:求积分 ∫ L R c x + d a x + b \int_L^R \frac {cx+d}{ax+b} ∫LRax+bcx+d
- 难度:★☆☆☆☆
- 自适应辛普森模板题。直接套公式即可;
3、LUOGU P4526【模板】自适应辛普森法2
- 链接:P4526【模板】自适应辛普森法2
- 题意:求积分 ∫ 0 ∞ x a x − x d x \int_0^\infty {x^{\frac {a}{x} - x}} \,{\rm d}x ∫0∞xxa−xdx
- 难度:★★☆☆☆
- 题解:当 a < 0 a<0 a<0 时,x 无限趋近于 0 时,原函数的值无穷大,所以这时候函数不收敛,所以直接输出 orz;
- 当 a > = 0 a>=0 a>=0 时,函数图像基本如下图所示:
直接套用 自适应辛普森,由于 x = 0 x = 0 x=0 的时候 x x x 在分母上,所以这里可以用一个比较小的精度 1 e − 8 1e-8 1e−8 作为下界;又由于收敛比较快,而且 a < = 50 a <= 50 a<=50,所以上界不是一个很大的值,基本确定 20 就可以了;原积分化简为:
∫ 1 e − 8 20 x a x − x d x \int_{1e-8}^{20} {x^{\frac {a}{x} - x}} \,{\rm d}x ∫1e−820xxa−xdx