首先强烈推荐一篇博文http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html
个人感觉退火算法明显比遗传算法理解简单,实现也更加方便.
首先上公式:
P(dE) = exp( dE/(kT) )
p(de)是指在当前的策略并不是那么优秀的情况下接受它的概率,所以de一定为负数,k是一个常数(本程序中取1),T为当前的温度
这就涉及一个问题,当前的策略并不优秀,为什么要接受它,这就提到贪心之类的算法只盯着每一步的最优解,很容易陷入局部最优。
而有时我们要下这座山才能爬上另外一座更高的山.
而T就是退火算法的由来,随着时间降低T逐渐降低,所以1/T变大,而之前说过de一定为负数,所以exp(de/kT)变小,也就是说随着时间的推移我们越来越只接受较好的策略。
一个很简单的比喻:
兔子喝醉了。它随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,它渐渐清醒了并朝最高方向跳去。这就是模拟退火。
那么当前策略优于之前的策略呢,那还等什么,赶快拿过来用啊...(直接更新,也就是说退火算法就是当要舍弃新产生的策略并不是直接丢弃)
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过大,则搜索到全局最优解的可能会较高,但搜索的过程也就较长。若r过小,则搜索的过程会很快,但最终可能会达到一个局部最优值
*/
i ++ ;
}
对于TSP问题J函数就是路程,但是注意路程越大,越应该舍弃.
模拟退火解决TSP的思路:
1. 产生一条新的遍历路径P(i+1),计算路径P(i+1)的长度L( P(i+1) )
2. 若L(P(i+1)) < L(P(i)),则接受P(i+1)为新的路径,否则以模拟退火的那个概率接受P(i+1) ,然后降温
3. 重复步骤1,2直到满足退出条件
产生新的遍历路径的方法有很多,下面列举其中3种:
1. 随机选择2个节点,交换路径中的这2个节点的顺序。
2. 随机选择2个节点,将路径中这2个节点间的节点顺序逆转。
3. 随机选择3个节点m,n,k,然后将节点m与n间的节点移位到节点k后面。
代码采用第二种方法.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <math.h>
#define cities 10//城市的个数
#define T 4000//定义初始温度
#define delta 0.98//用来控制降温的快慢
#define eps 0//结束时的温度
int distance[cities+1][cities+1];
int city[cities]; //访问的顺序
void init()
{
int i,j;
memset(distance,0,sizeof(distance));
srand(time(NULL));
for (i=0;i<cities;i++)
for (j=i+1;j<cities;j++)
{
distance[i][j]=rand()%100;
distance[j][i]=distance[i][j];
}
printf("城市距离矩阵如下:\n");
for (i=0;i<cities;i++)
{
for (j=0;j<cities;j++)
printf("%4d",distance[i][j]);
printf("\n");
}
}
void produce()//生成初始路径
{
int i,j,t,ok;
srand(time(NULL));
for (i=0;i<cities;i++)
{
t=rand()%cities;
ok=1;
for (j=0;j<i;j++)
if (city[j]==t)
{
ok=0;
break;
}
if (ok)
city[i]=t;
else
i--;
}
printf("初始选择为:\n");
for (i=0;i<cities;i++)
printf("%4d",city[i]);
printf("\n");
}
int main()
{
int i,j,t,temp1,temp2,before,temp,last,min;
double d;
init();//生成距离矩阵
produce();//生成初始路径
t=T;
srand(time(NULL));//生成新路径的方案是随机生成两个节点,交换这两个节点内片段的位置
while(t>eps)//生成一条新路径,如果新路径优于当前路径,那么采纳,否则以一定几率采纳(随时间降低)
{
before=0;
for (i=1;i<cities;i++)
before=before+distance[city[i-1]][city[i]];//计算原来的距离
temp1=rand()%cities;
temp2=rand()%cities;
if (temp1>temp2)//保证temp1<temp2
{
temp=temp1;
temp1=temp2;
temp2=temp1;
}
for (i=temp1;i<=(temp1+temp2)/2;i++)
{
temp=city[i];
city[i]=city[temp2+temp1-i];
city[temp2+temp1-i]=temp;
}
last=0;
for (i=1;i<cities;i++)
last=last+distance[city[i-1]][city[i]];//计算现在的距离
printf("%d %d\n",before,last);
if (last>before)//如果新的不好并且不可能继续
{
d=rand()%100;
d=d/100;
if (exp((before-last)*1.0/t)<d) //注意这边是before-last(一开始效果很不好,竟然是这边...),因为是要保证负的,这样exp肯定小于1,否则每一次都接受较大的那个了...
{
for (i=temp1;i<=(temp1+temp2)/2;i++)//再换回来
{
temp=city[i];
city[i]=city[temp2+temp1-i];
city[temp2+temp1-i]=temp;
}
}
}
t=t*delta;
}
printf("最优解为:\n");
for (i=0;i<cities;i++)
printf("%4d",city[i]);
printf("\n");
printf("最短路长度为:\n");
before=0;
for (i=1;i<cities;i++)
before=before+distance[city[i-1]][city[i]];//计算原来的距离
printf("%d\n",before);
return 0;
}
效果相当nice(比遗传算法效果还好,可能是遗传算法的交配部分设计的还是不够优秀),而且最后基本都收敛了.