夜深人静写算法(十八)- 辛普森积分

文章目录

    • 一、引例
      • 1、简单函数积分
      • 2、复杂函数积分
    • 二、梯形法则
      • 1、梯形法则原理
      • 2、梯形法则实现
    • 三、辛普森积分
      • 1、线段拟合
      • 2、抛物线拟合
      • 3、抛物线的定积分
      • 4、辛普森积分实现
    • 四、自适应辛普森积分法
    • 五、自适应辛普森积分题讲解
      • 1、HDU 1724 Ellipse
      • 2、LUOGU P4525【模板】自适应辛普森法1
      • 3、LUOGU P4526【模板】自适应辛普森法2

一、引例

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)=20sin(5x)+3x+20
  • 它的函数图像如下图所示:
    夜深人静写算法(十八)- 辛普森积分_第1张图片
  • 现在需要求下图中红色部分的面积,也就是求原函数在区间 [ 30 , 80 ] [30, 80] [30,80] 的定积分;
    夜深人静写算法(十八)- 辛普森积分_第2张图片
  • 表示成数学式如下:
    ∫ 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(20sin(5x)+3x+20)dx3080(100cos(5x)+6x2+20x+C)dx(100cos(580)+6802+2080+C)(100cos(530)+6302+2030+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、梯形法则原理

  • 梯形法则就是将每一小段的曲线段近似成线段,只要足够小,就能和曲线段完全吻合;
    夜深人静写算法(十八)- 辛普森积分_第3张图片
  • 如上图,蓝色部分就是一个竖着的梯形,两条平行于 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 1e3,而且当区间变大,这个误差也会越来越大;
  • 所以这种算法的时间复杂度是不接预估的,而且不具备普适性;

三、辛普森积分

1、线段拟合

  • 在利用梯形法则的时候,关注梯形的那条斜边就会发现,用线段去近似曲线段的时候,总是有那么一小块面积是存在误差的;
  • 如图所示,蓝色曲线为原曲线,红色线段为近似线段,灰色部分代表误差面积;
    夜深人静写算法(十八)- 辛普森积分_第4张图片

2、抛物线拟合

  • 那么我们如何减少这种误差呢?来看下面这个图:
    夜深人静写算法(十八)- 辛普森积分_第5张图片
  • 我们把原来用直线去拟合的方式改成抛物线(二次曲线),即图中黄色的曲线段,只要这条抛物线选择的恰到好处,误差就可以降到很小;那么原本我们可以利用梯形公式来求面积,现在斜边变成了一个抛物线段,如何求它的面积呢?
    夜深人静写算法(十八)- 辛普森积分_第6张图片
  • 我们把刚才的拟合曲线放到实际的场景中,如图所示,蓝色曲线为原曲线,黄色线段为拟合后的抛物线,红色的就是近似面积,用来代替原曲线围成的面积,那么我们发现,这里的红色部分其实就是黄色抛物线在区间 [ 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(t3s3)+2b(t2s2)+c(ts)3a(ts)(t2+ts+s2)+2b(ts)(t+s)+c(ts)(ts)(3a(t2+ts+s2)+2b(t+s)+c)6(ts)(2a(t2+ts+s2)+3b(t+s)+6c)6(ts)(2at2+3bt+2as2+3bs+2ats+6c)6(ts)((at2+bt+c)+(as2+bs+c)+(at2+2bt+as2+2bs+2ats+4c))6(ts)((at2+bt+c)+(as2+bs+c)+4(a(2(s+t))2+b(2(s+t))+c)6(ts)(F(t)+F(s)+4F(2(s+t)))

  • 1)根据定积分的定义,代入 s 和 t,并且将两端点的值相减;

  • 2)合并同类项,按照次幂归类,3次幂归一类,2次幂归一类,1次幂归一类;

  • 3)4)提取公因式 ( t − s ) (t-s) (ts);

  • 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(ts)(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)=20sin(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 1e9 (不要选择 1 e − 5 1e-5 1e5);

  • 最后来看下实际运行结果:

- -
梯形法则(分段100) 2120.495642592
梯形法则(分段10000) 2108.571685833
梯形法则(分段1000000) 2108.450863877
辛普森积分公式 1155.453393156
自适应辛普森积分 2108.449643365
实际积分 2108.449643364
  • 附上辛普森积分的源码,希望对大家理解有帮助:自适应辛普森积分类封装

五、自适应辛普森积分题讲解

  • 最后,介绍几个自适应辛普森积分的例子帮助理解。

1、HDU 1724 Ellipse

  • 链接:HDU 1724 Ellipse

  • 题意:求一个椭圆被两条平行于y轴的直线围起来的面积;
    夜深人静写算法(十八)- 辛普森积分_第7张图片

  • 难度:★☆☆☆☆

  • 题解:自适应辛普森模板题。这块面积是关于 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 0xxaxdx
  • 难度:★★☆☆☆
  • 题解:当 a < 0 a<0 a<0 时,x 无限趋近于 0 时,原函数的值无穷大,所以这时候函数不收敛,所以直接输出 orz;
  • a > = 0 a>=0 a>=0 时,函数图像基本如下图所示:夜深人静写算法(十八)- 辛普森积分_第8张图片
    直接套用 自适应辛普森,由于 x = 0 x = 0 x=0 的时候 x x x 在分母上,所以这里可以用一个比较小的精度 1 e − 8 1e-8 1e8 作为下界;又由于收敛比较快,而且 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 1e820xxaxdx

你可能感兴趣的:(夜深人静写算法,算法,ACM,自适应辛普森,二次曲线,定积分)