算法介绍
模拟退火算法本质上是一种贪心算法。在引出模拟退火算法之前,先来看一种比较好理解的算法——爬山算法。简而言之,爬山算法就算爬到一个相对来说的最高峰(继续爬就下坡了)就停止的算法,这种算法虽然快,但是很容易限于局部最优解而非全局最优解。所以我们需要对爬山算法进行改进,便有了模拟退火算法。
模拟退火算法在爬山算法的基础上增添了一个随机性。如果下一个解优于当前解,那么肯定要更新当前解;如果下一个解不优于当前解,那么有一定的概率接受下一个解,并且这个概率会随着时间推移降低。而这里选取的概率正是运用了物理学中的热力学原理:在温度为T时,出现能量差为dE的概率为P(dE)=exp(dE/kT),在算法中,dE为当前解与新的解的差。因为dE小于0,并且随着T增加exp(dE/kT)在(0,1)区间上单调递减,所以随着时间的推移,接受这个新解的概率会变小。在实际操作中可以用生成随机数的方式来模拟是否会选取这个解。在这里显然要设置三个参数:起始温度、终止温度、温度变化速度。要根据不同的题目进行不同的设置。模拟退火算法在计算机竞赛中也经常会用到。
例题:Haywire
分析:通过枚举牛的排序状态来获取最优解,这里用到模拟退火来判断是否进行下一步。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<stdlib.h>
using namespace std;
int n;
int a[15][5],p[15];
int ans;
int cal(){
int sum=0;
for(int i=1;i<=n;i++){
for(int j=0;j<3;j++){
int x=a[i][j];
int c=p[x]-p[i];
sum+=abs(c);
}
}
return sum/2;//每头牛被算了两次
}
void SA(){
double T=100000,endT=1,cT=0.99;
int x,y;//任意交换两头牛的顺序
while(T>endT){
do{
x=rand()%n+1;
y=rand()%n+1;
}while(x==y);
int tp=p[x];p[x]=p[y];p[y]=tp;
int temp=cal();
if(temp<ans){
ans=temp;
}
else{
double px=(ans-temp)/T;
double py=(double)rand()/RAND_MAX;
if(px>py){
tp=p[x];p[x]=p[y];p[y]=tp;
}
}
T*=cT;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=0;j<3;j++)
scanf("%d",&a[i][j]);
p[i]=i;//初始状态
}
ans=cal();
for(int k=0;k<=1000;k++)
SA();
printf("%d",ans);
return 0;
}
代码的思路是不难理解的,但是这里存在一个和爬山算法一样的问题,就是我们还是可能陷入局部最优解当中,而找不到全局最优解。并且这里是用随机数去模拟的,所以这个算法应当属于非完美算法。为了弥补非完美算法带来的不稳定性,只能多做几次模拟退火的过程,增大搜索的覆盖面,从而获得最优解。 但是次数增大难免会引起时间复杂度的增加,所以实际操作时需要注意平衡。