【codevs 1344】线型网络&模拟退火详解

本文介绍了模拟退火算法的基本原理及应用,通过对比穷举法、爬山算法等方法,阐述了模拟退火算法如何避免陷入局部最优解,并提供了一个具体的实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下都是些玄学算法,之后慢慢搞。

引入

现在假设给定一个函数,求最大值。
穷举法:直接搜索,但是效率低下。
爬山算法:每次朝着更高的地方走,走到最高点为止。但是会出现走到极大值但走不到最大值的情况。
模拟退火算法:每次朝着更高的地方走,但是“有一定概率朝着更低的地方走”,相比较爬山算法而言,模拟退火算法的改进就是尽量跳出极大值的局限。
遗传算法:和生物学中的遗传很像,初始给定一些个体,就是初始情况,个体之间的基因交换理解为一个新的方案。随着时间的推移,那些较差的个体(较差的解)逐渐被淘汰,剩下的就是较优的个体(较优的解)。
蚁群算法:一开始所有的搜索方案都是平等的,随着搜索的进行,有些路径会更优,这时候优先搜索这些较优的路径。

下面的题就是用模拟退火算法来解决。

金属冶炼的时候金属温度越高,金属分子间运动越剧烈。同样在模拟退火中,一开始的时候温度最高,接受较差解的概率越大;随着搜索的进行,温度不断降低,接受较差解的概率越小。可以这样理解,爬山算法是直接朝着高处走,而模拟退火是一开始乱跳,跳了几次之后就不再乱跳,而是也朝着最高处走,这样就能走出极大值的局限了。

就拿本题开刀了。
首先,我们要判断这个“接受较差解的概率”到底有多大。
P(dE)=edET
设当前方案和之前方案的能量变化量为dE(也就是本题中,两种方案的差)。如果dE小于等于0,说明是一个较优解,则一定接受;如果dE大于0,说明是一个较差解,所以这个概率P就能从0取到1。其中T表示当前的温度,温度越高,概率也越高。

bool accept(double delta,double tmp)
{
    if (delta <= 0) return true;
    return rand() % max_rand <= exp((-delta)/tmp) * max_rand;
}

下面是搜索的过程,将这个函数多运行几次,找出的最优解就是答案了。

double solve()
{
    int i; double dis1,dis2;
    double res = 100000000.0;
    const double max_tmp = 100000;//初始温度
    const double dec = 0.999;//每次降低温度的比率
    double tmp = max_tmp;
    fo(i,1,n) p[i] = i; calc(p,dis1);
    while (tmp > 0.01)
        {
            drunk(); calc(pp,dis2);//随机交换两个点,表示一种新的情况
            if (accept(dis2-dis1,tmp))
                {fo(i,1,n) p[i] = pp[i]; dis1 = dis2;}
            tmp *= dec;//降温
            if (dis1 < res) res = dis1;
        }
    return res;
}

下面是完整程序,一般来说:
1、T大10倍,时间复杂度也大10倍;
2、dec后面多一个9,时间复杂度大10倍;
3、初温大10倍,时间复杂度大20%左右。

#include<cmath>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1000000000
#define mod 1000000007
#define N 30
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int n,i,j,T;
int cnt;
int pp[N],p[N],x[N],y[N];
double dis[N][N];
const int max_rand = 2333333;
void drunk()
{
    int i,x,y;
    fo(i,1,n) pp[i] = p[i];
    x = rand() % n + 1; y = rand() % n + 1;
    swap(pp[x],pp[y]);
}
void calc(int *p,double &s)
{
    int i; s = 0;
    fo(i,1,n-1) s += dis[p[i]][p[i+1]];
}
bool accept(double delta,double tmp)
{
    if (delta <= 0) return true;
    return rand() % max_rand <= exp((-delta)/tmp) * max_rand;
}
double solve()
{
    int i; double dis1,dis2;
    double res = 100000000.0;
    const double max_tmp = 100000;
    const double dec = 0.999;
    double tmp = max_tmp;
    fo(i,1,n) p[i] = i; calc(p,dis1);
    while (tmp > 0.01)
        {
            drunk(); calc(pp,dis2);
            if (accept(dis2-dis1,tmp))
                {fo(i,1,n) p[i] = pp[i]; dis1 = dis2;}
            tmp *= dec;
            cnt++;
            if (dis1 < res) res = dis1;
        }
    return res;
}

int main()
{
    cnt = 0;
    srand(233333);
    scanf("%d",&n);
    fo(i,1,n) scanf("%d%d",&x[i],&y[i]);
    fo(i,1,n) fo(j,1,n) dis[i][j] = dis[j][i] = hypot(x[i]-x[j],y[i]-y[j]);
    double ans = 1000000000.0; T = 150;
    while (T--) ans = min(ans,solve());
    printf("%.2lf\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值