即使目标函数 f ( x ) f(x) f(x) 是一元函数,求最小值点也经常需要使用数值迭代方法。
在多元目标函数优化 中,一般每次迭代从上一步的 x ( t ) \boldsymbol{x}^{(t)} x(t) 先确定一个下降方向 d ( t ) \boldsymbol{d}^{(t)} d(t) ,然后对派生出的一元函数 h ( α ) = f ( x ( t ) + α d ( t ) ) , α ≥ 0 h(\alpha)=f\left(\boldsymbol{x}^{(t)}+\alpha \boldsymbol{d}^{(t)}\right), \alpha \geq 0 h(α)=f(x(t)+αd(t)),α≥0 求最小值点得到下降的步长 α t \alpha_t αt ,并令 x ( t + 1 ) = x ( t ) + α t d ( t ) \boldsymbol{x}^{(t+1)}=\boldsymbol{x}^{(t)}+\alpha_t \boldsymbol{d}^{(t)} x(t+1)=x(t)+αtd(t) , 求步长 的过程称为一维搜索,搜索可以是求一元函数 h ( α ) h(\alpha) h(α) 的精确最小值点,也可以求一个使得目标函数下降足够 多的 α \alpha α 作为步长。
对于多元目标函数 f ( x ) , x ∈ R d f(\boldsymbol{x}) , \boldsymbol{x} \in \mathbb{R}^d f(x),x∈Rd, 有一种优化方法是先沿 x 0 x_0 x0 坐标轴方向搜索,再沿 x 1 x_1 x1 坐标轴方向搜索,直到沿 x d x_d xd 坐标轴方向搜索后再重复地从 x 0 x_0 x0 坐标轴方向开始, 这种方法叫做坐标循环下降法。
一元函数的求根问题和优化问题使用的算法类似,下面讨论一元函数的优化和求根问题。
所涉及C++代码均存于github仓库。
python版本代码见于github仓库。
优化问题与求根问题密切相关,很多优化问题会归结为一个求根问题。对一元目标函数 f ( x ) f(x) f(x) ,若 f ( x ) f(x) f(x) 连续 可微,极值点一定是 f ′ ( x ) = 0 f^{\prime}(x)=0 f′(x)=0 的根。二分法是实际数值计算中一元函数求根使用最多的方法, 这种方法简单而又有比较高的收敛速度。
设函数 f ( x ) f(x) f(x) 在闭区间 [ a , b ] [a, b] [a,b] 连续, 且 f ( a ) f ( b ) ≤ 0 f(a) f(b) \leq 0 f(a)f(b)≤0 ,由零点定理,至少有一个点 x ∗ ∈ [ a , b ] x^* \in[a, b] x∗∈[a,b] 使得 f ( x ∗ ) = 0 f\left(x^*\right)=0 f(x∗)=0 。如果 f ( x ) f(x) f(x) 在 [ a , b ] [a, b] [a,b] 上还是严格单调函数则解 x ∗ x^* x∗ 存在是唯一的。
一般使用二分法求根时需要函数是单调的(不妨设单调递增)。
二分法求根用区间 [ a , b ] [a, b] [a,b] 中点 c c c 处函数值 f ( c ) f(c) f(c) 的正负号来判断根在区间中点的左边还是右边,显然,如果 f ( c ) f(c) f(c) 与 f ( a ) f(a) f(a) 异号,则 [ a , c ] [a, c] [a,c] 中有根;如果 f ( c ) f(c) f(c) 与 f ( b ) f(b) f(b) 异号,则 [ c , b ] [c, b] [c,b] 中有根。根据这样的想法,可以迭代地每次用法如下:
设真实根为 x ∗ x^* x∗ ,第 k k k 步的区间中点为 x ( k ) x^{(k)} x(k) ,则
∣ x ( k ) − x ∗ ∣ ≤ ( b − a ) ( 1 2 ) k , \left|x^{(k)}-x^*\right| \leq(b-a)\left(\frac{1}{2}\right)^k, ∣ ∣x(k)−x∗∣ ∣≤(b−a)(21)k,
二分法具有线性收敛速度。
求解 x 2 − ln x = 0 x^2-\ln x=0 x2−lnx=0的根。含根的区间为 [ 0 , 2 ] [0,2] [0,2]。
//
// Created by CHH3213 on 2022/9/12.
//
#include
#include
using namespace std;
#define EPS 1e-6
/*
* 使用二分法求解方程的根:np.log(x)+x^2=0
*/
double func(double x){
/**
* 构造函数
* x:自变量
* return:函数值
*/
return log(x)+x*x;
}
double bisection(double a, double b){
/**
* 二分法求根
* [a,b]: 含有根的区间
*/
if(a>b)swap(a,b);//确保a < b
double y_a = func(a),y_b =func(b);
if(y_a==0){// 已经是根
b=a;
return a;
}
if(y_b==0){// 已经是根
a=b;
return b;
}
while(b-a>EPS){
double x = a+(b-a)/2;
double y = func(x);
if(y==0){
a=x,b=x;
break;
}else if(y*y_a>=0){ // 根在右半边
a=x;
}else{//根在左半边
b=x;
}
}
return a;
}
int main(){
cout<<"answer: "<<bisection(0.0,2.0)<<"----prove: "<<func(bisection(0.0,2.0)) <<endl;
return 0;
};
求出的根为
answer: 0.652918----prove: -2.20888e-006
如果 f ( x ) f(x) f(x) 是 [ a , ∞ ) [a, \infty) [a,∞) 区间上的严格单调的连续函数, 且 lim x → ∞ f ( x ) \lim _{x \rightarrow \infty} f(x) limx→∞f(x) 与 f ( a ) f(a) f(a) 反号,则 f ( x ) f(x) f(x) 在 [ a , ∞ ) [a, \infty) [a,∞) 上存在唯 一的根, 这时需要先找到闭区间 [ a , b ] [a, b] [a,b] 使得 f ( a ) f(a) f(a) 与 f ( b ) f(b) f(b) 反号,二分法算法需要略作调整:
其它的情况,比如 f ( x ) f(x) f(x) 定义在 ( − ∞ , ∞ ) , ( a , b ) (-\infty, \infty),(a, b) (−∞,∞),(a,b) 的情况可类似处理, 都需要先找到使得区间端点函数值符号 相反的闭区间。
如果需要找出哪个闭区间含根,需要使用求含根区间的算法,假设初始步长为 h 0 h_0 h0:
求出含根的闭区间后再使用二分法进行求根。
假设我们要求 f ( x ) = 0 f(x)=0 f(x)=0的根,牛顿法的求解步骤如下:
给定一 个初值 x 0 x_0 x0 ,在 x 0 x_0 x0 处用一阶泰勒展式来近似表示函数 f ( x ) f(x) f(x)
f ( x ) = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) (1) \tag{1} f(x)=f\left(x_0\right)+f^{\prime}\left(x_0\right)\left(x-x_0\right) f(x)=f(x0)+f′(x0)(x−x0)(1)
将 f ( x ) = 0 f(x)=0 f(x)=0 带入公式 ( 1 ) (1) (1) ,求得与 x x x 轴交点横坐标 x = x 0 − f ( x 0 ) / f ′ ( x 0 ) x=x_0-f\left(x_0\right) / f^{\prime}\left(x_0\right) x=x0−f(x0)/f′(x0) ,这个 x x x 点并 不是函数 f ( x ) f(x) f(x) 的根,但是距离真正的根更近了一点,如下图所示。
将上一步所求的 x x x 值作为 x 1 x_1 x1 值,那么 x 1 = x 0 − f ( x 0 ) / f ′ ( x 0 ) x_1=x_0-f\left(x_0\right) / f^{\prime}\left(x_0\right) x1=x0−f(x0)/f′(x0) 在 x 1 x_1 x1 值处用一阶泰勒展式:
f ( x ) = f ( x 1 ) + f ′ ( x 1 ) ( x − x 1 ) (2) \tag{2} f(x)=f\left(x_1\right)+f^{\prime}\left(x_1\right)\left(x-x_1\right) f(x)=f(x1)+f′(x1)(x−x1)(2)
将 f ( x ) = 0 f(x)=0 f(x)=0 带入公式 ( 2 ) (2) (2) ,求得交点 x = x 1 − f ( x 1 ) / f ′ ( x 1 ) x=x_1-f\left(x_1\right) / f^{\prime}\left(x_1\right) x=x1−f(x1)/f′(x1) ,依次迭代进而推出公式 (3)
x n + 1 = x n − f ( x n ) / f ′ ( x n ) (3) \tag{3} x_{n+1}=x_n-f\left(x_n\right) / f^{\prime}\left(x_n\right) xn+1=xn−f(xn)/f′(xn)(3)
最终求得的 x x x 值变化小于预先设定的精度就认为这个 x x x 值是函数 f ( x ) f(x) f(x) 的近似根,此时牛顿法收敛。
求解 x 2 − ln x = 0 x^2-\ln x=0 x2−lnx=0的根。
//
// Created by CHH3213 on 2022/9/12.
//
#include
#include
using namespace std;
#define DELTA 1e-4
#define EPS 1e-6
/*
* 使用牛顿法求解方程的根:-np.log(x)+x^2=0
* 步骤:原函数->构造损失函数->对损失函数求导->进行梯度下降
*/
double func(double x){
/**
* 构造函数
* x:自变量
* return:函数值
*/
return log(x)+x*x;
}
double func_prime(double x){
/**
* 计算函数导数,以导数定义的方式求解
*/
return func(x+DELTA)-func(x-DELTA)/(2*DELTA);
}
double Newton(double x0){
double x = x0;
double x_last=0.1;
while(abs(x-x_last)>EPS){
x_last=x;
x=x_last-func(x_last)/ func_prime(x_last);
}
return x;
}
int main(){
cout<<"answer: "<<Newton(0.5)<<"----prove: "<<func(Newton(0.5)) <<endl;
return 0;
}
求出的根为
answer: 0.652899----prove: -5.607e-005
梯度下降算法一般用于求解最值问题,但同样也可以求解方程 f ( x ) f(x) f(x)的根。求解方程的根即求解在哪个点 f ( x ) f(x) f(x)离0的距离最近。数学上定义两个数距离就是绝对值,但是因为绝对值不便于计算,所以将其替换成等价的差的平方,即损失函数:
l o s s = ( f ( x ) − 0 ) 2 loss = (f(x)-0)^2 loss=(f(x)−0)2
求解方程的根,就是找到一个点使得构造的损失函数最小,这样就转化成了求最值问题。
求解二次方程 x 2 + 2 x − 3 = 0 x^2+2x-3=0 x2+2x−3=0的根。
我们通过设置不同的初值找出这个二次方程的根。
//
// Created by CHH3213 on 2022/8/18.
//
#include
#include
/*
* 使用梯度下降法求解二次方程的根
* 步骤:原函数->构造损失函数->对损失函数求导->进行梯度下降
*/
using namespace std;
#define DELTA 1e-4
#define EPS 1e-6
//y=ax^2+bx+c
double func(double a,double b,double c,double x){
return a*pow(x,2)+b*x+c;
}
//构造损失函数
double loss_func(double a,double b,double c,double x){
return pow(func(a,b,c,x)-0,2);
}
//构造损失函数导数
double loss_func_prime(double a,double b,double c,double x){
return (loss_func(a,b,c,x+DELTA)-loss_func(a,b,c,x))/DELTA;
}
double GradientDescent(double a,double b,double c,double x0,double learning_rate ){
/**
* 梯度下降算法
* a,b,c为二次函数的系数
* x0为迭代初值
* learning_rate为学习率
*/
double x = x0;
while(abs(loss_func(a,b,c,x))>EPS){
x = x-learning_rate* loss_func_prime(a,b,c,x);
}
return x;
}
int main(){
double a = 1.0,b=2.0,c=-3.0;
double x0=-b/(2*a)-1;//通过设置不同初值找出来
double x1= GradientDescent(a,b,c,x0,0.01);
x0=-b/(2*a)+1;
double x2= GradientDescent(a,b,c,x0,0.01);
cout<<x1<<" "<<x2<<endl;
}
对一元连续函数 f ( x ) f(x) f(x) ,若存在 A < x ∗ < B A
设定义于 ( − ∞ , ∞ ) (-\infty, \infty) (−∞,∞) 或 ( 0 , ∞ ) (0, \infty) (0,∞) 的连续目标函数 f ( x ) f(x) f(x) 存在局部极小值点 x ∗ x^* x∗, 且设 f ( x ) f(x) f(x) 在 x ∗ x^* x∗ 的一个邻域内是单 谷的, 则只要能找到三个点 a < c < b a
为了求 a , b a, b a,b, 从两个初始点 x 0 , x 1 x_0, x_1 x0,x1 出发,如果 f ( x 0 ) > f ( x 1 ) f\left(x_0\right)>f\left(x_1\right) f(x0)>f(x1) ,则最小值点可能在 x 1 x_1 x1 右侧,向右侧搜索找到 一个函数值超过 f ( x 1 ) f\left(x_1\right) f(x1) 的点 x 2 x_2 x2 ,这时最小值点应该在 ( x 0 , x 2 ) \left(x_0, x_2\right) (x0,x2) 内部; 如果 f ( x 0 ) < f ( x 1 ) f\left(x_0\right)
如果函数 f ( x ) f(x) f(x) 的定义域为 x ∈ ( 0 , ∞ ) x \in(0, \infty) x∈(0,∞) ,以上的算法应取初值 x 0 > 0 x_0>0 x0>0 ,如果出现向左搜索,可取迭代公式为 x k + 1 = 1 γ x k x_{k+1}=\frac{1}{\gamma} x_k xk+1=γ1xk。
如果目标函数 f ( x ) f(x) f(x) 连续可导,则求最小值点所在区间也可以通过求 a < b aa<b 使得 f ′ ( a ) < 0 , f ′ ( b ) > 0 f^{\prime}(a)<0, f^{\prime}(b)>0 f′(a)<0,f′(b)>0 来解决。可取初值 x 0 x_0 x0 ,如果 f ′ ( x 0 ) < 0 f^{\prime}\left(x_0\right)<0 f′(x0)<0 ,则类似上面算法那样向右搜索找到 b b b 使得 f ′ ( b ) > 0 f^{\prime}(b)>0 f′(b)>0 ; 如果 f ′ ( x 0 ) > 0 f^{\prime}\left(x_0\right)>0 f′(x0)>0 ,则向左搜索找到 a < x 0 a
0.618法(黄金分割法)是一种常用的不需要导数信息的一维搜索方法。假设 f ( x ) f(x) f(x) 在闭区间 [ a , b ] [a, b] [a,b] 内是单谷的,存在最小值点 x ∗ ∈ ( a , b ) x^* \in(a, b) x∗∈(a,b) , 这时可以用 0.618 0.618 0.618 法求最小值点。
在区间 [ a , b ] [a, b] [a,b] 内对称地取两个点 c < d c
d − a b − a = ρ ≜ 5 − 1 2 ≈ 0.618 , b − c b − a = ρ , \frac{d-a}{b-a}=\rho \triangleq \frac{\sqrt{5}-1}{2} \approx 0.618, \quad \frac{b-c}{b-a}=\rho, b−ad−a=ρ≜25−1≈0.618,b−ab−c=ρ,
黄金分割比例 ρ \rho ρ 满足
1 − ρ ρ = ρ , \frac{1-\rho}{\rho}=\rho, ρ1−ρ=ρ,
这样的比例的特点是,当区间缩小到 [ a , d ] [a, d] [a,d] 时, c c c 仍为缩小后的区间的右侧黄金分割点;当区间缩小到 [ c , b ] [c, b] [c,b] 时, d d d 仍为缩小后的区间的左侧黄金分割点。
这样选了 c , d c, d c,d 两个点后,比较 f ( c ) f(c) f(c) 和 f ( d ) f(d) f(d) 的值,如果 f ( c ) f(c) f(c) 较 小,则因为 a < c < d , f ( a ) > f ( c ) , f ( c ) < f ( d ) a
如果比较 f ( c ) f(c) f(c) 和 f ( d ) f(d) f(d) 时发现 f ( d ) f(d) f(d) 较小,则把搜 索区间缩小到 [ c , b ] [c, b] [c,b], 这时 d d d 是 [ c , b ] [c, b] [c,b] 内的左侧黄金分割点,只要再添加右侧黄金分割点。每次迭代使得区间长 度缩小到原来的0.618倍, 缩小后的两个黄金分割点中一个的坐标和函数值是已经算过的。
设规定的最小值点绝对误差限为 ϵ \epsilon ϵ ,算法如下:
0.618法具有线性收敛速度。
0.61 8 n ( b − a ) ≤ ϵ ⇒ n ≥ ln ϵ b − a ln 0.618 0.618^n(b-a)\leq \epsilon\Rightarrow n\ge\frac{ \ln\frac{\epsilon}{b-a}}{\ln 0.618} 0.618n(b−a)≤ϵ⇒n≥ln0.618lnb−aϵ
求解函数 f ( x ) = x 2 − 5 f(x)=x^2-5 f(x)=x2−5迭代 n n n次后的最小值区间。每次迭代区间长度减小到原来的0.618倍。
//
// Created by CHH3213 on 2022/9/12.
//
#include
#include
#include
using namespace std;
#define EPS 1e-6
/*
* 使用黄金搜索法求解迭代n次后的含最小值区间,:f(x)=x^2-5
*/
double func(double x){
/**
* 构造函数,单谷函数
* x:自变量
* return:函数值
*/
return x*x-5;
}
vector<double> golden_section_search(double a, double b,int n){
/**
* 含最小值的区间[a,b]
* n: 迭代次数
*/
double rho = (sqrt(5)-1)/2.0;//0.618黄金分割比
double c=rho*a+(1-rho)*b;//(a, b)中靠近a的黄金分割点
double d = rho*b+(1-rho)*a;//(a,b)中靠近b的黄金分割点
while(n--){
if(func(c)>func(d)){
//这时(c, b)是含最小值区间, d是(c, b)的中靠近c的黄金分割点, 仍用a, b表示含最小值区间,c表示靠近a的黄金分割点
a=c;
c=d;
d= rho*b+(1-rho)*a;//(a,b)中靠近b的黄金分割点
}else{
//这时(a, d)是含最小值区间,c是区间(a,d)的靠近d的黄金分割点,仍用a, b表示含最小值区间,d表示靠近b的黄金分割点
b=d;
d=c;
c=rho*a+(1-rho)*b;
}
}
return {a,b};
}
int main(){
vector<double> res = golden_section_search(-2,2,10);
cout<<res[0]<<' '<<res[1]<<endl;
return 0;
}
迭代10次的结果为
-0.0263112 0.00621124
用黄金分割法求解最小值也很简单,只要迭代次数够多,或者设置 b − a b-a b−a的精度达到预设的阈值即可。
进一步地,如果是求 f ( x ) = x 2 − 5 = 0 f(x)=x^2-5=0 f(x)=x2−5=0的根,那么可以构造损失函数,求解损失函数的最小值也就是求解方程的根。代码相比之前的改动很小:
//
// Created by CHH3213 on 2022/9/12.
//
#include
#include
#include
using namespace std;
#define EPS 1e-6
/*
* 使用黄金搜索法求解迭代n次后的方程的根所在区间:f(x)=x^2-5=0
*/
double func(double x){
//构造损失函数(x^2-5)^2
return pow(x*x-5,2);
}
vector<double> golden_section_search(double a, double b,int n){
/**
* 含最小值的区间[a,b]
* n: 迭代次数
*/
double rho = (sqrt(5)-1)/2.0;//0.618黄金分割比
double c=rho*a+(1-rho)*b;//(a, b)中靠近a的黄金分割点
double d = rho*b+(1-rho)*a;//(a,b)中靠近b的黄金分割点
while(n--){
if(func(c)>func(d)){
//这时(c, b)是含最小值区间, d是(c, b)的中靠近c的黄金分割点, 仍用a, b表示含最小值区间,c表示靠近a的黄金分割点
a=c;
c=d;
d= rho*b+(1-rho)*a;//(a,b)中靠近b的黄金分割点
}else{
//这时(a, d)是含最小值区间,c是区间(a,d)的靠近d的黄金分割点,仍用a, b表示含最小值区间,d表示靠近b的黄金分割点
b=d;
d=c;
c=rho*a+(1-rho)*b;
}
}
return {a,b};
}
int main(){
vector<double> res = golden_section_search(-3,3,20);
cout<<res[0]<<' '<<res[1]<<endl;
return 0;
}
为了用二次多项式逼近 f ( x ) f(x) f(x) ,只 要有 f ( x ) f(x) f(x) 函数图像上的三个点就可以。仍假设 f ( x ) f(x) f(x) 在闭区间 [ a , b ] [a, b] [a,b] 内是单谷的,存在最小值点 x ∗ ∈ ( a , b ) x^* \in(a, b) x∗∈(a,b) 。 设 c ∈ ( a , b ) c \in(a, b) c∈(a,b), 利用 ( a , f ( a ) ) , ( c , f ( c ) ) , ( b , f ( b ) ) (a, f(a)),(c, f(c)),(b, f(b)) (a,f(a)),(c,f(c)),(b,f(b)) 三个点求得二次插值多项式 φ ( x ) \varphi(x) φ(x) ,用 φ ( x ) \varphi(x) φ(x) 的最小值点 作为下一个近似值。
二次插值多项式知识点可以参考这篇博客。
设插值多项式为
φ ( x ) = β 0 + β 1 x + β 2 x 2 , \varphi(x)=\beta_0+\beta_1 x+\beta_2 x^2, φ(x)=β0+β1x+β2x2,
其中 β 2 > 0 \beta_2>0 β2>0, 在给定了 a < c < b a
β 2 = 1 b − a [ f ( b ) − f ( c ) b − c − f ( c ) − f ( a ) c − a ] , β 1 = f ( c ) − f ( a ) c − a − β 2 ( c + a ) , \begin{aligned} \beta_2 &=\frac{1}{b-a}\left[\frac{f(b)-f(c)}{b-c}-\frac{f(c)-f(a)}{c-a}\right], \\ \beta_1 &=\frac{f(c)-f(a)}{c-a}-\beta_2(c+a), \end{aligned} β2β1=b−a1[b−cf(b)−f(c)−c−af(c)−f(a)],=c−af(c)−f(a)−β2(c+a),
从而求得 φ ( ⋅ ) \varphi(\cdot) φ(⋅) 的最小值点
x ~ = − β 1 2 β 2 \tilde{x}=-\frac{\beta_1}{2 \beta_2} x~=−2β2β1
适当条件下抛物线法达到超线性收敛速度。
求解函数 f ( x ) = x 2 − 5 f(x)=x^2-5 f(x)=x2−5的最小值点。
//
// Created by CHH3213 on 2022/9/12.
//
#include
#include
/*
* 使用抛物线法求解 x*x-5的最小值点
*/
using namespace std;
#define DELTA 1e-4
#define EPS 1e-6
double func(double x){
/**
* 构造函数
* x:自变量
* return:函数值
*/
return x*x-5;
}
double quadratic_fit_search(double a,double b,double c,int n){
/**
* 抛物线法;
* 区间(a,b);
* a
while(n--){
double beta_1 = 1/(b-a)*((func(b)-func(c))/(b-c)-(func(c)- func(a))/(c-a));
double beta_2 = (func(c)-func(a))/(c-a)-beta_2*(c+a);
double x = -beta_1/(2*beta_2);
if(x<c){
b=c;
c=x;
}else{
a=c;
c=x;
}
}
return c;
}
int main(){
cout<<quadratic_fit_search(-3,3,1,10)<<endl;
return 0;
}
节 4.3.1 4.3.1 4.3.1 的算法 迭代不保证最小值点在拟合抛物线所用的三个点构成的区间内。下面是改进的做法。
对一元单谷函数 f ( x ) f(x) f(x) , 取三个点 a < b < c aa<b<c 使得 f ( a ) > f ( b ) f(a)>f(b) f(a)>f(b), f ( c ) > f ( b ) f(c)>f(b) f(c)>f(b) 。
在迭代过程中要求每一步产生的新的 a < b < c aa<b<c 都 满足 f ( a ) > f ( b ) , f ( c ) > f ( b ) f(a)>f(b), f(c)>f(b) f(a)>f(b),f(c)>f(b) , 即最小值一定在 ( a , c ) (a, c) (a,c) 内。我们可以直接利用Lagrange插值公式写出二次多项 式,并用导数等于零解出多项式的最小值点,可得从 a , b , c a, b, c a,b,c 计算下一个点的迭代公式:
x ∗ = 1 2 y a ( b 2 − c 2 ) + y b ( c 2 − a 2 ) + y c ( a 2 − b 2 ) y a ( b − c ) + y b ( c − a ) + y c ( a − b ) x^*=\frac{1}{2} \frac{y_a\left(b^2-c^2\right)+y_b\left(c^2-a^2\right)+y_c\left(a^2-b^2\right)}{y_a(b-c)+y_b(c-a)+y_c(a-b)} x∗=21ya(b−c)+yb(c−a)+yc(a−b)ya(b2−c2)+yb(c2−a2)+yc(a2−b2)
产生 x ∗ x^* x∗ 后,利用 a , b , c , x ∗ a, b, c, x^* a,b,c,x∗ 这四个点中找到含最小值的缩小的区间并仍用 a , b , c a, b, c a,b,c 三个点表示。
求解函数 f ( x ) = x 2 − 5 f(x)=x^2-5 f(x)=x2−5的最小值点。
//
// Created by CHH3213 on 2022/9/12.
//
#include
#include
/*
* 使用抛物线法求解 x*x-5的最小值点
*/
using namespace std;
#define DELTA 1e-4
#define EPS 1e-6
double func(double x){
/**
* 构造函数
* x:自变量
* return:函数值
*/
return x*x-5;
}
double quadratic_fit_search_2(double a,double b,double c,int n){
/**
* 抛物线法;
* 区间(a,c);
* a
while(n--){
double y_a=func(a),y_b=func(b),y_c=func(c);
double x = 0.5*(y_a*(b*b-c*c)+y_b*(c*c-a*a)+y_c*(a*a-b*b))/(y_a*(b-c)+y_b*(c-a)+y_c*(a-b));
double y_x = func(x);
if(x>b){
if(y_x>y_b){ //用 a < b < x 作为新的起点
c=x;
}else{ //用 b < x < c作为新的起点
a=b;
b=x;
}
}else if(x<b){
if(y_x>y_b){ //用 x < b < c作为新的起点
a=x;
}else{ //用 a < x < b作为新的起点
c=b;
b=x;
}
}
}
return b;
}
int main(){
cout<<quadratic_fit_search_2(-2,2,3,50)<<endl;
return 0;
}
同样地,如果要求方程的根,构造损失函数,求损失函数的最小值点就是在求方程的根。