【数值计算】数值解析--非线性方程的根

线性方程与非线性方程

当我们求关于rootfinding.eq1.gif的方程rootfinding.eq2.gif的解时,如果,rootfinding.eq3.gif是像

rootfinding.eq4.gif

这样的线性形方程(1次方程)的话,其解为,

rootfinding.eq5.gif

这里的rootfinding.eq6.gif。但是,rootfinding.eq3.gif是非线性方程的时候解法要复杂的多。比如,像下面这样的rootfinding.eq7.gif次代数方程(algebraic equation)的情况,

rootfinding.eq8.gif

rootfinding.eq9.gif的2次方程我们很容易求解,3次或4次方程可以通过Cardano公式或者Ferrari公式求解,然而5次以上却无法直接求解。

更不用说,还有像超越方程式(transcendental equation)这样的无穷次的代数方程(rootfinding.eq10.gif)。例如,rootfinding.eq11.gif用无穷幂级数表示的形式便是超越方程。超越方程一般无法直接求解,只能求近似解。

本文,我们对如下的非线性方程的数值求解方法进行逐一介绍。

  • 2分法
  • 牛顿-拉弗森方法
  • 多次元牛顿-拉弗森方法
  • 霍纳方法
  • DKA法

2分法 

假设rf_bisection.eq1.gif是在区间rf_bisection.eq2.gif上的函数。rf_bisection.eq3.gifrf_bisection.eq4.gif符号相反的时候(rf_bisection.eq5.gif),区间rf_bisection.eq2.gif内至少存在一个解。我们先求区间rf_bisection.eq2.gif的中点的函数值rf_bisection.eq6.gif,如果这个值的符号与rf_bisection.eq4.gif同号, 则在rf_bisection.eq7.gif内存在解。 这样的话,解的存在区间便变成了原来的rf_bisection.eq8.gif。同样,再使用rf_bisection.eq7.gif的中点的函数值,解的存在区间就变成了rf_bisection.eq9.gif。把上述步骤反复处理求得方程式的解的方法就叫做2分法(bisection method)。

【数值计算】数值解析--非线性方程的根_第1张图片
图1 2分法

图1为2分法求近似解的说明图。图中解的存在区间为rf_bisection.eq10.gif。用rf_bisection.eq2.gifrf_bisection.eq10.gif进行初始赋值。 这时的中点为rf_bisection.eq11.gif。 由于图1rf_bisection.eq12.gif,解在rf_bisection.eq13.gif内存在, 令rf_bisection.eq14.gif,这时的中点为rf_bisection.eq15.gif, 由图1rf_bisection.eq16.gif,解在rf_bisection.eq17.gif内存在。上述步骤反复迭代,求rf_bisection.eq18.gif

2分法的处理顺序如下:

  1. rf_bisection.eq19.gifrf_bisection.eq20.gif
  2. 求中点rf_bisection.eq21.gif的函数值rf_bisection.eq22.gif
  3. rf_bisection.eq23.gif的时候rf_bisection.eq24.gifrf_bisection.eq25.gif时,令rf_bisection.eq26.gif
  4. 返回步骤2。

迭代终止的收敛条件如下:

rf_bisection.eq27.gif

下面为2分法的代码示例。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 
/*!
 * 2分法(bisection method)
 * @param[in] func 
 * @param[in] xl,xr 检索范围
 * @param[out] x 解
 * @param[inout] max_iter 最大迭代次数(循环终了后,返回实际迭代次数)
 * @param[inout] eps 允许误差(循环终了后,返回实际的误差值) 
 * @return 1:成功,0:失败
 */
int bisection(double func(const double), double xl, double xr, double &x, int &max_iter, double &eps)
{
    double f = func(xl);
    double fmid = func(xr);
 
    if(f*fmid >= 0.0) return 0.0;
 
    int k;
    double dx = fabs(xr-xl), xmid;
    for(k = 0; k < max_iter; ++k){
        xmid = 0.5*(xl+xr);    // 中点
        dx *= 0.5;
 
        // 中点的函数值
        fmid = func(xmid);
 
        // 收敛判定
        if(dx < eps || fmid == 0.0){
            x = xmid;
            max_iter = k; eps = dx;
            return 1;
        }
 
        // 新的区间
        if(f*fmid < 0){
            xr = xmid;
        }
        else{
            xl = xmid;
            f = fmid;
        }
    }
 
    max_iter = k; eps = dx;
    return 0;
}

牛顿-拉弗森方法

用2分法虽然可以求出指定区间内的解,但是收敛太慢。如果rf_newton.eq1.gif是区间内连续可微的函数的话,用函数的梯度值可以更快收敛。

解的初期值设为rf_newton.eq2.gif。在 rf_newton.eq3.gif的函数rf_newton.eq1.gif的切线为,

rf_newton.eq4.gif

这条切线与rf_newton.eq5.gif相交(rf_newton.eq6.gif)的点为,

rf_newton.eq7.gif

rf_newton.eq8.gif作为下一次的近似值,用同样方法逐渐向解趋近。上述处理顺序如图2所示。这种方法便称作牛顿-拉弗森法(Newton-Raphson method),或者牛顿法(Newton method)。牛顿法的公式如下:

rf_newton.eq9.gif

迭代次数足够多的话会无限趋近rf_newton.eq10.gif的解。 迭代终止的收敛条件为,

rf_newton.eq11.gif

牛顿法只指定一个初始值,并且收敛速度比2分法快很多。然而,这种方法需要求rf_newton.eq1.gif及其微分值rf_newton.eq12.gif,而且根据设定的初始值,也存在解无法收敛的情况。

【数值计算】数值解析--非线性方程的根_第2张图片
图2 牛顿法

牛顿法代码示例如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
/*!
 * 牛顿法(Newton-Raphson method)
 * @param[in] func 
 * @param[inout] x 设定初始值,之后反复迭代
 * @param[inout] max_iter 最大迭代数(迭代终了,返回实际迭代数)
 * @param[inout] eps 允许误差(迭代终了,返回实际误差) 
 * @return 1:成功,0:失败
 */
int newton(double func(const double), double dfunc(const double), 
           double &x, int &max_iter, double &eps)
{
    double f, df, dx;
    int k;
    for(k = 0; k < max_iter; ++k){
        f = func(x);
        df = dfunc(x);
        x = xn-f/df;
 
        // 收敛判定
        dx = fabs(f/df);
        if(dx < eps || fabs(f) < eps){
            max_iter = k; eps = dx;
            return 1;
        }
    }
 
    max_iter = k; eps = dx;
    return 0;
}


多次元牛顿-拉弗森方法

把牛顿法表示成联立非线性方程的一般化形式:

【数值计算】数值解析--非线性方程的根_第3张图片

向量表示为,

rf_newton_md.eq2.gif

这里的,

rf_newton_md.eq3.gif

rf_newton_md.eq4.gif步的近似值表示成rf_newton_md.eq5.gif,把上式用 rf_newton_md.eq5.gif的附近点进行泰勒展开:

rf_newton_md.eq6.gif

这里的rf_newton_md.eq7.gifrf_newton_md.eq8.gif的雅可比行列。 忽略掉2次以上的项,联立非线性方程变为:

rf_newton_md.eq10.gif

rf_newton_md.eq11.gif,可得

rf_newton_md.eq12.gif

这个式子是以rf_newton_md.eq13.gif为未知数的线性联立方程,通过LU分解等解法可以得到rf_newton_md.eq13.gif。 然后再按照下式计算rf_newton_md.eq14.gif

rf_newton_md.eq15.gif

例)2元联立非线性方程

rf_newton_md.eq16.gif

然后,

【数值计算】数值解析--非线性方程的根_第4张图片

由此,与rf_newton_md.eq18.gif相关的公式如下:

【数值计算】数值解析--非线性方程的根_第5张图片

求解rf_newton_md.eq13.gif,得:

【数值计算】数值解析--非线性方程的根_第6张图片

然后再更新rf_newton_md.eq21.gif

rf_newton_md.eq22.gif

霍纳法

首先我们来计算一下用2分法或牛顿法解下述代数方程的运算次数,

rf_horner.eq2.gif

计算rf_horner.eq3.gif的值时,乘法运算rf_horner.eq4.gif次,加法运算rf_horner.eq1.gif次,乘法运算次数呈rf_horner.eq1.gif的2次增加,比如,rf_horner.eq5.gif时55次,rf_horner.eq6.gif时5050次。

为了减少乘法运算次数,对原式做如下变形:

rf_horner.eq7.gif

此时,从最里面的括号内开始计算的话,可知

【数值计算】数值解析--非线性方程的根_第7张图片

最后rf_horner.eq9.gif。 此时的乘法运算次数是rf_horner.eq1.gif次,加法运算也是rf_horner.eq1.gif次。当rf_horner.eq1.gif很大时,计算次数可以大幅减小。例如当乘法运算次数rf_horner.eq5.gif时为10次,rf_horner.eq6.gif时为100次。这种计算方法便被称作霍纳(Horner)。

用霍纳法求代数方程的值及导函数的代码示例如下。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 
/*!
 * 霍纳法求代数方程的值
 * @param[in] x 变量
 * @param[in] b 系数
 * @param[in] n 方程的次数
 * @return 代数方程式的值
 */
template<class T>
inline T func_h(double x, const vector &b, int n)
{
    T f = b[0];
    for(int i = 1; i <= n; ++i){
        f = b[i]+f*x;
    }
    return f;
}
/*!
 * 导函数计算
 * @param[in] x 变量
 * @param[in] b 系数
 * @param[in] n 方程的次数
 * @return 代数方程式的导函数值
 */
template<class T>
inline T dfunc_h(double x, const vector &b, int n)
{
    T df = n*b[0];
    for(int i = 1; i <= n-1; ++i){
        df = (n-i)*b[i]+df*x;
    }
    return df;
}

示例函数使用了复数计算,可以对应各种类型的方程。

DKA法 

代数方程包括重根在内有rf_dka.eq1.gif复数解(实数也是复数的一部分)。以上的方法,通过赋予合适的初始值和范围求得了一个解。当求代数方程的复数解时不得不重新设定初始值和检索范围。以下对求rf_dka.eq1.gif个复数解的方法进行讲解。

首先,有复数解的代数方程用未知数rf_dka.eq2.gif的表示如下:

rf_dka.eq3.gif

用牛顿法分析代数方程。上述方程的解表示成rf_dka.eq4.gif,方程发生如下变形:

rf_dka.eq5.gif

将其微分,

rf_dka.eq6.gif

rf_dka.eq7.gif等于rf_dka.eq8.gif时,由于rf_dka.eq9.gif,上式只剩下第一项。同理rf_dka.eq10.gif时左边仅剩第rf_dka.eq11.gif项。用公式表示,即:

rf_dka.eq12.gif

这里的rf_dka.eq13.gif。由牛顿法公式,rf_dka.eq14.gifrf_dka.eq1.gif的各近似值逼近,

【数值计算】数值解析--非线性方程的根_第8张图片

这里的rf_dka.eq13.gif。这个式子就是Weierstrass逼近法,或者,称作Durand-Kerner法[1]。以下把上式简称DK公式。

虽然我们可以通过DK公式计算rf_dka.eq1.gif个复数解,由于使用了牛顿法,有必要选择一个合适的rf_dka.eq14.gif的初始值rf_dka.eq16.gif。这里,我们使用下面的(Aberth)的初始值。

rf_dka.eq17.gif

这里的rf_dka.eq18.gifrf_dka.eq19.gif是复数平面上包含这rf_dka.eq1.gif个解的圆的半径。这里的rf_dka.eq20.gifrf_dka.eq21.gif

我们把使用Aberth的初始值,通过DK公式的迭代计算,获得rf_dka.eq1.gif个根的方法叫做DKA(Durand-Kerner-Aberth)法。

DKA法的处理顺序

  1. 计算中心rf_dka.eq22.gif,半径rf_dka.eq19.gif及初始值rf_dka.eq16.gif
  2. 用DK公式更新rf_dka.eq14.gif,在收敛前反复执行2,直到获得近似解。

关于Aberth初始值半径的求法有很多种,这里介绍的是最初Aberth提议的方法[2]。 令rf_dka.eq23.gif得到以下方程。

rf_dka.eq24.gif

这里的rf_dka.eq25.gif是通过霍纳法获得的系数。 rf_dka.eq26.gif 且所有系数rf_dka.eq25.gif非零时,rf_dka.eq27.gif的解在,求

rf_dka.eq28.gif

的方程时以解rf_dka.eq29.gif为半径的圆内存在。由此,rf_dka.eq30.gifrf_dka.eq31.gif内有解。使用计算rf_dka.eq19.gif的初始值的半径。 由于rf_dka.eq32.gif有单一的实数解,所以使用牛顿法求解。

使用DKA法计算多个根的代码如下。首先是计算Aberth初始值的函数。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
/*!
 * 使用Aberth法计算初始值
 * @param[out] z 初始值
 * @param[in] c 多项式系数
 * @param[in] n 方程的次数
 * @param[in] max_iter 半径计算用最大迭代次数
 * @param[in] eps 半径计算用的允许误差
 * @return 1:成功,0:失败
 */
int aberth(vector &z, const vector &c, int n, int max_iter, double eps)
{
    // 系数
    vector a;
    complexf zc = -c[1]/(c[0]*(double)n);
    horner(c, a, n, zc);
 
    vector<double> b(a.size());
    b[0] = abs(a[0]);
    for(int i = 1; i <= n; ++i){
        b[i] = -abs(a[i]);
    }
 
    // 通过牛顿法计算Aberth的初始值的半径
    double r = 100.0;
    newton(b, n, r, max_iter, eps);
 
    // Aberth的初始值
    for(int j = 0; j < n; ++j){
        double theta = (2*RX_PI/(double)n)*j+RX_PI/(2.0*n);
        z[j] = zc+r*complexf(cos(theta), sin(theta));
    }
 
    return 1;
}

这里由于涉及到了复数计算,所以使用C++中的complex库。

#include 	// 复数
typedef complex complexf;

计算半径时,rf_dka.eq33.gif的系数计算用到的霍纳函数如下。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
/*!
 * 求霍纳法的系数值
 * @param[in] a 代数方程的系数
 * @param[out] b x=x0时系数
 * @param[in] x0 
 */
template<class T>
inline void horner(const vector &a, vector &b, int n, T x0)
{
    if(n <= 2) return;
 
    b = a;
    for(int i = 0; i <= n; ++i){
        for(int j = 1; j <= n-i; ++j){
            b[j] += x0*b[j-1];
        }
    }
}

得到初始值后,利用以下的Weierstrass函数求解。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 
/*!
 * Weierstrass法(DK公式)
 * @param[inout] z 获得初始值,返回解
 * @param[in] c 多项式的系数
 * @param[in] n 方程的次数
 * @param[inout] max_iter 
 * @param[inout] eps 
 * @return 1:成功,0:失败
 */
int weierstrass(vector &z, const vector &c, int n, int &max_iter, double &eps)
{
    double e = 0.0, ej;
 
    vector zp;
    complexf f, df;
    int k;
    for(k = 0; k < max_iter; ++k){
        zp = z;
 
        // DK式的计算
        for(int j = 0; j < n; ++j){
            f = func(z[j], c, n);
            df = c[0];
            for(int i = 0; i < n; ++i){
                if(i != j){
                    df *= zp[j]-zp[i];
                }
            }
 
            z[j] = zp[j]-f/df;
        }
 
        // 误差计算
        e = 0.0;
        for(int j = 0; j < n; ++j){
            if((ej = abs(func(z[j], c, n))) > e){
                e = ej;
            }
        }
 
        // 收敛判定
        if(e < eps){
            max_iter = k; eps = e;
            return 1;
        }
    }
 
    eps = e;
    return 0;
}


关于Aberth的初始值

下面推导一下代数方程的解与系数之间的关系。首先,以2次方程为例。

rf_dka.eq34.gif

使用解rf_dka.eq35.gif把方程改写成如下形式。

rf_dka.eq36.gif

与之前的系数做对比,得到下面的关系式。

rf_dka.eq37.gif

同理,n次方程表示如下,

【数值计算】数值解析--非线性方程的根_第9张图片

于是,n次方程的解与系数的关系为,

rf_dka.eq39.gif

这里的rf_dka.eq40.gif。如,当方程为3次方程时(rf_dka.eq41.gif),

【数值计算】数值解析--非线性方程的根_第10张图片

rf_dka.eq43.gif时的关系式,

rf_dka.eq44.gif

Aberth的初始值中,把解rf_dka.eq45.gif所在复数平面的重心:

rf_dka.eq46.gif

当作中心的半径为rf_dka.eq19.gif的圆周上等间距的配置rf_dka.eq16.gif。也就是说,

rf_dka.eq47.gif

由欧拉公式(rf_dka.eq48.gif),可得:

rf_dka.eq49.gif

这里的rf_dka.eq50.gifrf_dka.eq21.gif。另外,角度值加上rf_dka.eq51.gif是为了防止,初始值rf_dka.eq16.gif在实轴上对称分布(防止成为共轭复数)。



[1] http://en.wikipedia.org/wiki/Durand%E2%80%93Kerner_method
[2] O. Aberth, ``Iteration methods for finding all zeros of a polynomial simultaneously'', Math. Comput. 27, pp.339-344, 1973. 

你可能感兴趣的:(数值计算)