1.原理
模拟退火算法(Simulated Annealing Algorithm )其实也是一种随机爬山法(Hill Climbing)。其不同之处在于模拟退火算法在下一个状态情况“变坏”的情况下以指数级概率接受该移动,这种随机接受方法可以使得爬山法跳出局部山峰的平坦区域,从而得到全局最优解。
算法开始时,设置初始温度T(应该是要足够大才行)、温度下降速率rate(应该是设置足够小),然后开始迭代:
(0)计算curr(curr为当前改变状态后的评估值),如果curr=0,则算法结束,找到全局最优解,否则进行下面(1)-(4)反复迭代。
(1)设dE=curr-last(curr为当前改变状态后的评估值,last为上一次评估值)。
(2)如果dE<=0,则接受该状态改变
(3)如果dE>0,则以概率p接受该状态改变,其中概率p的计算公式为:
p=
上面公式可以求得全局最小值,如果指数部分加一个负号,可以求得全局最大值。(也许吧??)
分析:一开始T很大,p接近1或超过1,很大概率是接受该后继状态;接下来,T逐渐下降,变得越来越小,由于dE<0,故p会很小,这时就很少能接受该后继状态了。
(4)每一轮次结束后修改温度T=T*rate,如果T小于设定的最小温度,算法结束,没有找到解。
2.代码实现
其数据结构和计算评估值函数参见上一篇文章:https://blog.csdn.net/obestboy/article/details/86695712
int simulatedAnnealing(int s[])
{
int h=heuristic(s);//评估函数见前一博文
if(h==0) return 0;
int curr=h;//改变状态后的评估值
int last=h;//上一次评估值
int c=0;//迭代轮次计数器
double T=SIZE*SIZE*1000;//初始温度
double rate=0.999; //温度下降速率
double minT=0.00001;
int k1=0,k2=0;//两个计数器而已
while (1)
{
int counter=0;
c++;
for(int i=0;i<SIZE;i++) //第i行
{
for(int j=0;j<SIZE;j++)//第i行皇后放第j列上
{
if(j!=s[i]) //s[i]为第i行皇后的现在列号
{
//临时调整:将第i行的皇后放在第j列上,计算评估值
curr=adjust(s,i,j,last);
counter++;
if(curr==0)
{
printf("\n迭代次数:%d\n",c);
printf("\n执行次数=%d,未执行次数=%d\n",k1,k2);
accept(s,i,j);
return c;
}
int dE=curr-last;
int p=(int) 1000*exp(1.0*dE/T);//计算概率
//下面myRandom函数见前一博文
int ss=myRandom(0,1000,counter);
if(dE<=0 )
{
accept(s,i,j);
last=curr;
break;
}
//执行概率大于生成的概率:执行
else if(p>=ss) //可以和上面的合并,主要看有多少没执行或执行
{
accept(s,i,j);
last=curr;
k1++;
break;
}
else k2++;//printf("没执行\n");
}
}
}
T*=rate;//温度下降
if(T<minT)
{
printf("没有找到!\n");
printf("\n迭代次数:%d\n",c);
printf("\n执行次数=%d,未执行次数=%d\n",k1,k2);
return c;
}
}
}
3.结果分析
模拟退火算法速度慢于爬山法,其初始温度、温度下降速率等的设置比较费劲,需要反复测试,代码中的设置对于某些个数的皇后不一定能找到最优解。(500以下应该能找到解)