函数极值问题求解——C++实现

对于一个简单函数来说,例如在这里插入图片描述
,它的图像存在最大值和最小值,那么给定一个区间我们可以利用以下几种方法进行求解。(0<=x<=100,y的值会给出)

方法1:二分法求解

假定存在极小值在区间内,那么函数一定是先减后增,利用函数fx求解函数值,函数fdx则为fx函数的导数,利用导数我们可以找到该函数fx的极小值。

double fx(double x,double y)
{
	return 7*pow(x,7)+6*pow(x,6)+2*pow(x,3)+8*pow(x,2)-y*x;
} 
double fdx(double x,double y)
{
	return 49*pow(x,6)+36*pow(x,5)+6*pow(x,2)+16*x-y; 
}

二分法求解,设置left为左边界,right为右边界,则解一定位于left和right之间,当左右边界之间的差值小于某一精确度时,就认为找到了解。具体操作如下,首先判断左右边界差值作为循环条件,接着每次循环找到左右边界的中间值,一旦该点导数值小于0,说明该点的右侧存在正确解,则将左边界设置为该点,反之将右边界缩进,左右边界通过循环不断的向正确解缩进。满足某一精确度后即可输出结果。(注意这里的函数是比较常规简单的单调函数

double left=0.0,right=100.0;
int count=0;
double preci=1e-6;//设置精确度
while(right-left>preci) //若超时,在保证精度的情况下,可以改成count++<100
{
	double mid =(left+right)/2.0;
	if(fdx(mid,y)<0)
	{
		left=mid;
	}
	else
	{
		right = mid;
	}
}
方法2:牛顿二次迭代

同样是对常规函数求解,只是牛顿二次迭代可以更快的求得结果,即便结果有些许相差,但是在经过100次以上的迭代后,答案已经很精确了。对于二次迭代,需要对函数进行二次求导,得到一次求导后各点的斜率。

double fddx(double x)
{
    return 294*pow(x,5)+180*pow(x,4)+12*x+16;
}

首先设置一个边界值,对于该边界值一阶导数的值进行判断,判断是否接近0点,或者在进行两百次循环后退出。接下来,从右边界进行缩进,对于该点进行二阶求导,在该点做一条函数切线,与x轴相交的点相比于原边界值一定更接近正确解。具体写法如下,求一阶导的函数值,该函数值除以斜率(也就是该点二阶导的值)即为缩进的x距离,在原有边界上减去该值,即向正确解缩进。

double preci=1e-8;//设置精确度
double ans=100;
int count=0;
while(fdx(ans,y)>preci&&count++<200)
{
	ans = ans-(fdx(ans,y)/fddx(ans));
}
方法3:三分法(使用于区间内单个峰值)

函数在某个区间内存在单个峰值,可以利用求导公式计算得到该峰值的计算结果,对于部分函数求导比较麻烦,所以采用三分法求函数在某个区间内的峰值(仅适用于单峰,多峰需采用方法4模拟退火)

void cal(int x)
{
	//TODO 计算函数
}
double l=0,r=100;
int count=0;
double eps=1e-6;//设置精确度
while(r-l>eps)
{
	double mid =(l+r)/2.0;
	double midr = (mid+r)/2.0;//这里取的是右半区间,也可以取左半区间
	if(cal(mid)>cal(midr)) //判断方法,若所求峰值为最大,则mid大于其他
	{
		r=midr;
	}
	else
	{
		l= mid;
	}
}
方法4:模拟退火算法(适用于区间内多个峰值求最值)

在对于复杂函数进行求解时,以上三种方法,只能求得局部最优的解。利用模拟退火算法才可以得到全局最优解。
具体原理很复杂还不太理解,代码实现如下

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <vector>
#include <set>
#include <stack>
#include <queue>
#include <cstdlib> //srand,rand,RAND_MAX
#include <ctime> //time
using namespace std;
typedef long long ll;
int num,mark,m,n,t,maxlen,start,end;
#define sc(n) scanf("%d",&n)
double fx(double x,double y)
{
	return 7*pow(x,7)+6*pow(x,6)+2*pow(x,3)+8*pow(x,2)-y*x;
}
double randouble()
{
	return (rand()%10001)/10000.0;//得到小数随机数技巧
}
int main()
{
		srand((unsigned int)time(NULL));//初始化伪随机数生成器
		double y;
		scanf("%lf",&y);
		double preci=1e-8;
		double t=100;//火的温度
		double delta=0.98;//降温系数,降温系数与代码的时间复杂度相关,同时会影响解的精确度
		double x=randouble()*100.0;
		int count=10;
		double ans=999999999;
		while(t>preci)
		{
			for(int k=1;k<=count;k++)
			{
				double nowfx=fx(x,y);
				double xnew=x+(randouble()*2-1)*t;//从【-1,1】之间得到小数
				if(xnew>=0&&xnew<=100)
				{
					double newfx=fx(xnew,y);
					double detay=newfx-nowfx;
					if(detay<0)
					{
						x=xnew;
					}
					else
					{
						double p=pow(exp(1),-(detay)/t);
						if(randouble()<p)
						{
							x=xnew;
						}
					}
				}
			}
			t=t*delta;
			ans=min(ans,fx(x,y));
		}
		printf("%.4f\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值