模拟退huo算法的特点_模拟退火算法 & BZOJ3680

模拟退火算法 & BZOJ3680

0x00 一本正经的胡说八道

模拟退火算法是个很好玩儿的算法,而且打起来也很方便,没有学习这个算法之前,我对于搜索的理解大概有以下几种:BFS & DFS

分别以栈、队列形式模拟的搜索方式,几乎是所有搜索方式的框架所在,但是一旦搜索扩展状态增多,几乎鲜有可以使用纯暴力搜索实现的情况(DFS在完全扩展状态下是指数级别的复杂度,BFS遇到大的状态爆队列是妥妥的),一般来讲都要通过剪枝来进行优化。爬山算法

取得局部最优解的方法,每次搜索时都选择扩展状态中最优的那一个,当发现一个状态比其扩展状态都优的时候就停止搜索,并输出该状态。由于ACM中的问题一般都不会像这种搜索方法一样目光短浅,所以可使用的场所一般很少。记忆化搜索

原理和动态规划一样,要领在于归纳出状态并保存,并重复利用已经保存的状态,对于没有历史记录的状态分解并继续搜索(其实动态规划就是从最基础的状态向后推,记忆化搜索就是从最后的状态往前递归)。对于整理出动态规划的状态转移方程很有用,所以使用途径也非常广。IDA* & A*

区别在于是否用迭代加深实现,都是对于子状态进行权重判断,并优先选择权重最高的子状态进行搜索。(对于八数码,还原魔方,K短路这种问题就是神技)。

这些算法都是不盲目的搜索,巧妙的搜索,智慧的搜索,对于目标有着明确的规划。

但在旅行商问题面前,统统20块,统统20块,统统20块。有一个推销员,要到n个城市推销商品,他要找出一个包含所有n个城市的具有最短路程的环路。

懵逼了吧,启发函数怎么写,局部最优在哪里,状态怎么保存??

这时候就来感受一发

equation?tex=Random(模拟退火算法)的伟大吧。

随机这种东西,貌似在ACM里好像就是用于骗分……在我们机房里,曾经拥有用

equation?tex=RandomAC题目的传奇级人物Ran神,但其实

equation?tex=Random有更多实际的用处(用

equation?tex=rand%28%29代替

equation?tex=%28l%2Br%29%2F2防止快排退化,用

equation?tex=rand%28%29构建Treap树保持二叉搜索树的平衡等等),还有就是模拟退火算法,也算是随机化算法中一个极好的例子吧。对不加约束的随机有了收敛,就像不加约束的野马有了缰绳 —— 沃·兹吉硕德

0x01 算法思想

在讲模拟退火算法的时候,先将一个小明爬山的故事,这是这篇文章:大白话解析模拟退火算法给我的极大启发,之前总是被热力学的一堆名词搞得一脸懵逼。

有一天小明同学去爬山

小明左看右看,看到右边有一个山峰,小明很开心,这就是我的目标!

小明爬啊爬,爬到了山顶A。

小明再次左看右看,虽然右边有个更高的山头B,但是小明已经认为自己爬到了最高的地方,小明很开心,小明回家了。

-------------

equation?tex=Day2分割线-------------

第二天小明喝醉了酒,继续来爬这座山,由于小明头脑不清醒,他开始随机的乱走,有可能在去山顶的路上。也有可能在下山的过程中。

一开始小明醉的找不着北,步伐简直像是随机乱跳,好、但后来酒渐渐消去,小明最终意识到了山顶的存在,到达了山顶。

喝醉的小明和普通的小明其实就是爬山算法和模拟退火算法的区别,爬山算法不允许出现出现解比原来不优的情况,而模拟退火算法允许在一定的概率内出现比当前状态不优的状态,也正是这种随机,使得搜索从一种坐井观天的状态中跳出,找到真正是全局最优的解。(难道喝醉更好吗23333)

equation?tex=%5Clambda+%28S+%29表示对于当前状态的估价,

equation?tex=%5Clambda+%28S%5Crightarrow+%29表示对下一个状态的估价。当

equation?tex=%5Clambda+%28S%5Crightarrow+%29+%5Cgeq+%5Clambda+%28S+%29时,这自然是最好的,这种转移是允许的。

equation?tex=%5Clambda+%28S%5Crightarrow+%29+%3C+%5Clambda+%28S+%29,但是有一个奇异的状态产生时,这种转移也会允许(但是产生这种奇异状态的概率会越来越小,毕竟小明越来越清醒了)。

而这个奇异状态的出现,来自于热力学的公式

equation?tex=P%28dE%29%3Dexp%28dE%2FkT%29,也就是说温度

equation?tex=kT(一开始这个温度为充分大)越高,出现温差值

equation?tex=dE为降温的概率就越大,反之就越小,而这个温差来自于

equation?tex=%5Clambda+%28S%5Crightarrow+%29

equation?tex=%5Clambda+%28S+%29两个状态之间的差值,由于这个差值小于0,温度

equation?tex=kT大于0,所以

equation?tex=P%28dE%29%5Cin+%5Cleft%28+0%2C1+%5Cright%29+

温度

equation?tex=kT一直一步步降低,结果也一步步趋于稳定,当

equation?tex=kT%3CEPS时,结束搜索,输出结果。

0x03 伪代码

while(T > T_MIN) {// 温度还可以降温dE = S(NEXT) - S(NOW);

if(dE >= 0 || exp(dE / T) > random(0, 1)) // state 1 or 2S(NOW) = S(NEXT);

T *= c; // 降温的过程}

框架很简单,效率很美好。

0x04 例题gty又虐了一场比赛,被虐的蒟蒻们决定吊打gty。gty见大势不好机智的分出了n个分身,但还是被人多势众的蒟蒻抓住了。蒟蒻们将n个gty吊在n根绳子上,每根绳子穿过天台的一个洞。这n根绳子有一个公共的绳结x。吊好gty后蒟蒻们发现由于每个gty重力不同,绳结x在移动。蒟蒻wangxz脑洞大开的决定计算出x最后停留处的坐标,由于他太弱了决定向你求助。

不计摩擦,不计能量损失,由于gty足够矮所以不会掉到地上。

Input

输入第一行为一个正整数n(1<=n<=10000),表示gty的数目。

接下来n行,每行三个整数xi,yi,wi,表示第i个gty的横坐标,纵坐标和重力。

对于20%的数据,gty排列成一条直线。

对于50%的数据,1<=n<=1000。

对于100%的数据,1<=n<=10000,-100000<=xi,yi<=100000

Output

输出1行两个浮点数(保留到小数点后3位),表示最终x的横、纵坐标。

Sample Input

3

0 0 1

0 2 1

1 1 1

Sample Output

0.577 1.000问题其实就是找一个点

equation?tex=%5Calpha+,使得

equation?tex=%5Csum_%7Bi%3D1%7D%5E%7BN%7D%7Bdist%28%5Calpha+%2Ci%29+%2A+W_%7Bi%7D+%7D+最小,使用模拟退火算法即可。

0x05 代码

#include #include #include #include

#define INF (1e17)#define EPS (1e-3)#define PI (acos(-1.0))#define FIRE(x) (x *= 0.99)using namespace std;

const int MAXN = 10000 + 10;

int N;

double total = INF;

struct Point {

double x, y, w;

Point (double _x, double _y) : x(_x), y(_y) {}

Point (void) {}

void Read(void) {

scanf("%lf%lf%lf", &x, &y, &w);

}

void operator += (Point t) {

x += t.x; y += t.y;

}

void operator /= (int N) {

x /= N, y /= N;

}

};

Point now, ans, point[MAXN];

inline double Dist(Point a, Point b) {

return sqrt((a.x - b.x) * (a.x - b.x) +

(a.y - b.y) * (a.y - b.y));

}

inline double Statistic(Point p) {

double res = 0.0;

for (int i = 0; i < N; i++) res += Dist(p, point[i]) * point[i].w;

if (res < total) total = res, ans = p;

return res;

}

inline double Rand(void) {

return (rand() % 1000 + 1) / 1000.0;

}

int main(void) {

srand(10086);

scanf("%d", &N);

register int i;

for (i = 0; i < N; i++) point[i].Read(), now += point[i];

now /= N;

double T = 100000.0, alpha, sub;

while (T > EPS) {

alpha = 2.0 * PI * Rand();

Point tmp(now.x + T * cos(alpha), now.y + T * sin(alpha));

sub = Statistic(now) - Statistic(tmp);

if(sub >= 0 || exp(sub / T) >= Rand()) now = tmp;

FIRE(T);

}

T = 0.001;

for (i = 1; i <= 1000; ++i) {

alpha = 2.0 * PI * Rand();

Point tmp(ans.x + T * cos(alpha) * Rand(), ans.y + T * sin(alpha) * Rand());

Statistic(tmp);

}

printf("%.3lf %.3lf\n", ans.x, ans.y);

return 0;

}效率如下图。

为什么效率变化会这么快,其实是伪代码中降温率c取值的原因,降温率越慢,速度越慢(但结果越精确),反之速度越快,但就有可能像那个WA一样降温过快,还没找到最优解温度已经趋于了稳定,所以实际应用时不建议这个值设置得过小。

0x07 完。

但顺便说一句,BZOJ, ZOJ, POJ(此处原文为各大OJ网站,由@dashgua@PeterWang 指出不严谨,谢谢)都不允许使用time()函数(防止出现漏洞),因此用srand()初始化时应该用一个常数。

0x08 其实还没有完

我的专栏说是讲讲算法(实际上也就是讲讲算法),不过是把我自己的学习过程和灵感和大家一起分享,希望大家对算法有新的理解,或者不理解的能够通过我的专栏慢慢理解。所以可能很多内容大家之前已有涉猎。深知这些的ACM大犇就当是看一个ACMer的成长吧。当然,别忘了提出您宝贵的建议和点一个赞喔、

真的完了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值