浅谈欧洲算法——模拟退火

初听说退火这个名词感觉就很(zhuang)帅(A__CDEFG...)

直到学了退火之后,我才发现:

退火不只是帅,而且非常万能

甚至比 D (大) F (法) S (师)还要万能

简直就是骗(de)分神器啊

简介

作为一个计算机算法,它竟然在百度上有物理词条!

当时我看了就懵了,你说计算机一个算法,跟冶炼金属有什么关系啊?

后来我看了算法的词条...

是不是更懵了...

方便大家理解(变得更懵),我搬了百度上的定义:



Simulate Anneal Arithmetic (SAA,模拟退火算法)
根据Metropolis准则,粒子在温度T时趋于平衡的概率为e-ΔE/(kT),其中e为温度T时
的内能,ΔE为其改变量,k为Boltzmann常数。用固体退火模拟组合优化问题,将内能E模拟为目标函数值f,温度T演化成控制参数t,即得到解组合优化问题的模拟退火算法:由初始解i和控制参数初值t开始,对当前解重复“产生新解→计算目标函数差→接受或舍弃”的迭代,并逐步衰减t值,算法终止时的当前解即为所得近似最优解,这是基于蒙特卡罗迭代求解法的一种启发式随机搜索过程。退火过程由冷却进度表(Cooling Schedule)控制,包括控制参数的初值t及其衰减因子Δt、每个t值时的迭代次数L和停止条件S。

(懒的话不看也罢,本来就是拿来凑字数的)



说实话看完还是挺清楚(?)的,总计一下就是:

通过不断产生随机新解,并且比较新解与目前最优答案的差距大小;若新解较当今最有更优,就在新解的附近再产生下一个新解。通过大量的循环,从而达到引导答案最终趋向于标准最优解的目的。

稍微有点感觉了吧。

再说简单一点就是:

随便乱蒙答案,如果感觉蒙的比较好,就在它附近继续蒙

只要你蒙的足够多,你总能蒙到正确答案

...

(其实好像跟金属退火还没啥关系,没事 右上角洛谷日报2018: 浅谈玄学算法——退火 接着往下看)

->continue

实现过程

既然是退火,那就得有火吧。

嗯...

对不起

这个还真没火(打开电脑主机,你会在你的CPU上看到火

在计算机里面,退火过程主要用的是三个参数:初始温度T , 每次退火后温度变化量delta , 结束温度t 。其中a是一个略小于1的数。

说明一下这三个变量的用途吧,其实了解了用途之后应该也就能懂退火的原理了。

初始温度T:

这个主要决定的是我们随机蒙答案的范围。T越大,我们的随机答案就会分布地越广。主要保证的是算法的答案不会因为数据的偏移(过大或过小)而漏掉可能的正解。

只要是数据,T就一视同仁 。

结束温度t :

这个决定我们算法最后的精度。t越小,我们最后生成随机答案的范围就会越小。主要防止因为随机范围过大而造成的大量无意义的随机,能够保证我们最后的答案与标准答案相差无几(基本会被测试点无视或超出输出要求的精度),举个形象的栗子,我们t一般取的是1e-15(10的-15次方)。这里答案即使与标准答案有差,又有拿到题目会让你输出这种精度呢?

变化量delta(我不会打那个数学符号...):

T让我们随机的范围要广,t又说会把随机定到一个很小的范围。那到底随机随什么啊!

这个时候delta就出现了,它的作用就是在每次随机后让温度减小那么一点点(真的只是一点点)。让我们的随机从开始追求广度的T逐渐变化成强调细节的t。说白了就是让我们的退火既有T的广度,不会漏答案;又有t的精度。

看都这里可能又有人会懵了。你随机既要枚举广度,后面又要缩小范围。我咋知道所的范围是不是对的。没准你在错的答案附近随机蒙半天呢?

这个问题的答案其实就是退火算法的精髓:

对于随机的每一个答案:如果它比当前最优解更优,则接受它为当前最优解;若不是,则以一个跟当前温度有关的概率接受它。

->continue

理解

第一个人疑问可能所有看到这里的人都会问。

Q: 在随机时如果产生了一个并不是当前最优的新解,为什么不是直接开始下一轮的随机,而是以一个跟当前温度有关的概率接受它呢?

嗯...这个其实根本没法解释 挺好解释的。因为大家都知道,当前最优其实并不代表整体最优。因此我们在随机产生新解时,即使这个解是当前最优,我们仍然无法保证它会是整体最优。而我们每次枚举的范围是跟t相关的,所以其实开始并不是枚举所有可能的解;而是枚举一个范围超级大的解(在当前最优的基础上上下浮动一个关于t的函数)。而标准最优解可能与当前最优解位置或是数量相差较大。大到一次随机也无法达到那个范围。因此我们需要一些数作为中间过度。以让我们的随机能够随到整体最优解,而不会被 恶意卡数据的出题人陷害 当前最优蒙蔽了双眼。

所以是时候完整讲一下这个算法了;

通过随机rand函数,在当前的局部最优解上随机上下浮动(浮动幅度与当前温度成正相关)产生新解。对于每一个新解,若它较当前更优。则无条件接受它为当前最优解,否则则以一个概率(概率大小仍然与当前温度成正相关)来接受它。每一次随机结束后,将当前温度减小一点。

估计如果认真看了前面的人应该都 自闭 理解了。

它非常鲜明地体现了计算机的风格( 简单粗暴,以速度求质量 )

当然又有人要会问了。

Q: 既然在高温时接受当前不那么优的解的概率也趋近于1,也就是基本都接受了。那么凭什么最后能得到最优解呢?

其实我之前在学退火时也有这个问题。这个问题的答案其实就是退火算法之所以能得到最优解的理论依据。

因为如果出现更优解,我们是不考虑概率地无条件接受的;因此一个解越优,它被接受的总概率就会越大(虽然可能只会比次优解大0.00001%)。但是我们通过数百万次的随机运算不断地去扩大这个概率差(扩大是指数级增长的)。最终使得最优解的存活概率接近1。

这个随机到底随了多少次呢?

每一道题都有不同的次数,但大部分题的数量级是差不多的(程序时间就那么1000ms)。

就以P2210 Haywire例题来解答(其实我之前也不清楚)。我在我的代码里面加了一个countt变量,每一次随机运算它就自增一次,最后它的值有多大呢?

(17是答案,自己无视掉就行了)

足足有2357060!!!

值得一提的是我这题用的参数:

初始温度:3000

结束温度:1e -17( 10的负17次方 )

每次随机结束后温度的变化值:0.996( 温度 * 变化值 = 新温度 )

退火次数:20 ( 就前面三个参数重复调用了20次 )

其实也不必太吃惊,基本所有题目退火用的参数都是这几个( 可能次数会有点差异 )

->continue

例题

( 还是放例题讲容易点 )P1337 平衡点

作为一道纯正的几何物理题,竟然不给物竞的人做...

这题用模拟退火来做是考虑因为这题的最后解只有一个,况且比较好验证解(O n 复杂度还可以接受);并且如果用浮点运算的话可能会造成精度损失,最终影响答案。

#include<iostream>
#include<stdlib.h>
#include<cmath>
#include<cstdio>
#define cold 0.996 // 降温系数
#define temperature 3000 // 初始温度
#define time 4 // 退火次数( 这种纯物理题不用太多次 )
#define INF 999999
using namespace std ;
struct spot{
    int x ;
    int y ;
    int weight ;
};
spot map[1001] ;
int n ;
double t ;
double tmpx , tmpy , tmpw ;
double ansx , ansy , anse ;
//double nowx , nowy , nowe ;
double energy ( double x , double y ){ // 判断当前点的能量,能量越小越稳定
    double e = 0 ;
    for( int i = 1 ; i <= n ; i ++ ){
        tmpx = x -map[i].x ;
        tmpy = y - map[i].y ;
        tmpw = map[i].weight ;
        e += sqrt( tmpx * tmpx + tmpy * tmpy ) * tmpw ;
    }
    return e ;
}
void colddown(){
    t = temperature ;
    while( t > 1e-15 ){ // 开始模拟退火
//      cout << "debug" << " " << t << endl ;
        double nowx = ansx + ( ( rand() * 2 ) - RAND_MAX ) * t ; // 随机产生一个值,使点位移
        double nowy = ansy + ( ( rand() * 2 ) - RAND_MAX ) * t ;
        double nowe = energy( nowx , nowy ) ;
        double de = nowe - anse ;
//      cout << nowx << " " << nowy << endl ;
        if( de < 0 ){ // 如果当前解更优,就直接接受
            ansx = nowx ;
            ansy = nowy ;
            anse = nowe ;
//          cout << anse << endl ;
        }
        else{
            if( exp( -de / t ) * RAND_MAX > rand() ){ // 否则以一个概率接受
                ansx = nowx ;
                ansy = nowy ;
            }
        }
        t *= cold ;
    }
}
void solve(){
    for( int i = 1 ; i <= time ; i ++ ){
        colddown() ; // 多来几次,保证结果正确性
    }
}
int main () {
    srand( 201821307 ) ; // 一个好的随机数种子很重要
    cin >> n ;
    for( int i = 1 ; i <= n ; i ++ ){
        cin >> map[i].x >> map[i].y >> map[i].weight ;
        ansx += map[i].x ;
        ansy += map[i].y ;
    }
    ansx /= n ;
    ansy /= n ;
    anse = energy( ansx , ansy ) ;
    solve() ;
    printf( "%.3lf  %.3lf" , ansx , ansy) ;
    return 0 ;
}

( 注释掉的那几条是调程序时用的 )

其实这道题应该也不用多说,如果理解了前面的原理基本就不难懂。

( 竟然已经水了200多行 )

->continue

适用范围

虽然退火算法看起来好像非常暴力万能,但其实根dfs一样,也是有一定适用范围的。

  1. 答案较为简单,不许要输出过多数据的题目
  2. 能够在比较短的时间内验证答案( NP类问题都可以试试 )
  3. 不同的解之间有明显的互相转化的关系,及可以通过某个随机操作使一个解转化为另一个解
  4. 解集一半只有 1~3 个峰值,整体上呈现为数个单调函数
  5. 自己脸够白非酋落下了WA的眼泪

最后要补充的一点是,可能你的算法并没有任何问题,但你题目就是A不了。其实这是退火算法造成的,毕竟是随机算法。如果退火的三个系数或是随机种子用的不够好。WA也是很正常的。

(比如这题我并没有改任何的代码,只是用不同的参数去退火,结果却能造成不同的分数 )

最后只能祝大家找的种子全部Ac 脱欧入非

原文地址:https://www.cnblogs.com/CHNmuxii/p/12232477.html

时间: 2024-10-03 22:38:44

浅谈欧洲算法——模拟退火的相关文章

hdu1875浅谈prim算法的朴素实现

阅读原题 题目大意 给你几个(<=100)小岛的坐标,然后你把所有的岛都修上桥连接起来,求最小花费,还有个附加的限制:只有岛之间的距离大于等于10,或小于等于1000时才能修桥. 大概是因为十米以内不用建桥,千米以上无法建桥.哈哈,说着玩的. 很明显这是一道MST(最小生成树)的题目,貌似也有人用并查集AC过. 最小生成树算法 概述 最小生成树的常用算法有两个kruskal和prim算法.两者都是不停地执行归并操作,然而一言以蔽之,两者的不同之处在于:kruskal----归并边:prim---

浅谈分词算法(2)基于词典的分词方法

[TOC] 前言 在浅谈分词算法(1)分词中的基本问题中我们探讨了分词中的基本问题,也提到了基于词典的分词方法.基于词典的分词方法是一种比较传统的方式,这类分词方法有很多,如:正向最大匹配(forward maximum matching method, FMM).逆向最大匹配(backward maximum matching method,BMM).双向扫描法.逐词遍历法.N-最短路径方法以及基于词的n-gram语法模型的分词方法等等.对于这类方法,词典的整理选择在其中占到了很重要的作用,本

浅谈分词算法(1)分词中的基本问题

[TOC] 前言 分词或说切词是自然语言处理中一个经典且基础的问题,在平时的工作中也反复的接触到分词问题,用到了不同的模型,不同的方法应用在各个领域中,所以想对分词问题做一个系统的梳理.大多数分词问题主要是针对类似汉语.韩语.日语等,词语之间并没有天然的分割,而像英语等,句子中是带有天然的分割的.但是英语也会涉及到分词问题,比如实体识别.词性标注等内容.而本系列文章更多的是讨论汉语中的分词问题,首先我们从分词问题的基本出发,之后从传统的词典分词到将分词转为序列标注问题的分词,以及最新的结合深度学

浅谈Manacher算法与扩展KMP之间的联系

首先,在谈到Manacher算法之前,我们先来看一个小问题:给定一个字符串S,求该字符串的最长回文子串的长度.对于该问题的求解,网上解法颇多,时间复杂度也不尽相同,这里列述几种常见的解法. 解法一 通过枚举S的子串,然后判断该子串是否为回文,由于S的子串个数大约为,加上每次判断需要的时间,所以总的时间复杂度为,空间复杂度为. bool check(string &S, int left, int right) { while (left < right && S[left]

浅谈聚类算法(K-means)

聚类算法(K-means)目的是将n个对象根据它们各自属性分成k个不同的簇,使得簇内各个对象的相似度尽可能高,而各簇之间的相似度尽量小. 而如何评测相似度呢,采用的准则函数是误差平方和(因此也叫K-均值算法): 其中,E是数据集中所有对象的平方误差和,P是空间中的点,表示给定对象,mi为簇Ci的均值.其实E所代表的就是所有对象到其所在聚类中心的距离之和.对于不同的聚类,E的大小肯定是不一样的,因此,使E最小的聚类是误差平方和准则下的最优结果. 选取代表点用如下几个办法: (1)凭经验.根据问题性

浅谈KMP算法及其next[]数组

KMP算法是众多优秀的模式串匹配算法中较早诞生的一个,也是相对最为人所知的一个. 算法实现简单,运行效率高,时间复杂度为O(n+m)(n和m分别为目标串和模式串的长度),比蛮力算法的O(nm)快了许多. 理解KMP算法,关键是理解其中的精髓——next[]数组. (统一起见,下文将目标字符串记作obj,将模式字符串记作pattern,这与后面的程序代码是一致的) 我们给一个字符串S定义一个next值,记作next(S),next(S)=n表示: (1)S的前n个字符构成的前缀,和后n个字符的后缀

浅谈Tarjan算法及思想

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树.搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量.Tarjan算法有点类似于基于后序的深度遍历搜

从数组循环左移问题中浅谈考研算法设计的规范代码

问题:设将n(n>1)个整数存放到一维数组R中.设计一个算法,将R中的序列循环左移p(0<p<n)个位置,即将R中的数据由{X0,X1,...,Xn-1}变换为{Xp,Xp+1,...,Xn-1,X0,X1,...,Xp-1}.要求:写出本题的算法描述. 分析: 本题不难,要实现R中序列循环左移p个位置,只需先将R中前p个元素逆置,再将剩下的元素逆置,最后整体逆置操作即可.本题算法描述如下: 1 #include <iostream> 2 using namespace st

浅谈HASH算法与CSDN密码泄漏事件

http://www.cnbeta.com/articles/166531.htm 一.HASH算法不是加密算法 HASH算法是一种消息摘要算法,不是一种加密算法,但由于其单向运算,具有一定的不可逆性,成为加密算法中的一个构成部分,完整的加密机制不能仅依赖HASH算法. 二.HASH算法的碰撞现象HASH算法可以理解为将任意的信息经过提炼后,成为一个定长的字符串.世界上的信息数量为无穷大,所以定长的字符串不可能表达所有的摘要,因此存在所谓的“碰撞”,即2个同样的信息源摘要是一样的. 2004年山