非线性方程求根迭代法(C++)

2024-01-15 06:28

本文主要是介绍非线性方程求根迭代法(C++),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 问题描述
  • 算法描述
    • 不动点迭代法
      • 一维情形
      • 多维情形
    • 牛顿迭代法
      • 单根情形
      • 重根情形
    • 割线法
    • 抛物线法
    • 逆二次插值法
  • 算法实现
    • 准备工作
    • 一般迭代法
    • 割线法
    • 抛物线法
    • 逆二次插值法
  • 实例分析
    • 例1
    • 例2

迭代法是一种求解非线性方程根的方法, 它通过构造一个迭代过程, 将一个非线性方程转化为一个等价的不动点方程, 然后通过迭代逼近不动点, 从而得到非线性方程的根.

迭代法的基本思想是将隐式方程转化为显式的计算公式, 然后通过迭代, 求方程近似根.

问题描述

已知方程

f ( x ) = 0 \bm f(\bm x)=\bm 0 f(x)=0

R n \mathbb R^n Rn的某个区域 D D D上有根, 求方程的近似解 x \bm x x.

算法描述

迭代法要求将上述方程化为如下形式:

x n + 1 = φ ( x n ) \bm x_{n+1}=\bm\varphi(\bm x_n) xn+1=φ(xn)

则可取 φ \bm\varphi φ为迭代函数. 而迭代函数的构造方式并不是唯一的, 不同算法的构造方式不同, 下面将逐个进行介绍.

不动点迭代法

不动点迭代法是一种求解非线性方程的数值方法, 其基本思想是将原方程变形为关于 x x x的迭代方程, 然后通过不断迭代求解 x x x的值.

一维情形

假设有非线性方程

f ( x ) = 0 f(x)=0 f(x)=0

则可以通过恒等变形改写为

x = φ ( x ) x=\varphi(x) x=φ(x)

因此, 我们可以得到如下不动点迭代公式:

x n + 1 = φ ( x n ) x_{n+1}=\varphi(x_n) xn+1=φ(xn)

故求解原方程的解就转化为求解函数 φ ( x ) \varphi(x) φ(x)的不动点. 设 x 0 x_0 x0为方程的一个近似解, 则利用上述不动点迭代公式即可逼近原方程的解.

多维情形

在一维情形的基础下, 进一步考虑非线性方程组

f ( x ) = 0 \bm f(\bm x)=\bm 0 f(x)=0

和一维情形类似, 可将方程变形为

x = φ ( x ) \bm x=\bm\varphi(\bm x) x=φ(x)

故有高维不动点迭代公式:

x n + 1 = φ ( x n ) \bm x_{n+1}=\bm\varphi(\bm x_n) xn+1=φ(xn)

牛顿迭代法

牛顿迭代法(Newton-Raphson method)是求解方程近似根的一种方法, 其基本思想是利用泰勒公式将方程进行线性化, 然后通过不断迭代, 使得误差逐渐缩小, 最终得到近似解.

单根情形

首先, 将 f ( x ) f(x) f(x) x = x 0 x=x_0 x=x0处进行泰勒展开, 得到

f ( x ) = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ( x − x 0 ) 2 + ⋯ f(x) = f(x_0) + f^{\prime}(x_0)(x-x_0) + \frac{f^{\prime\prime}(x_0)}{2}(x-x_0)^2 + \cdots f(x)=f(x0)+f(x0)(xx0)+2f′′(x0)(xx0)2+

其中 f ′ ( x ) f^{\prime}(x) f(x) f ′ ′ ( x ) f^{\prime\prime}(x) f′′(x)分别表示 f ( x ) f(x) f(x) x x x处的导数和二阶导数.

e ( x ) = f ( x ) f ′ ( x ) e(x) = \frac{f(x)}{f^{\prime}(x)} e(x)=f(x)f(x)

则有

e ( x ) = − f ( x 0 ) f ′ ( x 0 ) − ( x − x 0 ) − f ′ ′ ( x 0 ) 2 f ′ ( x 0 ) ( x − x 0 ) 2 − ⋯ e(x) = - \frac{f(x_0)}{f^{\prime}(x_0)} - (x-x_0) - \frac{f^{\prime\prime}(x_0)}{2f^{\prime}(x_0)}(x-x_0)^2 - \cdots e(x)=f(x0)f(x0)(xx0)2f(x0)f′′(x0)(xx0)2

因此, 可以通过迭代公式

x n + 1 = x n − f ( x n ) f ′ ( x n ) x_{n+1} = x_n - \frac{f(x_n)}{f^{\prime}(x_n)} xn+1=xnf(xn)f(xn)

不断迭代求解, 直到 e ( x ) e(x) e(x)的值足够小(即达到所需的精度), 即可得到方程的近似解.

值得注意的是, 牛顿迭代法的前提是方程的导数, 即 f ′ ( x ) f^{\prime}(x) f(x)在所求解的区间内不等于零, 否则会导致方法失效. 此外, 迭代过程中可能会出现震荡或收敛于非解的情况, 此时需要采取适当的策略进行优化和调整.

重根情形

x 0 x_0 x0 f ( x ) f(x) f(x)的根, 且其重数大于 1 1 1, 此时 f ( x 0 ) f(x_0) f(x0) f ′ ( x 0 ) f^\prime(x_0) f(x0)都为 0 0 0, 这给牛顿法和后面的割线法都带来了问题, 因为在它们的公式分母中都包含导数或其估算值, 当解收敛到离根非常近时, 可能会导致除零错误.

对此, 我们可以令

u ( x ) = f ( x ) f ′ ( x ) u(x)=\frac{f(x)}{f^\prime(x)} u(x)=f(x)f(x)

则可以证明: 若 x 0 x_0 x0为原方程的 m m m重根, 则其为方程

u ( x ) = 0 u(x)=0 u(x)=0

的单根, 从而对 u ( x ) = 0 u(x)=0 u(x)=0应用牛顿迭代法即可.

割线法

f : R → R f:\mathbb R\rightarrow\mathbb R f:RR, 但函数 f f f的性质不好, 可能在某些地方不可导或导数难以计算. 这时我们就可以利用割线近似切线, 即

f ′ ( x n ) ≈ f ( x n ) − f ( x n − 1 ) x n − x n − 1 f^\prime(x_n)\approx\frac{f(x_n)-f(x_{n-1})}{x_n-x_{n-1}} f(xn)xnxn1f(xn)f(xn1)

代入牛顿迭代公式, 就得到了割线法:

x k + 1 = x k − x n − x n − 1 f ( x n ) − f ( x n − 1 ) f ( x k ) x_{k+1}=x_k-\frac{x_n-x_{n-1}}{f(x_n)-f(x_{n-1})}f(x_k) xk+1=xkf(xn)f(xn1)xnxn1f(xk)

抛物线法

抛物线法又称为米勒法, 是割线法的推广. 割线法是利用前两项构造线性插值, 而抛物线法则是利用前三项构造二次插值, 则我们可以求得如下迭代公式:

x k + 1 = x k − 2 f k ω + s g n ( ω ) ω 2 − 4 f k f [ x k , x k − 1 , x k − 2 ] x_{k+1}=x_k-\frac{2f_k}{\omega+{\rm sgn}(\omega)\sqrt{\omega^2-4f_kf[x_k,x_{k-1},x_{k-2}]}} xk+1=xkω+sgn(ω)ω24fkf[xk,xk1,xk2] 2fk

其中,

ω = f k − f k − 1 x k − x k − 1 + x k − x k − 1 x k − x k − 2 ( f k − f k − 1 x k − x k − 1 − f k − 1 − f k − 2 x k − 1 − x k − 2 ) \omega=\frac{f_k-f_{k-1}}{x_k-x_{k-1}}+\frac{x_k-x_{k-1}}{x_k-x_{k-2}}\left(\frac{f_k-f_{k-1}}{x_k-x_{k-1}}-\frac{f_{k-1}-f_{k-2}}{x_{k-1}-x_{k-2}}\right) ω=xkxk1fkfk1+xkxk2xkxk1(xkxk1fkfk1xk1xk2fk1fk2)

逆二次插值法

在抛物线法中, 当构造的抛物线不与 x x x轴相交时, 可能会收敛到方程的复数根. 这一方面体现了抛物线法的一个优点: 实的初始值迭代求得方程的复数根; 但同时也有可能无法收敛到实数根. 使用逆二次插值法可以解决这个问题.

逆二次插值法又称为反抛物线法, 即使用以 y y y为自变量的抛物线 x = g ( y ) x=g(y) x=g(y)与横轴的交点逼近函数 f ( x ) f(x) f(x)的实根. 可计算得迭代公式如下:

x k + 1 = x k − y k x k − x k − 1 y k − y k − 1 + y k y k − 1 y k − y k − 2 ( x k − x k − 1 y k − y k − 1 − x k − 1 − x k − 2 y k − 1 − y k − 2 ) x_{k+1}=x_k-y_k\frac{x_k-x_{k-1}}{y_k-y_{k-1}}+\frac{y_ky_{k-1}}{y_k-y_{k-2}}\left(\frac{x_k-x_{k-1}}{y_k-y_{k-1}}-\frac{x_{k-1}-x_{k-2}}{y_{k-1}-y_{k-2}}\right) xk+1=xkykykyk1xkxk1+ykyk2ykyk1(ykyk1xkxk1yk1yk2xk1xk2)

算法实现

准备工作

由于牵涉到高维问题, 我们首先编写一个向量之间的度量函数:

double distance(const std::vector<double> &x, const std::vector<double> &y)
{size_t n(x.size());if (!n)return 0;if (n != y.size())return NAN;double r(0);auto i = x.cend(), j = y.cend();do{double t(*--i - *--j);r += t *= t;} while (--n);return sqrt(r);
}

由于抛物线法中涉及到符号函数, 另外编写符号函数sgn:

// 符号函数
template <class T>
inline int sgn(const T &x) noexcept
{if (x == 0)return 0;if (x > 0)return 1;return -1;
}

此外, 为了方便用户输入函数, 还要编写一个字符串替换的函数:

/** 替换子串* str   : 要替换的字符串* oldStr: 旧子串* newStr: 新子串* except: 排除的子串*/
std::string &substring_replace(std::string &str, const std::vector<std::string> &oldStr, const std::vector<std::string> &newStr, const std::vector<std::string> &except)
{if (oldStr.empty()){str.clear();return str;}// if(oldStr.size()!=newStr.size())//     if(oldStr.size()>newStr.size())//         oldStr.erase(oldStr.begin()+newStr.size(),oldStr.end());//     else//         newStr.erase(newStr.begin()+oldStr.size(),newStr.end());size_t n(oldStr.size() > newStr.size() ? newStr.size() : oldStr.size());QBitArray a(str.length()); // 这里用了Qt中的QBitArray, 使用bool数组也可以实现其功能for (const auto &i : except){size_t pos(0);while ((pos = str.find(i, pos)) != std::string::npos){a.fill(true, pos, pos + i.length());pos += i.length();}}size_t pos(0);for (size_t k(0); k < n; ++k){const std::string &pre = oldStr[k], &next = newStr[k];F:while ((pos = str.find(pre, pos)) != std::string::npos){size_t i(pre.length()), p(pos);doif (a.at(p++)){pos += pre.length();goto F;}while (--i);QBitArray t(a.size() + next.size() - pre.size());dot.setBit(i, a.at(i));while (++i < pos);p = pos + pre.length();str.replace(pos, pre.length(), next);if (i != (pos += next.length()))dot.setBit(i, false);while (++i != pos);while (p != a.size())t.setBit(i++, a.at(p++));a = t;}}return str;
}

以及浮点向量转字符串向量的函数:

// double向量转string向量
std::vector<std::string> vec_to_string(const std::vector<double> &x)
{std::vector<std::string> y(x.size());auto i = x.cbegin();auto k = y.begin();while (i < x.cend())*k++ = *i++;return y;
}

一般迭代法

为了方便用户输入, 我们首先引入字符串函数计算1:

extern double calStr(string);
extern double calStr_x(string, const double &);
vector<string> func{"sqrt", "sin", "log", "exp", "pow", "abs", "cos", "tan", "asin", "acos", "atan"};

接着实现一般迭代法的单步迭代:

/** 迭代法* current_vec: 迭代向量* func       : 迭代函数* x          : 未知数** 返回(bool):*  true : 迭代失败*  false: 迭代成功*/
bool iterative_method(std::vector<double> &current_vec, const std::vector<std::string> &f, const std::vector<std::string> &x)
{std::vector<double> x0(current_vec);unsigned n(current_vec.size());do{std::string toCal(f[--n]);substring_replace(toCal, x, vec_to_string(current_vec), func);if (isnan(current_vec[n] = calStr(toCal))){current_vec = x0;return true;}} while (n);return false;
}

最终, 实现一般迭代法如下:

/** 迭代法* current_vec: 迭代向量* func       : 迭代函数* x          : 未知数* epsilon    : 迭代终止条件* mid        : 迭代中间结果导出** 返回(bool):*  true : 迭代失败*  false: 迭代成功*/
bool iterative_method(std::vector<double> &current_vec, const std::vector<std::string> &func, const std::vector<std::string> &x, double epsilon, QString &mid) // mid使用了QString, 可以对应转换为std::string或者std::vector<vector<double>>
{for (auto &i : x)mid.append(QString::fromStdString(i)).append(',');*(mid.end() - 1) = '\n';for (auto &i : current_vec)mid.append(QString::number(i, 'g', 14)).append(',');std::vector<double> x0(current_vec), x1(x0);if (iterative_method(current_vec, func, x)){current_vec = x0;return true;}*(mid.end() - 1) = '\n';for (auto &i : current_vec)mid.append(QString::number(i, 'g', 14)).append(',');while (distance(current_vec, x1) >= epsilon){x1 = current_vec;if (iterative_method(current_vec, func, x)){current_vec = x0;return true;}*(mid.end() - 1) = '\n';for (auto &i : current_vec)mid.append(QString::number(i, 'g', 14)).append(',');}mid.chop(1);return false;
}

割线法

/** 割线法* f   : 原方程函数* x1  : 第1个值* x2  : 第2个值* e   : 精度* path: 迭代保存路径*/
void secant_method(const function<double(double)> &f, double x1, double x2, double e = 1e-6, const QString &path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation))
{QFile file(path);if (!(file.open(QIODevice::WriteOnly)))throw "文件保存失败!";double f1(f(x1)), f2;if (isnan(f1))throw "区间内存在奇点!";file.write((to_string(x1) + '\n' + to_string(x2)).c_str());while (abs(x1 - x2) >= e){if (isnan(f2 = f(x2))){file.close();throw "区间内存在奇点!";}double t = x2 - (x2 - x1) * f2 / (f2 - f1);file.write(('\n' + to_string(t)).c_str());x1 = x2;x2 = t;f1 = f2;}file.close();
}

抛物线法

/** 抛物线法* f   : 原方程函数* x1  : 第1个值* x2  : 第2个值* x3  : 第3个值* e   : 精度* path: 迭代保存路径*/
void parabolic_method(const function<double(double)> &f, double x1, double x2, double x3, double e = 1e-6, const QString &path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation))
{QFile file(path);if (!(file.open(QIODevice::WriteOnly)))throw "文件保存失败!";double f1(f(x1)), f2(f(x2)), f3, omega0((f2 - f1) / (x2 - x1));if (isnan(f1) || isnan(f2)){file.close();throw "区间内存在奇点!";}file.write((to_string(x1) + '\n' + to_string(x2) + '\n' + to_string(x3)).c_str());while (abs(x1 - x2) >= e){if (isnan(f3 = f(x3))){file.close();throw "区间内存在奇点!";}double omega1 = (f3 - f2) / (x3 - x2), d123 = (omega1 - omega0) / (x3 - x1), omega = omega1 + (x3 - x2) * d123, delta = omega * omega - 4 * f3 * d123;if (delta < 0){file.close();throw "迭代出现复根!";}double t = x3 - 2 * f3 / (omega + sgn(omega) * sqrt(delta));file.write(('\n' + to_string(t)).c_str());x1 = x2;x2 = x3;x3 = t;f1 = f2;f2 = f3;omega0 = omega1;}file.close();
}

逆二次插值法

/** 逆二次插值法* f   : 原方程函数* x1  : 第1个值* x2  : 第2个值* x3  : 第3个值* e   : 精度* path: 迭代保存路径*/
void inverse_quadratic_interpolation(const function<double(double)> &f, double x1, double x2, double x3, double e = 1e-6, const QString &path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation))
{QFile file(path);if (!(file.open(QIODevice::WriteOnly)))throw "文件保存失败!";double f1(f(x1)), f2(f(x2)), f3, omega0((x2 - x1) / (f2 - f1));if (isnan(f1) || isnan(f2)){file.close();throw "区间内存在奇点!";}file.write((to_string(x1) + '\n' + to_string(x2) + '\n' + to_string(x3)).c_str());while (abs(x1 - x2) >= e){if (isnan(f3 = f(x3))){file.close();throw "区间内存在奇点!";}double omega1 = (x3 - x2) / (f3 - f2), t = x3 - omega1 * f3 + (omega1 - omega0) * f3 * f2 / (f3 - f1);file.write(('\n' + to_string(t)).c_str());x1 = x2;x2 = x3;x3 = t;f1 = f2;f2 = f3;omega0 = omega1;}file.close();
}

实例分析

例1

设有方程 x 3 + 2 x 2 + 10 x − 20 = 0 x^3+2x^2+10x-20=0 x3+2x2+10x20=0.

首先计算出 f ′ ( x ) = 3 x 2 + 4 x + 10 f^\prime(x)=3x^2+4x+10 f(x)=3x2+4x+10, 下面我们讨论选取不同初值时Newton迭代法的收敛速度.

分别取 x 0 ∈ { x ∈ Z : − 128 ⩽ x ⩽ 127 } x_0\in\{x\in\mathbb Z:-128\leqslant x\leqslant 127\} x0{xZ:128x127}, 并取终止标准为 ∣ f ( x n ) ∣ < 1 0 − 6 |f(x_n)|<10^{-6} f(xn)<106, 计算得其误差与迭代次数如下所示:


观察可得, 在 x 0 = 1 x_0=1 x0=1附近时迭代次数较少, 收敛较快, 接着在 1 1 1附近取更密集的初值, 计算结果如下所示:


利用卡当公式, 我们可以求得方程有唯一实根

x = − 2 3 − 13 4 3 3 3 3930 + 176 3 + 1 3 6 3930 + 352 3 = 1.36880 ⋯ x=-\frac23-\frac{13\sqrt[3]{4}}{3\sqrt[3]{3\sqrt{3930}+176}}+\frac13\sqrt[3]{6\sqrt{3930}+352}=1.36880\cdots x=323333930 +176 1334 +31363930 +352 =1.36880

可见当初值越靠近根 x x x, 迭代次数越少, 收敛速度越快.

例2

取初值 x 0 = 4 , x 1 = 3.8 x_0=4,x_1=3.8 x0=4,x1=3.8, 求方程 x 3 − 2 x − 5 = 0 x^3-2x-5=0 x32x5=0 [ 1 , 4 ] [1,4] [1,4]上的根.

仍然取终止标准为 ∣ f ( x n ) ∣ < 1 0 − 6 |f(x_n)|<10^{-6} f(xn)<106, 使用Newton迭代法可得误差为 − 1.77636 × 1 0 − 5 -1.77636\times10^{-5} 1.77636×105, 迭代次数为6次; 取终止标准为 ∣ x n + 1 − x n ∣ < 1 0 − 6 |x_{n+1}-x_n|<10^{-6} xn+1xn<106, 使用割线法可得误差为 2.37144 × 1 0 − 13 2.37144\times10^{-13} 2.37144×1013, 迭代次数为8次.


  1. 参见C++实现简单计算器(字符串替换). ↩︎

这篇关于非线性方程求根迭代法(C++)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/607954

相关文章

C++ 中的 if-constexpr语法和作用

《C++中的if-constexpr语法和作用》if-constexpr语法是C++17引入的新语法特性,也被称为常量if表达式或静态if(staticif),:本文主要介绍C++中的if-c... 目录1 if-constexpr 语法1.1 基本语法1.2 扩展说明1.2.1 条件表达式1.2.2 fa

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++常见容器获取头元素的方法大全

《C++常见容器获取头元素的方法大全》在C++编程中,容器是存储和管理数据集合的重要工具,不同的容器提供了不同的接口来访问和操作其中的元素,获取容器的头元素(即第一个元素)是常见的操作之一,本文将详细... 目录一、std::vector二、std::list三、std::deque四、std::forwa

C++字符串提取和分割的多种方法

《C++字符串提取和分割的多种方法》在C++编程中,字符串处理是一个常见的任务,尤其是在需要从字符串中提取特定数据时,本文将详细探讨如何使用C++标准库中的工具来提取和分割字符串,并分析不同方法的适用... 目录1. 字符串提取的基本方法1.1 使用 std::istringstream 和 >> 操作符示

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提

C++快速排序超详细讲解

《C++快速排序超详细讲解》快速排序是一种高效的排序算法,通过分治法将数组划分为两部分,递归排序,直到整个数组有序,通过代码解析和示例,详细解释了快速排序的工作原理和实现过程,需要的朋友可以参考下... 目录一、快速排序原理二、快速排序标准代码三、代码解析四、使用while循环的快速排序1.代码代码1.由快