C++ 数学与算法系列之牛顿、二分迭代法求解非线性方程

news2024/12/26 13:13:30

1. 前言

前文介绍了如何使用“高斯消元法”求解线性方程组。

本文秉承有始有终的态度,继续介绍“非线性方程”的求解算法。

本文将介绍 2 个非线性方程算法:

  • 牛顿迭代法。
  • 二分迭代法。

牛顿迭代法(Newton's method)又称为牛顿-拉夫逊方法(Newton-Raphson method),是拉夫逊牛顿同时提出来的一种在实数域复数域近似求解方程的方法。

为何说是近似求解方程

因为对于多数方程式,因不存在求根公式,或者说无法或很难找到标准的可以直接套用的模板公式。

因而求精准解非常困难,从而寻找方程的近似根就显得特别重要。

即使如牛顿大神提出的方法,也只是近似求解的算法,甚至需要满足某种收敛条件的方程式才能使用牛顿迭代法求解。

下面将具体介绍这 2 种求解算法。

2. 牛顿迭代算法

下面将通过一系列演示图,直观告诉大家牛顿迭代法的算法思想。算法中,牛顿用到了微积分相关的知识。

所以,在阅读下文时,需要具备微积分的认知。

牛顿迭代算法求解方程式的过程,有点类似福尔摩斯探案。通过蛛丝马迹,先合理的预测,然后根据推理逻辑,让预测离真相近一点、再近一点……一直到找到或接近真相。

实事告诉我们,不是所有的预测都能找到真相。同理,基于预测的牛顿迭代法也不一定总是能找到方程式的解,看完下面的演示流程,你将明白。

假设现有一非线性方程式 f(x),其在平面坐标轴上的曲线图案如下。所谓求解,指求其与横坐标轴相交时的点的 x值。

n1.png

现在,看看牛顿是如何使用微积分思想找到这个解的。只能说,牛逼人的思想非我等凡人能比拟。

2.1 基本思想

  • 在横坐标上找一 x0 点(也称预测点),并绘制 (x0,f(x0)) 点与曲线相交的切线。切线和横坐轴相交于 x1

n9.png

  • 再绘制(x1,f(x1))点与曲线的切线,此时,切线与横轴相交于x2,继续绘制出(x2,f(x2))与曲线的交点……如此迭代,直到切线与横坐标轴的交点与曲线和横坐标的交点重合,此交合点便是曲线的解。是不是很简单,为什么是牛顿发现的,而不是我?

n2.png

  • x0的选择并不完全是任意的,也应该有基本的推理依据。预测点是关键,如果与真实值相差太远,则迭代次数会很大。理论上,只要预测点给的好,且此方程式满足牛顿迭代算法的前提条件,无论迭代多少次,解必能找出来,无非就是时间的长短。

2.2 如何求解 x1

现在的问题转向到如何通过已知的x0值计算出x1的值?是否存在一个标准的公式?

现在就是微积分上场的时候,请屏住呼吸!真相将昭然若揭。

  • x0 和 x1之间选择任一点x,从此点向上绘制垂直线,假设与切线相交的位置的纵坐标值为y 。并绘制如下箭头所指的三角形。

n3.png

  • 三角形为直角三角形,学过三角函数的都知道,会存在如下的关系。

n5.png

n4.png

  • 现在轮到微积分知识上场,它告诉我们,其中的tan0就是切线与曲线的斜率。根据微积分原理,斜率即是x0在曲线上的导数,可以根据导函数计算出来,即tan0=f'(x0)。太完美了,如此公式可演变如下:

n6.png

继续化丽的转身后,它便如涅槃重生一样,破茧成如下人见人爱的模样:

n7.png

  • 因切线与横坐标轴相交的位置y=0,从而便可以求得 x1的值:
𝑥1=𝑥0−𝑓(𝑥0)/𝑓′(𝑥0)

同理,求得 x2的值。

𝑥2=𝑥0−𝑓(𝑥1)/𝑓′(𝑥1)

最后,可以抽象出牛顿迭代公式,即迭代法中的核心子逻辑

xn+1=xn-f(xn)/f'(xn)

只能说,太神奇了!计算机中的算法一词来源于数学,计算机学科本也源自数学学科,因一脉相承,说计算机的尽头是数学,一点不假,计算机只是工具而已。

2.3 收敛性

什么是牛顿迭代算法的收敛性?

通俗理解,选定预测点后,也许中间会有偏离,或许会忽远忽近,但无论如何最终都能靠近真实解,这便是收敛性。

换一句话而言,如果通过预测点,无法收敛到真实值,则无法求出解。

如果预测点为曲线的驻点,很不幸,由此点绘制的切线不会和横坐标轴相交,是无法求方程式的解。

n10.png

另,如果收敛越来越远,也不能使用牛顿迭代法。如下图所示。

n11.png

怎样的方程式能使用牛顿迭代法,牛顿迭代法已经给出了答案,可自行查阅一下。

2.4 编码实现

现在来一个具体的案例:求解如下方程式。

f(x)=x4-3x3+1.5x2-4

牛顿迭代法中的子逻辑需要求解函数导函数。受限于篇幅,导函数的推导在此不负赘。这里仅给出常见的基本函数的导函数公式,再根据导函数生成法则,直接找到求解函数导函数 f‘(x)=4x3-9x2+3x。

10.png

Tips: 如果对微积分或导函数不是很了解,建议查阅一下高等数学书。

牛顿迭代法可以使用递归和非递归方案实现。

因初始值为预测值,从而可能导致递归或迭代的次数会很大。前文说过,牛顿迭代法并不是一个解非线性方程式的通用算法,也就是说使用牛顿迭代法可能得不到解。

故最好在编写算法时添加如下的辅助手段:

  • 保证函数在整个定义域内最好是二阶可导的。
  • 预测点会影响计算量,可限制迭代的次数,当在此限制下不能得到结果时,则增加其它的判断手段试错。

2.4.1 递归实现

#include <iostream>
#include <cmath>
using namespace std;

/*
* 原函数  f(x)=x^4-3x^3+1.5x^2-4
*/
double yfun(double x) {
	return pow(x,4)-3*pow(x,3)+1.5*pow(x,2)-4.0;
}

/*
* 导函数 f'(x)=4*x^3-9*x^2+3x
*/
double dfun(double x) {
	return 4*pow(x,3)-9*pow(x,2)+3*x;
}

/*
* 递归实现牛顿迭代法
* val:预测值
* precision:精度
* deep:递归深度
*/
double newtonIter(double val,double precision,int deep) {
	if(deep==0)return -1;
	//求解
	double res=yfun(val);
	if( res==0.0 || fabs(res) < precision )
		//如果找到
		return val;
	//根据牛顿迭代公式修正 val 值
	val=val-res/dfun(val);
	//递归
	return newtonIter(val,precision,deep);
}

/*
*非递归实现
*/
double newtonIter_(double val,double jd) {
	double res=yfun(val);
	while( !(res==0.0 || fabs(res) <jd)  ) {
		//根据牛顿迭代公式修正 val 值
		val=val-yfun(val)/dfun(val);
		res=yfun(val);
	}
	//如果找到
	return val;
}

int main() {
	double val=0.0;
	int deep=0;
	double precision=0.0;
	cout<<"请输入预测值"<<endl;
	cin>>val;
	cout<<"递归或迭代的最大次数:"<<endl;
	cin>>deep;
	cout<<"输入精度:"<<endl;
	cin>>precision;
	double res=newtonIter(val,precision,deep);
	cout<<res;
	return 0;
}

测试:

x=0其倒数为 0,说明为驻点,不能做为预测值。

n12.png

再试着把把 2代入导函数,其导数为 2,可以作为预测值试试:

n8.png

正向测试一下,把2.64894代入原函数,可知是符合精度要求的近似值。

2.4.2 非递归实现

//省略……
double newtonIter_(double val,double precision,int deep) {
	int i=0;
	double res=0;
	while( 1 ) {
		res=yfun(val);
		if( res==0.0 || fabs(res) <precision)
			return val;
		//根据牛顿迭代公式修正 val 值
		val=val-yfun(val)/dfun(val);
		i++;
		if(i==deep)
			return -1;
	}
	//没有
	return -1;
}

3. 二分迭代法

二分迭代法使用了二分算法思想求解非线性方程式。

下面要求使用二分迭代法求解 2x3-5x-1=0 方程式,且要求误差不能大于10e-5。二分迭代法也只是近似求解算法。

3.1 基本思想

预测或初步判断值的范围。

  • 如上述方程,把 0代入方程式,可知 f(0)=-1,然后把 1 代入方程式,则 f(1)=-4,再把 2代入方程式,得到f(2)=5。如下绘制函数在 [0,1,2] 3 个点之间的大致走势图,分析后可知在[1,2]之间必然会有一个解。

b1.png

  • 使用二分思想,计算出 [1,2]之间的中间点 x=(1+2)/2=1.5。把1.5代入方程式得到函数值为f(1.5)=-1.75。重新修正一下走势图。因为 f(1.5)*f(1)>0,说明f(1.5)和f(1)在同一边,其真实值应该在 1.5的右边。如果f(1.5)*f(1)<0,则说明f(1.5)和f(1)在横坐标的两侧,说明真实值应该在 1.5的左边。分析后可知直实值缩小在[1.5,2]之间。

b2.png

  • 再计算[1.5,2]之间的中间点x=(1.5+2)/2=1.75,把1.75代入方程式得f(1.75)=0.96875,发现值已经慢慢接近 0。分析可知,真实值应该在[1.5,1.75]之间,继续二分迭代,便可以找到近似值。

3.2 编程实现

二分迭代法同样有递归和非递归两种方案。

3.2.1 非递归

#include <iostream>
#include <cmath>
using namespace std;
/*
* 原函数
*/
double yfun(double x) {
	return 2*pow(x,3)-5*x-1;
}
/*
* 非递归实现二分迭代法,
* left:左边界
* right:右边界
* precision:精度
* 为了避免找不到结果,可以限制迭代次数。
*/
double binaryIter(double left,double right,double precision) {
	//计算左边界的函数值
	double leftVal=yfun(left);
	//右边界的函数值
	double rightVal=yfun(right);
	while(true) {
		//中间位置
		double midPos=(left+right) /2;
		//函数值
		double midVal= yfun(midPos);
		if( midVal==0.0  || fabs(midVal) < precision ) {
			//找到
			return midPos;
		} else if( leftVal*midVal>0 ) {
			//真实值应该在 midPos 的右边
			left=midPos;
		} else {
			right=midPos;
		}
	}
}
int main() {
	cout<<"左边界"<<endl;
	double left=0.0;
	cin>>left;
	cout<<"右边界"<<endl;
	double right=0.0;
	cin>>right;
	cout<<"精度:"<<endl;
	double precision=0;
	cin>>precision;
	double res= binaryIter(left,right,precision);
	cout<<res;
	return 0;
}

输出结果:

b3.png

根据前面的函数走势图,可以直观感觉到在横坐标轴的左边还有一个值。把-1代入函数,知f(-1)=2,可预测在[-1,0]之间还有一个近似值。

b5.png

b4.png

同样,可以使用二分迭代法求解上文的方程式 f(x)=x4-3x3+1.5x2-4 的解:

很容易预测出函数在[2,3]之间有解。只需要修改 yfun函数。

double yfun(double x) {
	return pow(x,4)-3*pow(x,3)+1.5*pow(x,2)-4.0;
}

其结果和牛顿迭代法计算出来的一样。

b6.png

3.2.2 递归方案

最后提供二分迭代法的递归实现。

double binaryIter_(double left,double right,double precision) {
	if(left>right)return -1;
	//计算左边界的函数值
	double leftVal=yfun(left);
	//右边界的函数值
	double rightVal=yfun(right);
	//中间位置
	double midPos=(left+right) /2;
	//函数值
	double midVal= yfun(midPos);
	if( midVal==0.0  || fabs(midVal) < precision ) {
		//找到
		return midPos;
	} else if( leftVal*midVal>0 ) {
		//真实值应该在 midPos 的右边
		return binaryIter_(midPos,right,precision);
	} else {
		return binaryIter_(left,midPos,precision);
	}
}

4. 总结

本文讲解了牛顿、二分迭代求解非线性方程。虽然牛顿迭代没有二分迭代那么容易理解,但其功能却是强大很多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/138199.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

千万别熬夜:只有睡觉,才能修复DNA损伤

睡眠是人体的一种修复过程&#xff0c;可以恢复精神和解除疲劳。人的一生中&#xff0c;大约三分之一的时间是在睡眠中度过&#xff0c;良好的睡眠是国际社会公认的三项健康标准之一&#xff0c;而睡眠时间过短或睡眠不佳均会影响健康。在中国&#xff0c;超3亿人存在睡眠障碍、…

基于智能电控柜原理的物联网云平台方案

本方案基于智能电控柜的原理&#xff0c;通过无线传输模块将现场采集到的数据经过无线网络发送到物联网云平台&#xff0c;同时可通过云组态和数据中心将现场画面所见即所得的同步到互联网终端。 用户在安装有监控软件后&#xff0c;可以用手机 APP或者在云平台上直接控制电控柜…

Ubuntu自动登录脚本,expect自动切换用户,xshell自动登录脚本

Ubuntu自动登录脚本&#xff0c;expect自动切换用户&#xff0c;xshell自动登录脚本一、!/usr/bin/expect -f的意义二、spawn命令行&#xff1a;三、send命令&#xff1a;四、expect五、interact命令&#xff1a;六、xshell自动化脚本1、怎么使用脚本2、编写脚本3、vbs的不足本…

https访问流程详解

1. 基础知识 1.1 https起源 鲍勃有两把钥匙&#xff0c;一把是公钥&#xff0c;另一把是私钥。 鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。 苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密&#xff0c;就可以达到保密的效果。 鲍勃收信后&#xf…

jvm-sandbox:基础了解及demo演示

文章目录一、基础准备-被测应用二、代码编写-自定义Module三、jvm-sandbox安装及基础命令四、jvm-sandbox demo演示4.1、改变方法返回4.2、异常注入五、资源链接一、基础准备-被测应用 准备&#xff1a;先创建一个基础的SpringBoot项目并打jar包后在服务器启动 RestControll…

TP5反序列化利用链

说明 该文章来源于同事lu2ker转载至此处&#xff0c;更多文章可参考&#xff1a;https://github.com/lu2ker/ 文章目录说明TP5反序列化利用链下图是Mochazz 大佬画的非常优雅的一张调用链图Action&#xff01;CUT&#xff01;TP5反序列化利用链 本文以第二人称视角重点谈谈给…

max蒙皮动画+动作

首先关于max人物动画&#xff01;如何制作&#xff01; 首先&#xff01; 我们要准备一个模型&#xff01;人物的模型&#xff01; 这是一个人物模型&#xff01;obj的&#xff01;没有任何东西&#xff01;你也可以选择其他&#xff0c;我是从虚幻里面直接导出的&#xff0…

太卷了,华为某领导说招外包只要985!

你听说过华为od吗&#xff1f;od是outsourcing dispatch&#xff08;外包派遣&#xff09;的简称&#xff0c;虽然华为每年会挑选一部分优秀的od员工转为华为正编员工&#xff0c;但od本质上还是外包。最近一位华为员工爆料&#xff1a;太卷了&#xff01;领导说招od员工也要98…

玻纤效应对skew的影响(二)

玻纤效应对对内skew的影响 参数对对内Skew的影响 在一个差分对中&#xff0c;对内skew是由PN走线Dk的差异造成的。导致Dk有差异的原因有很多&#xff0c;例如走线位置&#xff0c;core和pp的玻璃束位置&#xff0c;走线宽度等等。但是这些因素影响角度也不同&#xff0c;下图…

应用程序已被Java 安全阻止-- 如何全局设置Java 控制面板参数

最近遇到一个客户问题&#xff0c;客户方存在一个使用场景为使用IE访问一个页面 之后通过点击页面的按钮调起一个applet程序&#xff0c;结果遇到了一个弹窗告警&#xff1a;应用程序已被Java安全阻止。 对于这个问题 解决方案有两个&#xff1a; 1.将访问的页面站点加入到例外…

BGP在数据中心的应用2——BGP如何适应数据中心网络

注&#xff1a; 本文根据《BGP in the Datacenter》整理&#xff0c;有兴趣和英文阅读能力的朋友可以直接看原文&#xff1a;https://www.oreilly.com/library/view/bgp-in-the/9781491983416/上一部分笔记请参考&#xff1a;https://blog.csdn.net/tushanpeipei/article/deta…

echarts中得一些使用技巧和方法

一、取数据的最大值&#xff1a; let maxNum maxData.sort((a, b) > b - a)[0]&#xff1b; 二、echarts 自适应 所有的echarts里面设置了字体根据最外层body的字体来改变大小 // app.vue中的代码 // 页面开始加载时修改font-size var html document.getElementsByTagN…

VGG详解

入门小菜鸟&#xff0c;希望像做笔记记录自己学的东西&#xff0c;也希望能帮助到同样入门的人&#xff0c;更希望大佬们帮忙纠错啦~侵权立删。 ✨完整代码在我的github上&#xff0c;有需要的朋友可以康康✨ https://github.com/tt-s-t/Deep-Learning.git 目录 一、VGG网络的…

小侃设计模式(二十)-迭代器模式

1.概述 迭代器模式&#xff08;Iterator Pattern&#xff09;提供了一种方法访问一个容器对象中各个元素&#xff0c;而又不暴露该对象的内部细节。迭代器模式用于访问集合中的元素而不需要知道集合底层的数据形式。在JAVA语言中&#xff0c;迭代器模式已经成为其中不可缺少的…

3000字13张图详细介绍RAID0、1、5、6、10、50、60,非常值得收藏!

RAID简述 RAID 是一种用于提高数据存储性能和可靠性的技术&#xff0c;英文全称&#xff1a;Redundant Array of Independent Disks&#xff0c;中文意思&#xff1a;独立磁盘冗余阵列。RAID 系统由两个或多个并行工作的驱动器组成&#xff0c;这些可以是硬盘或者 SSD&#xf…

力扣刷题记录——344.反转字符串、345.反转字符串中的元音、349.两个数组的交集

本专栏主要记录力扣的刷题记录&#xff0c;备战蓝桥杯&#xff0c;供复盘和优化算法使用&#xff0c;也希望给大家带来帮助&#xff0c;博主是算法小白&#xff0c;希望各位大佬不要见笑&#xff0c;今天要分享的是——《344.反转字符串、345.反转字符串中的元音、349.两个数组…

Jvm 系列(十二) JVM的执行引擎全面讲解

JVM 执行引擎 1、执行引擎概述 执行引擎是Java虚拟机核心的组成部分之一。 “虚拟机”是相对于“物理机”的概念&#xff0c;这两种机器都有代码执行能力&#xff0c;其区别是物理机的执行引擎是直接建立在处理机、缓存、指令集和操作系统层面上的&#xff0c;而虚拟机的执行…

国产直流马达驱动芯片SS6216的功能参数以及应用

直流有刷电机驱动芯片SS6216是为消费类产品&#xff0c;玩具和其他低压或者电池供电的运动控制类应用提供了一个集成的有刷电机驱动器解决方案。是为低电压下工作的系统而设计的直流电机驱动集成电路&#xff0c;单通道低导通电阻。具备电机正转/反转/停止/刹车四个功能。 直流…

STL剖析(二):容器底层数据结构及常见用法

一.概述 本文主要聚焦于STL容器&#xff0c;STL完整的容器分类体系如下所示&#xff0c;下文将逐一对各个容器底层的数据结构以及常见用法进行介绍。 测试环境&#xff1a;Ubuntu 22.04 g 11.3.0 二.顺序容器 顺序容器都对应着线性数据结构。 2.1 array array的使用需要引…

6.2 微服务-SpringBoot

目录 6.2.1 SpringBoot 6.2.1.1 什么是Spring Boot 6.2.1.2 SpringBoot的特点 6.2.2 快速入门 6.2.2.1 创建工程 6.2.2.2 引入依赖 6.2.2.3 启动类 6.2.2.4 controller 6.2.2.5 测试 6.2.3 注解与属性注入 6.2.3.1 注解 6.2.3.1.1 EnableAutoConfiguration 6.2.3.1…