# 知识点学习——模拟退火算法

模拟退火算法

打算补一下上一把牛客比赛的K题Solar Energy,需要模拟退火知识点的储备。打算速通以后出出看K题。

参考资料

感谢在网上分享学习心得的各位同学。
模拟退火学习笔记1
模拟退火学习笔记2
模拟退火学习笔记3

正式阐述

命名来源:

模拟退火算法来源于固体退火原理,是一种基于概率的算法,将固体加温至充分高,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小。

退火思想引入到组合优化领域。它是基于Monte-Carlo迭代求解策略的一种随机寻优算法,其出发点是基于物理中固体物质的退火过程与一般组合优化问题之间的相似性。模拟退火算法从某一较高初温出发,伴随温度参数的不断下降,结合概率突跳特性在解空间中随机寻找目标函数的全局最优解,即在局部最优解能概率性地跳出并最终趋于全局最优。
模拟退火算法是一种通用的优化算法,理论上算法具有概率的全局优化性能,目前已在工程中得到了广泛应用,诸如VLSI、生产调度、控制工程、机器学习、神经网络、信号处理等领域。
模拟退火算法是通过赋予搜索过程一种时变且最终趋于零的概率突跳性,从而可有效避免陷入局部极小并最终趋于全局最优的串行结构的优化算法。
得到解组合优化问题的模拟退火算法:由初始解i和控制参数初值t开始,对当前解重复“产生新解→计算目标函数差→接受或舍弃”的迭代,并逐步衰减t值,算法终止时的当前解即为所得近似最优解,这是基于蒙特卡罗迭代求解法的一种启发式随机搜索过程。退火过程由冷却进度表(Cooling Schedule)控制,包括控制参数的初值t及其衰减因子Δt、每个t值时的迭代次数L和停止条件S。

以上资料来源于百度百科。
重点已经用加粗显示。

概率性跳出,有效避免陷入局部极小值并最终趋向于全局最小值。

适用问题:

总而言之,模拟退火SA是玄学的随机化算法。
当一个问题的方案数量极大(甚至是无穷时),而且不是一个单峰函数极值不唯一时,我们常使用模拟退火求解。

计算过程:

在这里插入图片描述
以上截图自百度百科,说实话我没咋看明白。
大概意思就是先找到一个初始解(局部最优解),然后跳出去寻找,有概率找到最优解。
显然算法是否能AC出最优解与初始解的位置以及搜寻的附近解的区域大小有关。
如果 Δ t 设置的比较大(左右横跳一次的半径)的话有几率跳出去,但是太大了又可能跳来跳去从而找不到最优解。
emmm,总而言之?看命!

著名动图:
在这里插入图片描述

公式:

  • 第一步!
    ​ 程序开始时,我们先要 srand(一个常数)。这个常数可以决定分数,可以使用 233333,2147483647 等等。
  • 继续调试!
    模拟退火一次往往跑不出最优解,常数一换,又可以冲冲冲!
    每次看到一片红wa都要感叹怎么不是股票是股票就好了。
    可以用一个全局变量记录所有跑过的 SA 的最优解,每次从哪个最优解开始继续 SA,可以减少误差。
    这步好像比较进阶,待我细学一下!
  • 时间复杂度
    主要看脸。
    这是算法竞赛界的抽卡。

如何生成新解

坐标系内:随机生成一个点,或者生成一个向量。 序列问题: random_shuffle 或者随机交换两个数。
网格问题:可以看做二维序列,每次交换两个格子即可。

T(t) = T0/log10(1+t)这个叫经典模拟退火
T(t) = T0/(t+1)这个叫快速模拟退火

一些技巧
  1. 分块模拟退火
    有时函数的峰很多,模拟退火难以跑出最优解。
    此时可以把整个值域分成几段,每段跑一遍模拟退火,然后再取最优解。

  2. 卡时
    有一个 clock() 函数,返回程序运行时间。
    可以把主程序中的 simulateAnneal(); 换成 while((double)clock()/CLOCKS_PER_SEC < MAX_TIME) simulateAnneal();
    这样子就会一直跑模拟退火,直到用时即将超过时间限制。
    这里的 MAX_TIME是一个自定义的略小于时限的数。

在这里插入图片描述

板子:

伪代码:

/*
* J(y):在状态y时的评价函数值
* Y(i):表示当前状态
* Y(i+1):表示新的状态
* r: 用于控制降温的快慢
* T: 系统的温度,T0——>正无穷
* T_min :温度的下限,若温度T达到T_min,则停止搜索
*/
while( T > T_min )
{
  dE = J( Y(i+1) ) - J( Y(i) ) ;

  if ( dE >=0 ) //表达移动后得到更优解,则总是接受移动
Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
  else
  {
// 函数exp( dE/T )的取值范围是(0,1) ,dE/T越大,则exp( dE/T )也越大
if ( exp( dE/T ) > random( 0 , 1 ) )
Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
  }
  T = r * T ; //降温退火 ,0<r<1 。r越大,降温越慢;r越小,降温越快
  /*
  * 如果 r 设置的比较大的话有几率跳出去,但是太大了又可能跳来跳去从而找不到最优解。
  */
  i ++ ;
}

例题1:给出平面内的一些点,求在一个矩形的范围内找到一个点,使得这个点到其他所有点的最小距离最大(费马点)。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define EPS 1e-3
#define INF 1e12
#define MAX 10000
using namespace std;
 
struct Point{
	double x,y;
	double length;
	Point(double _x,double _y):x(_x),y(_y) {}
	Point() {}
}point[MAX],now,ans;
 
int cases;
double m,n;
int cnt;
 
double dE;
 
inline double Calc(Point p1,Point p2);
inline void Initialize();
inline void SA();
inline double Rand();
inline double Statistic(Point p);
 
int main()
{
	srand(19970804);
	for(cin >> cases;cases; --cases) {
		scanf("%lf%lf%d",&m,&n,&cnt);
		for(int i = 1;i <= cnt; ++i)
			scanf("%lf%lf",&point[i].x,&point[i].y);
		Initialize();
		SA();
		printf("The safest point is (%.1lf, %.1lf).\n",ans.x,ans.y);
	}
	return 0;
}
 
inline double Calc(Point p1,Point p2)
{
	return sqrt((p1.x - p2.x) * (p1.x - p2.x) + 
				(p1.y - p2.y) * (p1.y - p2.y));
}
 
inline void Initialize()
{
	now = Point(m * Rand(),n * Rand());
	ans.length = 0.0;
}
 
inline void SA()
{
	double T = sqrt(m * m + n * n) * 0.5;
	for(;T > EPS;T *= 0.998) {
		double alpha = 2.0 * acos(-1.0) * Rand();
		Point temp(now.x + T * cos(alpha) * Rand(),now.y + T * sin(alpha) * Rand());
		if(temp.x < 0 || temp.y < 0 || temp.x > m || temp.y > n)
			continue;
		dE = Statistic(temp) - Statistic(now);
		if(dE >= 0 || exp(dE / T) >= Rand())
			now = temp;
	}
	T = 0.5;
	for(int i = 1;i <= 500; ++i) {
		double alpha = 2.0 * acos(-1.0) * Rand();
		Point temp(ans.x + T * cos(alpha) * Rand(),ans.y + T * sin(alpha) * Rand());
		if(temp.x < 0 || temp.y < 0 || temp.x > m || temp.y > n)
			continue;
		Statistic(temp);
	}
}
 
inline double Rand()
{
	return (rand() % 1000 + 1) / 1000.0;
}
 
inline double Statistic(Point p)
{
	double re = INF;
	for(int i = 1;i <= cnt; ++i)
		re = min(re,Calc(point[i],p));
	if(re > ans.length)
		ans = p,ans.length = re;
	return re;
}

例题二:求序列最小值

宝藏

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <iostream>
using namespace std;
const int maxn = 16;
int g[maxn][maxn], n, m;
#define INF 0x3f3f3f3f
struct Node {
    int d[maxn], deep[maxn];

    Node() {
        for (int i = 1; i <= n; i++)
            d[i] = i, deep[i] = 0;
    }

    Node(const Node &rhs) {
        memcpy(d, rhs.d, sizeof(d));
        memset(deep, 0, sizeof(deep));
    }

    Node operator=(const Node &rhs) {
        memcpy(d, rhs.d, sizeof(d));
        memset(deep, 0, sizeof(deep));
        return *this;
    }

    inline int solve() {//按照打通顺序求代价
        int ret = 0;
        deep[d[1]] = 1;//第一个打通的节点深度为1
        for (int i = 2; i <= n; i++) {
            int temp = 0x7fffffff;
            for (int j = 1; j < i; j++) {//枚举由哪一个已经打通的节点打通道路
                if (g[d[i]][d[j]] != 0x3f3f3f3f && deep[d[j]] * g[d[i]][d[j]] < temp)
                    temp = deep[d[j]] * g[d[i]][d[j]], deep[d[i]] = deep[d[j]] + 1;
            }
            if (temp == 0x7fffffff)return 0x7fffffff;//当前方案不可行可以提前退出了
            ret += temp;
        }
        return ret;
    }
};

int SA() {
    //SA
    const double max_temp = 10000.0;//初始温度
    const double delta_temp = 0.98;//降温系数
    double temp = max_temp;//当前温度
    Node path;//打通顺序
    while (temp > 0.1) {
        Node tpath(path);
        swap(tpath.d[rand() % n + 1], tpath.d[rand() % n + 1]);//随机一个新解
        double delta = tpath.solve() - path.solve();//求解
        if (delta < 0)path = tpath;//如果新解更优,则接受
        else if (exp(-delta / temp) * RAND_MAX >= rand())path = tpath;//否则以一定概率接受
        temp *= delta_temp;
    }
    return path.solve();
}

int main() {
    srand(rand());
    memset(g, 0x3f, sizeof(g));
    cin >> n >> m;
    for (int u, v, d, i = 1; i <= m; i++) {
       cin>>u>>v>>d;
        g[u][v] = min(g[u][v], d);
        g[v][u] = min(g[v][u], d);
    }
    int ans = INF;
    for (int i = 1; i <= 300; i++)
        //跑SA,取最优值
        ans = min(ans, SA());
    printf("%d\n", ans);
    return 0;
}

板子

int SA() {
    //SA
    const double max_temp = 10000.0;//初始温度
    const double delta_temp = 0.98;//降温系数
    double temp = max_temp;//当前温度
    Node path;//打通顺序
    while (temp > 0.1) {
        Node tpath(path);
        swap(tpath.d[rand() % n + 1], tpath.d[rand() % n + 1]);//随机一个新解
        double delta = tpath.solve() - path.solve();//求解
        if (delta < 0)path = tpath;//如果新解更优,则接受
        else if (exp(-delta / temp) * RAND_MAX >= rand())path = tpath;//否则以一定概率接受
        temp *= delta_temp;
    }
    return path.solve();
}


接口

    srand(rand());
   for (int i = 1; i <= 300; i++)
        //跑SA,取最优值
        ans = min(ans, SA());

例题三:求经典多项式最值

Hdu 2899
给出y , 求在这里插入图片描述
的最小值

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>

using namespace std;

const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e4 + 5;
double n, x, y;
double now;
double ans;

double f(double x) {
    //评估函数,计算.
    return 6.0 * pow(x, 7) + 8.0 * pow(x, 6) + 7.0 * pow(x, 3) + 5.0 * pow(x, 2) - y * x;
}


double sa() {
    double next_next;
    ans = inf;
    //最开始的能量值,需要非常大
    double T = 100.0;
    //初始温度
    //(可以适当修改,最好和给的数据最大范围相同,或者缩小其原来0.1)
    double d = 0.9765432;
    //降温系数
    // (可以适当修改,影响结果的精度和循环的次数,)
    double eps = 1e-12;
    //最终温度
    // (要是因为精度问题,可以适当减小最终温度)
    double TT = 0.99;
    //采纳新解的初始概率
    double dd = 0.99;
    //(可以适当修改,采纳新解变更的概率)
    // (这个概率下面新解更新的时候,最好和未采纳的新解更新的次数是一半一半)
    double res = f(now);
    //传入的初始默认解(now)下得到的评估能量值
    if (res < ans)
        ans = res;
    int num = 1;
    while (T > eps) {
        for (int i = -1; i <= 1; ++i)
            if (now + i * T <= 100 && now + T * i >= 0) {
                next_next = now + T * i;//新解
                double tmp = f(next_next);//新解下的评估能量值
                if (tmp < ans) ans = tmp;//降温成功,更新当前最优解

                if (tmp < res) res = tmp, now = next_next;// 降温成功,采纳新解
                else if (TT > rand() % 10000 / 10000.0) {
                    res = tmp;
                    now = next_next;
                }
                //没有降温成功,但是以一定的概率采纳新解
                // 用于测试,设定的采纳新解的概率,是否为一半一半,可以适当修改降温参数dd
            }
        T *= d;
        TT *= dd;
    }
    return ans;
}

int main() {
    srand(114514);
    cin>>n;
    while (n--) {
        cin >> y;
        now = 50.0;
        sa();
        cout << ans << fixed << setprecision(4) << endl;
    }
    return 0;
}
 

小结:

做了题目感觉靠自己理解就好,写就完事了。
这几次都蛮欧的,虽然是随机化算法但还是一次就A了。
这算法我学两三天了。
一会就冲一下K.Solar Energy看看!好耶!

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值