非线性方程f(x)=0求根数值方法——《数值计算方法》

《数值计算方法》系列总目录

第一章 误差序列实验
第二章 非线性方程f(x)=0求根的数值方法
第三章 CAD模型旋转和AX=B的数值方法
第四章 插值与多项式逼近的数值计算方法
第五章 曲线拟合的数值方法
第六章 数值微分计算方法
第七章 数值积分计算方法
第八章 数值优化方法



一、算法原理

1、波尔查诺二分法

  求解非线性方程的根的时候,二分法主要适用于在已知方程的根的大致区间的情况下,通过二分法可将区间内的端点逐步逼近零点,直到得到一个任意小的包含零点的间隔。二分法判定过程的第1步是选择中点。分析可能存在的三种情况。
1. List item
2. 如果f(b)和f©符号相反,则在区间[c,b]内存在零点。
3. 如果f©=0,则c是零点。
  如果情况1或情况2,则表示找到了一个比 lim ⁡ n → ∞ c n = r \mathop {\lim }\limits_{n \to \infty } {c_n} = r nlimcn=r原先区间范围小一半的区间,它包含根并称之为对区间进行压缩。为了持续此过程,需要对新的更小区间进行重新标号。重复执行直到区间足够小,在误差允许的范围内,我们便认为找到了方程根的近似值。
  设 f ∈ C [ a , b ] f \in C[a,b] fC[a,b],且存在数 r ∈ [ a , b ] r \in [a,b] r[a,b]满足 f ( r ) = 0 f(r) = 0 f(r)=0,如果f(a)和f(b)的符号相反,且 { c n } n = 0 ∞ \{ {c_n}\} ^\infty _{n = 0} {cn}n=0表示二分法生成的中点序列,则 ∣ r − c n ∣ ≤ b − a 2 n + 1 |r - {c_n}| \le \frac{{b - a}}{{{2^{n + 1}}}} rcn2n+1ba,其中n=0,1…。这样序列 { c n } n = 0 ∞ \{ {c_n}\} ^\infty _{n = 0} {cn}n=0收敛到零点x=r即可表示为 lim ⁡ n → ∞ c n = r \mathop {\lim }\limits_{n \to \infty } {c_n} = r nlimcn=r
  通过迭代求解区间中点的值,重新确定区间,当函数值的绝对值小于给定误差范围的时候,找到了近似解。

2、试值法

  试值法与二分法的原理基本相同,但是收敛速度相对较快。与二分法条件一样,找到给定区间的割线 与X轴的交点,把这一交点作为近似值。迭代过程中,比较函数值的正负,并重新确定区间。通过迭代,最终在给定误差范围内得到方程的近似解。
  通过对斜率不同的计算方式,可以得到每次迭代中的近似值的表达形式,与二分法的判别条件相同。

  1. 如果 f ( a ) f(a) f(a) f ( c ) f(c) f(c)符号相反,则在区间[a,c]内存在零点。
  2. 如果 f ( b ) f(b) f(b) f ( c ) f(c) f(c)符号相反,则在区间[c,b]内存在零点。
  3. 如果 f ( c ) = 0 f(c)=0 f(c)=0,则c是零点。

  最终,通过比较 ∣ f ( c ) ∣ |f(c)| f(c)的与给定误差范围的大小,确定迭代的终止条件,并认为找到了可以接受的方程的近似解。

3、牛顿拉夫森法

  牛顿拉夫森法比二分法和试值法收敛速度更快。基本原理是假设初始值 p 0 {p_0} p0在根 p p p附近。函数 y = f ( x ) y=f(x) y=f(x)的图形与x轴相交于点 ( p , 0 ) (p,0) (p,0)。而且点 ( p 0 , f ( p 0 ) ) ({p_0},f({p_0})) (p0,f(p0))位于靠近点 ( p , 0 ) (p,0) (p,0)的曲线上。将 p 1 {p_1} p1定义为曲线在点 ( p 0 , f ( p 0 ) ) ({p_0},f({p_0})) (p0,f(p0))的切线与x轴的交点,通过图像的显示则可以看到 p 1 {p_1} p1 p 0 {p_0} p0更靠近 p p p。如果写出切线L的两种表达式。可得到 p 1 {p_1} p1 p 0 {p_0} p0相关的方程。
m = 0 − f ( p 0 ) p 1 − p 0 m = f ′ ( p 0 ) {\rm{ }}m = \frac{{0 - f({p_0})}}{{{p_1} - {p_0}}}{\rm{ }}m = f'({p_0}) m=p1p00f(p0)m=f(p0)
p 1 = p 0 − f ( p 0 ) f ′ ( p 0 ) {\rm{ }}{p_1} = {p_0} - \frac{{f({p_0})}}{{f'({p_0})}} p1=p0f(p0)f(p0)
p k = g ( p k − 1 ) = p k − 1 − f ( p k − 1 ) f ′ ( p k − 1 ) {\rm{ }}{p_k} = g({p_{k - 1}}) = {p_{k - 1}} - \frac{{f({p_{k - 1}})}}{{f'({p_{k - 1}})}} pk=g(pk1)=pk1f(pk1)f(pk1)

二、实验内容及核心算法代码

1、波尔查诺二分法

  利用二分法求解函数的零点,通过二分法的实验原理,我们设置起始区间值为 =0, =2。方程的根位于这个区间内。通过不断的取区间的中点 ,并求出中点的函数值f( ),并与区间两端的函数值进行比较,进而来重新确定区间的中点。通过比较函数值的绝对值| f( )|与设置误差的大小,从而确定方程最终收敛的近似值。
其中,程序的核心算法代码为

float GetfunrootbyBinsearch(float (*f)(float), float x0, float x1, int& nIter, const int MaxIter, const float eps, float** valset,bool& ifOuepreci)
{
	float val0, val1, xh, valh;
	nIter = 0;//the raw index
	//if the initial is the root,return it
	val0 = f(x0);
	if (fabs(val0) < eps)
	{
		return x0;
	}
	val1 = f(x1);
	if (fabs(val1) < eps)
	{
		return x1;
	}
	//if set the error initial,program exit
	if (f(x0) * f(x1) > 0)
	{
		cout << "set the error initial!program exit!" << endl;
		exit(0);
	}
	//Porchano binary search method
	do
	{
		xh = (x0 + x1) / 2;
		valh = f(xh);
		valset[nIter][0] = nIter + 1; valset[nIter][1] = x0; valset[nIter][2] = xh; valset[nIter][3] = x1; valset[nIter][4] = valh;
		if (val0 * valh > 0)
		{
			x0 = xh;
			val0 = valh;
		}
		else
		{
			x1 = xh;
			val1 = valh;
		}
		++nIter;//move the index
		if (nIter == MaxIter)
		{
			cout << "cannot find the root within the MaxIter num!" << endl;
			ifOuepreci = true;
			break;
		}
	} while (fabs(valh) > eps);
	return xh;
#endif

  这段代码的作用是实现,不断求取与判断的区间中点的函数值与给定误差的大小,从而作为判断循环的条件。并将每一步得到数据保存在一个二维指针指向的空间内。当所求得的函数值小于给定误差时,作为取得方程的近似解,并返回这一近似解。

2、试值法求解xsin(x)-1=0原理实现

  与二分法求解方程的近似解的条件一致,设置起始区间值为 a 0 a_0 a0=0, b 0 b_0 b0=2,利用试值法的原理的表达式
c n = b n − f ( b n ) ( b n − a n ) f ( b n ) − f ( a n ) {c_n} = {b_n} - \frac{{f({b_n})({b_n} - {a_n})}}{{f({b_n}) - f({a_n})}} cn=bnf(bn)f(an)f(bn)(bnan)
  进行迭代,从而不断生成新的取值区间,从而缩小近似解的取值区间。判断的所取区间的近似解的函数值与给定误差的大小,从而求得可以接受的方程的近似解。
  其核心代码如下

float GetCk(float (*f)(float), float x0, float x1)
{
	float meval1, meval2;
	meval1 = f(x1) * (x1 - x0);
	meval2 = f(x1) - f(x0);
	return x1 - meval1 / meval2;
}
float GetfunrootbyFSP(float (*f)(float), float x0, float x1, int& nIter, const int MaxIter, const float eps, float** valset, bool& ifOuepreci)
{
	float val0, val1, xh, valh;
	nIter = 0;//the raw index
	//if the initial is the root,return it
	val0 = f(x0);
	if (fabs(val0) < eps)
	{
		return x0;
	}
	val1 = f(x1);
if (fabs(val1) < eps)
	{
		return x1;
	}
	//if set the error initial,program exit
	if (f(x0) * f(x1) > 0)
	{
		cout << "set the error initial!program exit!" << endl;
		exit(0);
	}
	//Porchano binary search method
	do
	{
		xh = GetCk(f,x0,x1);
		valh = f(xh);
		valset[nIter][0] = nIter + 1; valset[nIter][1] = x0; valset[nIter][2] = xh; valset[nIter][3] = x1; valset[nIter][4] = valh;
		if (val0 * valh > 0)
		{
			x0 = xh;
			val0 = valh;
		}
		else
		{
			x1 = xh;
			val1 = valh;
		}
		++nIter;//move the index
		if (nIter == MaxIter)
		{
			cout << "cannot find the root within the MaxIter num!" << endl;
			ifOuepreci = true;
			break;
		}
	} while (fabs(valh) > eps);
	return xh;
}

3、牛顿拉弗森法求解sqrt(5)的近似值原理实现

  利用牛顿方法原理中推导出来的表达式,通过求得所给函数在A等于5时候的零点,从而求出根号5的近似值。迭代函数的简要推导如下所示。与之前面的二分法和试值法迭代过程相似。通过简化迭代函数的表达式,从而减少迭代过程中计算机运算的次数,从而减小误差,同时通过传递引用的方式,在一个封装的函数中同时得到多个相关的结果。
f ( x ) = x 2 − A f(x) = {x^2} - A f(x)=x2A
g ( x ) = x − f ( x ) f ′ ( x ) = x − x 2 − A 2 x g(x) = x - \frac{{f(x)}}{{f'(x)}} = x - \frac{{{x^2} - A}}{{2x}} g(x)=xf(x)f(x)=x2xx2A
g ( x ) = x + A / x 2 g(x) = \frac{{x + A/x}}{2} g(x)=2x+A/x
p k = p k − 1 + A p k − 1 2 {\rm{ }}{p_k} = \frac{{{p_{k - 1}} + \frac{A}{{{p_{k - 1}}}}}}{2} pk=2pk1+pk1A
  其核心代码如下

float GetSqrtrootbyNewtonMethod(float (*g)(float, float), const float eps, float* pn, float p0, int& nIter, float A, int nMaxIter, bool& ifFindRoot)
{
	float err;
	pn[0] = p0;
	nIter = 0;
	if (fabs(pn[0] - g(A, pn[0])) < eps)
	{
		return pn[0];
	}
	do
	{
		++nIter;
		pn[nIter] = g(A, pn[nIter - 1]);
		err = (pn[nIter] - pn[nIter - 1]) / pn[nIter];
		if (nIter >= nMaxIter)
		{
			ifFindRoot = false;
			cout << "cannot find the root within the precision!" << endl;
			break;
		}
	} while (fabs(err) > eps);
	return pn[nIter];

4、牛顿拉弗森法求解炮弹飞行轨迹问题原理实现

  通过分析炮弹在飞行过程中的运动状态,并考虑在飞行过程中的所受的阻力,得到炮弹在飞行过程中的运动学方程,且为一个非线性的方程。通过对炮弹进行在水平方向和竖直方向上的运动分析,利用Newton-Raphson方法,从而计算出炮弹从发出到落地所需要的时间,即f(t)=0的近似解,从而得出炮弹飞行过程中的飞行时间,最高高度,水平位移距离等多个参数。通过分析可得如下表达式。
y = υ y t − 16 t 2 , a n d x = υ x {\rm{ }}y = {\upsilon _y}t - 16{t^2},{\rm{ }}and{\rm{ }}x = {\upsilon _x} y=υyt16t2,andx=υx
y = f ( t ) = ( C υ y + 32 C 2 ) ( 1 − e − t / C ) − 32 C t x = r ( t ) = C υ x ( 1 − e − t / C ) \begin{array}{l} y = f(t) = (C{\upsilon _y} + 32{C^2})\left( {1 - {e^{ - t/C}}} \right) - 32Ct\\ x = r(t) = C{\upsilon _x}\left( {1 - {e^{ - t/C}}} \right) \end{array} y=f(t)=(Cυy+32C2)(1et/C)32Ctx=r(t)=Cυx(1et/C)
b 0 = 4 5 0 , υ y = υ x = 160 f t / sec ⁡ , C = 10 {b_0} = {45^0},{\upsilon _y} = {\upsilon _x} = 160ft/\sec ,C = 10 b0=450,υy=υx=160ft/sec,C=10
f ( 8 ) = 83.22 , f ( 9 ) = − 31.5 f(8) = 83.22,f(9) = - 31.5 f(8)=83.22,f(9)=31.5
p 0 = 8 , f ′ ( p 0 ) = − 104.32 {p_0} = 8,f'({p_0}) = - 104.32 p0=8,f(p0)=104.32
g ( x ) = x − f ( x ) f ′ ( x ) ⇒ p 1 = 8 − 83.22 − 104.32 = 8.7977 g(x) = x - \frac{{f(x)}}{{f'(x)}} \Rightarrow {p_1} = 8 - \frac{{83.22}}{{ - 104.32}} = 8.7977 g(x)=xf(x)f(x)p1=8104.3283.22=8.7977
  可知炮弹落地时间位于8s~9s内,于是利用迭代的算法进行求解,其核心算法代码如下

void NewtonRaphsonIterEquation(float (*f)(float, float, float), float (*df)(float, float, float), float (*ddf)(float, float, float), float C, float t0, float Vy, float Vx, float** valset,int& nIter,float& yDis,float& xt, const int nMaxIter,const float eps,bool& ifOutPre)
{
	nIter=0;
	float pk=t0,pk1,err;
	//solve out the fly time
	do
	{
		pk1 = NR_Equation(C,Vy,pk, f, df);
		valset[nIter][0] = nIter; valset[nIter][1] = pk; valset[nIter][2] = pk1-pk;
		valset[nIter][3] = f(C,Vy,pk);
		++nIter;
		err = (pk1 - pk) / pk1;
		pk = pk1;
		if (nIter >= nMaxIter)
		{
			cout << "cannot get the root within the precision" << endl;
			ifOutPre = true;
			break;
		}
	} while (fabs(err) > eps);
	//return the flytime
	xt = pk1;
	//solve out the y(t) derivative =0 time
	int tIter=0;
	pk = t0;
	do
	{
		pk1 = NR_Equation(C, Vy, pk, df, ddf);
		++tIter;
		err = (pk1 - pk);
		pk = pk1;
		if (nIter >= nMaxIter)
		{
			break;
		}
	} while (fabs(err) > eps);
	//the highest height
	yDis = f(C, Vy, pk1);
}

5、实验数据结果

  实验具体数据结果在文末链接中,大家可以自行下载验证。其中,各个算法的计算结果均正确,且算法收敛速度依次为 牛顿-拉弗森方法 > 试值法 > 二分法。因此可以看出,根据牛顿-拉弗森方法的原理,其收敛速度还与方程解附近的函数斜率有关,并不是所有情况下其收敛速度都会较快,具有严重的依赖性。当函数性质不太良好时,可能其收敛速度会低于两外两种方法。因此,在实际运用中,还应考虑函数的特性。


三、结论

  通过分析求解非线性方程近似解的原理,我们发现可以用三种不同的迭代方式,使得方程的根的可能取值区间无限的缩小,最后可以通过比较取值区间的横向长度,或区间中点函数值与0的距离,如果在我们设置的允许误差范围内,并可认为找到了非线性方程的近似解。通过分析求解非线性方程近似解的原理,我们发现可以用三种不同的迭代方式,使得方程的根的可能取值区间无限的缩小,最后可以通过比较取值区间的横向长度,或区间中点函数值与0的距离,如果在我们设置的允许误差范围内,并可认为找到了非线性方程的近似解。
通过实验验证三种不同的方式,我们发现。三种方法求解非线性方程的根的收敛性质良好,且求得根的精确度非常高。


声明:本系列《数值计算方法》所有章节源代码均为作者编写,大家可以自行下载,仅供学习交流使用,欢迎大家评论留言或者私信交流,请勿用于任何商业行为。其中,各章实验数据结果也在资源链接中,大家可以自行实验对照,欢迎大家批评指正!文件中每个算法均由作者进行初步测试,部分算法可直接用于工程开发作为模块参考。

各章算法源代码(C++)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值