模拟退火法含程序Word下载.docx
《模拟退火法含程序Word下载.docx》由会员分享,可在线阅读,更多相关《模拟退火法含程序Word下载.docx(18页珍藏版)》请在冰豆网上搜索。
![模拟退火法含程序Word下载.docx](https://file1.bdocx.com/fileroot1/2023-1/26/09e68ce6-4edc-43cf-90ef-90a6e163ec40/09e68ce6-4edc-43cf-90ef-90a6e163ec401.gif)
他们对一个模拟热力学系统提供了一系列选择项,并假设:
系统构形从能量
变化到能量
的概率为
。
很显然,如果
,
将大于1;
在这类情况下,对构形的能量变化任意指定一个概率值
,也就是说,该系统总是取这个选择项。
这种格式总是采取下降过程,偶尔采取上升步骤。
目前已被公认为米特罗波利斯算法。
为了将米特波利斯算法应用于热力学以外的系统,必须提供以下几项基本要素:
1.对可能的系统构形的一种描述。
2.一个有关构形内部随机变化的生成函数,这些变化将作为“选择项”提交给该系统。
3.一个目标函数
(类似于能量),求解
的极小值,即为算法所要完成的工作。
4.一个控制参数T(类似于温度)和一个退火进程,该进程用来说明系统是如何从高值向低值降低的,例如在温度T时每次下降步骤中要经过多少次随机的构形变化以及该步长是多大等等。
应说明的是,这里“高”和“低”的含义,还有进程表的确定,都需要一定的物理知识和/或一些摸索的实验。
10.9.1组合极小化:
旅行推销员问题
下面是我们用“旅行推销员问题”为具体实例说明模拟退火法的应用。
假设一个推销员,要去N个分别位于
的城市进行推销,并于最后返回他原来所在的城市,要求每个城市只能去一次,而且所经过的路径要尽可能地短。
这个问题属于一类所谓“NP-完全问题”。
这类问题求出一个精确解所需的计算时间是随N的增加以指数exp(常数×
N)增长的。
当N不断增大时,运行时间将迅速增加,进而导致费用高到令人难以接受的程度。
旅行推销员问题也是众多极小化问题中的一种,它的目标函数
具有多个局部极小值。
在实际应用当中,常常有足够多的条件可以从多个极小值中选出一个最小的,这个最小值即使不是绝对最小,也相当接近绝对最小了。
退火法的目的就是要获得这个最小值,同时又要将计算量限制在N的低阶次的数量级上。
旅行推销员问题也是按模拟退火问题的方式进行处理的。
具体如下:
1.构形将N个城市分别标记为
中的数,其中每个城市具有坐标
一个构形就是数字
的一个排列,可以解释为推销员途径的城市的顺序。
2.调整林(Lin)曾经提出过一种所谓“转移有效集”,这里的“转移”包括两种类型:
(a)移走路径的某一段,然后对这段路径上的城市用相反次序重新进行排列,并用后者来代替前者。
(b)移走某段路径,并用位于城市间的随机选取的另一段路径来取代被移走的路径。
3.目标函数在旅行推销员问题的一种最简单的形式中,目标函数
被定义为旅途的总长度
(10.9.2)
这里认为第N+1个点与第1个点是重合的。
但是为了表明模拟退火法的灵活性,我们还要用到下面的技巧:
假设推销员无端端地害怕飞越密西西比河,在这种情况下,我们对每个城市给定一个参数
,如果该城市位于密西西比河以东,
取1;
若在密西西比河以西则取-1。
对于目标函数
,我们将其改写为:
(10.9.3)
由于每过一次河都将以
作为惩罚,因而现在我们设计的算法的目标,就变成了寻找尽可能回避过河的最短路径。
路径长度对过河次数的相对重要性将由我们选择的
来确定。
图10.9.1表明了所得的结果。
显然,这种技巧可以推广到包含许多相互冲突的目的要求的极小化问题当中。
(A)(B)
(C)
图(A)表示的是从四个随机分布的城市中间找到的一条(接近)最短路径,中间竖虚线标识的是一条河流,但这是对过河没有附加您罚项的情况。
在图(B)中对过河施加的惩罚项很大,而图中所示的解本身的过河次数也相应地只有少得不能再少的两次。
在图(C)中惩罚项为负,这就是说,推销员实际上成了恣意偷渡的走私者!
图10.9.1用模拟退火法解决旅行推销员问题
4.退火进程这一步需要借助试验来确定。
首先要进行一些随机调整,然后利用它们来确定从转移到转移过程中将会遇到的
值之范围。
对参数T取一初始值(这个初始值要远远大于通常所能遇到的
的最大值),并以倍增的步长下减,每次使T总共减少10%。
我们拿每个新的常数T值去试各种100N重构形,或10N成功的重构形,无论哪个在前出现就取哪个。
当实在不能再进一步减小
时,则停止。
下面的旅行推销员程序利用了米特罗波利斯(Metropolis)算法,并展示了模拟退火技术应用于组合问题的几个主要方面。
#include<
stdio.h>
math.h>
#defineTFACTR0.9//退火进程:
每步中t的下降值由该因子决定
#defineALEN(a,b,c,d)aqrt(((b)-(a))*((b)-(a))+((d)-(c))*((d)-(c)))
voidanneal(floatx[],floaty[],intiorder[],intncity)
/*
本算法用于求解在ncity个城市之间作往返旅行的最短路径,其中这ncity个城市的位置坐标存贮在数组x[l..ncity]和y[1..ncity]中。
数组iorder[1..ncity]表示途径城市的顺序。
在输出项中,iorder中的元素将被置为数字l到ncity的某排对,本程序将返回它所能求出的最佳选择路径。
*/
{
intirbit1(unsignedlong*iseed);
intmetrop(floatde,floatt);
floatran3(long*idum);
floatrevcst(floatx[],floaty[],intiorder[],intncity,intn[]);
voidreverse(intiorder[],intncity,intn[]);
floattrncst(floatx[],floaty[],intiorder[],intncity,intn[]);
voidtrnspt(intiorder[],intncity,intn[]);
intans,nover,nlimit,il,i2;
inti,j,k,nsucc,nn,idec;
staticintn[7];
longidum;
unsignedlongiseed;
floatpath,de,t;
nover=100*ncity;
//在任何温度下,所试路径的最大次数
nlimit=10*ncity;
//在继续进行之前,成功的路径改变的最大次数
path=0.0;
t=0.5;
for(i=1;
i<
ncity;
i++){//计算初始路径的长度
i1=iorder[i];
i2=iorder[i+1]:
’
path+=ALEN(x[il],x[i2],y[i1],y[i2]);
}
i1=iorder[ncity];
//将路径头尾相连并结束循环
i2=iorder[1]
path+=ALEN(x[i1],x[i2],y[i1],y[i2]);
idum=-1;
iseed=111
for(j=1;
j<
=100;
j++){//试验100个温度值
nsucc=0;
for(k=1;
k<
=nover;
k++){
do{
n[1]=1+(int)(ncity*ran3(&
idum));
//选择段的起始点…
n[2]=1+(int)((ncity-1)*ran3(&
//…段的结尾
if(n[2]>
=n[1])++n[2];
nn=1+((n[1]-n[2]+ncity-1)%ncity);
//nn为不位于当前段上的城市数
}while(nn<
3);
idec=irbit1(&
iseed);
//确定是否做段反转或段输送
if(idec==0){//做输送
n[3]=n[2]+(int)(abs(nn-2)*ran3(&
idum))+1;
n[3]=1+((n[3]-1)%ncity);
//输送到一个不在当前路径上的某处
de=trncst(x,y,iorder,ncity,n);
//计算代价
ans=metrop(de,t);
//做预测
if(ans){
++nsucc;
path+=de;
trnspt(iorder,ncity,n);
//输送工作结束
}
}else{//做段反转
de=revcst(x,y,iorder,ncity,n);
//作预测
if(ans){
++nsucc;
path+=de;
reverse(iorder,ncity,n);
//完成段反转
}
if(nsucc>
=nlimit)break;
//如果成功的转移超过次数则提前结束
printf("
\n%s%10.6f%s&
12.6f\n"
"
T="
t,"
PathLength="
path);
SuccessfulMoves:
%6d\n"
,nsucc);
t*=TFACTR;
//退火进程
if(nsucc==0)return;
//如果不成功则返回
#include<
#defineALEN(a,b,c,d)sqrt(((b)-(a))*((b)-(a))+((d)-(c))*((d)-(c))))
floatrevcst(floatx[],floaty[],intiorder[],intncity,intn[])
/*
该子程序返回的是反转某给定路径所需的代价函数值。
参数中ncity为城市数;
数组x[1..ncity],y[1..ncity]为这些城市的位置坐标;
iorder[n..ncity]为当前路线;
数组n的头两个元素n[1]和n[2]分别代表将要被反转的路径段的起点城市和终点城市。
子程序的输出项de为反转所需的代价值,但真正的反转过程并不是由该程序执行。
floatxx[5],yy[5],de;
inti,ii;
n[3]=1+((n[1]+ncity-2)%ncity);
//找出n[1]以前的城市
n[4]=1+(n[2]%ncity);
//..找出n[2]以后的城市
for(j=1;
=4;
j++){
ii=iorder[n[j]];
//求四个所涉及到的城市的坐标
xx[j]=x[ii];
yy[j]=y[ii];
de=-ALEN(xx[1],xx[3],yy[1],yy[3]);
//计算断开路径段两端所需的代价
de-=ALEN(xx[2],xx[4],yy[2],yy[4]);
//以及用相反顺序重新连接所需的代价
de+=ALEN(xx[1],xx[4],yy[1],yy[4]);
de+=ALEN(xx[2],xx[3],yy[2],yy[3]);
returnde;
voidreverse(intiorder[],intncity,intn[])
该子程序的作用是执行一段路径的反转过程。
输入参数iorder[l..ncity]给出当前的路线顺序;
向量n的前四个元素中,n[1]和n[2]分别为将要被反转的路径的起点和终点城市,n[3]和n[4]则分别为紧随n[1]之前和紧随n[2]之后的两个城市的标号,其中n[3]和n[4]由函数revcst给出。
在输出端,iorder又将被作为返回值,它的定义是n[1]到n[2]路段被反转后的行程路线。
intnn,j,k,l,itmp;
nn=(1+((n[2]-n[1]+ncity)%ncity))/2;
//为实现反转操作,必须交换这么多城市
=nn;
k=1+((n[1]+j-2)%ncity);
//从段的端部开始,顺序交换城市对,
l=1+((n[2]-j+ncity)%ncity);
//直至到达路径的中心
itmp=iorder[k];
iorder[k]=iorder[l];
iorder[l]=itmp;
#defineALEN(a,b,c,d)sqrt(((b)-(a))*((b)-(a))+((d)-(c))*((d)-(c)))
floattrncat(floatx[],floaty[],intiorder[],intncity,intn[])
该子程序返回的是输送某段给定路径所需的代价函数值。
输入参数中ncity为城市的个数;
x[1..ncity]和y[1..ncity]为这些城市的位置坐标,数组n的前三个元索分别为:
将要被输送的路径段的起、止点城市以及这段路径将要被插入处的标志城市(插在该城市之后),该子程序的输出项de为计算出来的输送代价值,但实际的输送操作井不由该子程序执行。
floatxx[7],yy[7],de;
intj,ii;
n[4]=1+(n[3]%ncity);
//找出位于n[3]之后的城市..
n[5]=1+((n[1]+ncity-2)%ncity);
//..位于n[1]之前的一个城市..
n[6]=1+(n[2]%ncity);
//..位于n[2]之后的一个城市..
=6;
ii=iorder[n[j]];
//求出六个城市的有关坐标
de=-ALEN(xx[2],xx[6],yy[2],yy[6]);
//计算下列操作所需代价,断开n[1]到n[2]
de-=ALEN(xx[1],xx[5],yy[l],yy[5]);
//间的路径。
打开n[3]和n[4]之间的空间;
de-=ALEN(xx[3],xx[4],yy[3],yy[4]);
//连接这个空间中的路径段;
以及连接n[5]、n[6]
de+=ALEN(xx[1],xx[3],yy[1],yy[3]);
de+=ALEN(xx[2],xx[4],yy[2],yy[4]);
de+=ALEN(xx[5],xx[6],yy[5],yy[6]);
returnde;
#include"
nrutil.h"
voidtrnspt(intiorder[],intncity,intn[])
该子程序的作用是执行真正的段输送操作,输入参数iorder[1..ncity]给出当前的路径顺序;
数组n共有6个元素,其意义分别为:
n[1]和n[2]分别代表将要被输送的路径段的起点城市和终点城市;
n[3]和n[4]为两个相邻的城市,n[1]至n[2]路径段即将放入它们中间;
n[5]和n[6]分别为n[1]之前和n[2]之后的两个城市。
在这六个元索中n[4],n[5]和n[6]由函数trncst给出。
在输出端,iorder将根据路径段的移动和变化作出相应的修改。
intml,m2,m3,nn,j,jj,*jorder;
jorder=ivector(1,ncity);
m1=1+((n[2]-n[1]+ncity)%ncity);
//找出位于n[1]和n[2]间城市个数
m2=1+((n[5]-n[4]+ncity)%ncity);
//…n[4]到n[5]间的城市个数
m3=1+((n[3]-n[6]+ncity)%ncity);
//…n[6]和n[3]间的城市个数
nn=1;
for(j=1;
j<
=m1;
j++){
jj=1+((j+n[1]-2)%ncity);
//复制所选路径段
jorder[nn++]=iorder[jj];
if(m2>
0){
for(j=1;
=m2;
j++){//复制n[4]到n[5]间的路径段
jj=1+((j+n[4]-2)%ncity);
jorder[nn++]=iorder[jj];
if(m3>
=m3;
j++){//最后,复制n[6]到n[3]间的路径段
jj=1+((j+n[6]-2)%ncity);
jorder[nn++]=iorder[jj];
=ncity;
j++)//将jorder拷贝回iorder
iorder[j]=jorder[j];
free_ivector(jorder;
1,ncity);
intmetrop(float,de,floatt)
该子程序为米特罗波利斯算法的程序实现。
metrop返回的是一个布尔型变量,由该变量决定是否接受一个使目标函数e产生改变量de的重构形。
如果de<
0则metrop=1(真),而当de>
0时,metrop为真的概率是exp(一de/t),这里才是一个由退火进程决定的温度值。
floatran3(long*idum);
staticlonggljdum=1;
returnde<
0.0||ran3(&
gljdum)<
exp(-de/t);
10.9.2模拟遇火法在连续极小化问题中的应用
模拟退火法的基本思想也可以应用于具有连续N维控制参数的极小化问题当中,例如,在某个函数
(这里x为一个N维向量)有许多局部极小点的情况下,求解它的(理想的全局的)极小值。
这时米特罗波利斯算法所需的四要素可以具体化为:
1)目标函数即为
的值;
2)系统状态描态即为N维空间中的点x;
3)类似于温度的控制参数T以及一个使T逐渐降低的退火进程仍为原先的定义;
4)描述构形内部随机变化的发生器即为一个从x到x+△x采取随机步骤的方法。
在上述四要素中问题最大的是最后一条。
目前已发表的文献中[7-10],介绍了几种不同的选择△x办法。
但我们认为,这些方法都不算成功。
问题在于“效率”二字:
当局部的向下运动存在时,如果某个随机变化发生器几乎总是作出向上运动的决策,那么它的效率就很低。
我们认为,一个好的发生器在等高线的“窄谷”中仍应保持高效性;
当算法在接近收敛到极小点处时,它的效率也不应越变越低。
在上面我们提到的几篇文献中,除[7]中介绍的方法之外,其他所有方法都表现不同程度的低效性。
下面我们将要介绍的这种方法,利用了下降单纯形法(见第10.4节)的一种修改后的形式。
首先我们将米特罗波利斯算法四要素中的系统状态描述,由单个点x改为一个其有N+1个点的单纯形;
单纯形的“操作”和第10.4节中介绍的相同,分为反射、扩张和收缩三种。
然后我们将一个正的、呈对数分布的随机变量(与温度T成比例)添加到存贮的函数值中(该函数值与单纯形的每个顶点都有关联),再从每个被当作替代的新点的函数值中减去一个类似的随机变量。
和普通的米特罗波利斯方法一样,这种方法总是接受一个真正的下降步骤。
但有时也接受一次上升步骤。
在极限过程T→0中,该算法恰好变成了下降的单纯形法,并收敛到一个局部极小点。
在T的某有限值处,单纯形将扩展到一定的规模,其大小接近于在这个温度值所能达到的区域;
然后单纯形在这个区域内部做随机的滚动翻转式布朗运动,并在该过程中抽取一些新的、近似随机的点作为样本。
一个区域被利用的效率与其狭窄度(对椭球状山谷,指它的主轴比率)及方向性均无关。
如果温度降低得足够缓慢,那么单纯形将极有可能收缩到那个区域内,而那个区域内包含已遇到的最低相对极小。
由于在所有模拟退火法的应用场合中,“足够缓慢”一词的含意根据问题的不同可以有相当大的细微区别,因而成功与否往往取决于退火进程选择。
下面