模拟退火算法
本文为整理的介绍爬山算法、模拟退火算法的资料。部分来源网络。
爬山算法(Hill Climbing)
介绍模拟退火前,先介绍爬山算法。爬山算法是一种简单的贪心搜索算法,该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解。
爬山算法实现很简单,其主要缺点是会陷入局部最优解,而不一定能搜索到全局最优解。如图1所示:假设C点为当前解,爬山算法搜索到A点这个局部最优解就会停止搜索,因为在A点无论向那个方向小幅度移动都不能得到更优的解。
模拟退火(SA,Simulated Annealing)思想
爬山法是完完全全的贪心法,每次都鼠目寸光的选择一个当前最优解,因此只能搜索到局部的最优值。模拟退火其实也是一种贪心算法,但是它的搜索过程引入了随机因素。模拟退火算法以一定的概率来接受一个比当前解要差的解,因此有可能会跳出这个局部的最优解,达到全局的最优解。以图1为例,模拟退火算法在搜索到局部最优解A后,会以一定的概率接受到E的移动。也许经过几次这样的不是局部最优的移动后会到达D点,于是就跳出了局部最大值A。
模拟退火算法描述:
若J(Y(i+1))>=J(Y(i)) (即移动后得到更优解),则总是接受该移动
若J(Y(i+1))<J(Y(i)) (即移动后的解比当前解要差) ,则以一定的概率接受移动,而且这个概率随着时间推移逐渐降低(逐渐降低才能趋向稳定)
这里的“一定的概率”的计算参考了金属冶炼的退火过程,这也是模拟退火算法名称的由来。
根据热力学的原理,在温度为T时,出现能量差为dE的降温的概率为P(dE),表示为:
P(dE)=exp(dE/(kT))
其中k是一个常数,exp表示自然指数,且dE<0。这条公式说白了就是:温度越高,出现一次能量差为dE的降温的概率就越大;温度越低,则出现降温的概率就越小。又由于dE总是小于0(否则就不叫退火了),因此dE/kT<0
,所以P(dE)的函数取值范围是(0,1)。
随着温度T的降低,P(dE)会逐渐降低。
我们将一次向较差解的移动看做一次温度跳变过程,我们以概率P(dE)来接受这样的移动。
关于爬山算法与模拟退火,有一个有趣的比喻:
爬山算法:兔子朝着比现在高的地方跳去。它找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是爬山算法,它不能保证局部最优值就是全局最优值。
模拟退火:兔子喝醉了。它随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,它渐渐清醒了并朝最高方向跳去。这就是模拟退火。
模拟退火算法伪代码
/*
* J(y):在状态y时的评价函数值
* Y(i):表示当前状态
* Y(i+1):表示新的状态
* r: 用于控制降温的快慢
* T: 系统的温度,系统初始应该要处于一个高温的状态
* T_min :温度的下限,若温度T达到T_min,则停止搜索
*/
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, Traveling Salesman Problem):有N个城市,要求从其中某个问题出发,唯一遍历所有城市,再回到出发的城市,求最短的路线。
旅行商问题属于所谓的NP完全问题,精确的解决TSP只能通过穷举所有的路径组合,其时间复杂度是O(N!)。
使用模拟退火算法可以比较快的求出TSP的一条近似最优路径。(使用遗传算法也是可以的,我将在下一篇文章中介绍)模拟退火解决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后面。
代码
package com.test1;
import java.lang.reflect.Array;
import java.util.*;
public class SimulatedAnnealing {
public static void main(String[] args) {
SA sa=new SA();
int[][] test=sa.getData();
int[][] test2=sa.getMatrix(6);
sa.sa(test2, 2);
}
}
class SA{
//随机获取一个矩阵数据表示个城市之间的距离
public int[][] getMatrix(int cityNum){
Random random=new Random();
int[][] matrix=new int[cityNum][cityNum];
for(int i=0;i<cityNum;i++){
matrix[i][i]=999;
}
for(int i=0;i<cityNum;i++){
for(int j=i+1;j<cityNum;j++){
matrix[i][j]=random.nextInt(100);
matrix[j][i]=matrix[i][j];
}
}
return matrix;
}
//随时选择两个城市交换位置,生成新的路径
public List getNewPath(List path){
Random random=new Random();
int size=path.size();
if(size<2){
System.out.println("城市数目太少,无法生成新的路径");
return path;
}
int position1;
int position2;
do{
position1=random.nextInt(size-1);
position2=random.nextInt(size-1);
}while(position1==0 || position2==0 || position1==position2);
Integer temp=(Integer)path.get(position1);he xo
path.set(position1,(Integer)path.get(position2));
path.set(position2,temp);
return path;
}
//计算接受的概率
public double acceptanceProbability(int length,int newLength,double temperature){
if(newLength < length){
return 1.1;
}
return Math.exp((length-newLength)/temperature);
}
//模拟退火过程,得到最优方案
public List sa(int[][] matrix,int start){
//record the number of cycles
int num=1;
//初始化温度
double temperature=1000;
//冷却概率
double coolingRate=0.997;
//初始化解决方案
//List firstPath=greedy(matrix,1);
List firstPath=getFirstPath(matrix.length,start);
List bestPath=firstPath;
System.out.print("第0次: path:");
printPath(bestPath);
System.out.print(" length:"+getLength(matrix, bestPath)+"\n");
//循环直至系统冷却
while(temperature>1){
//get current path's length
int length=getLength(matrix, bestPath);
//create a new path
List newPath=getNewPath(bestPath);
//get newPath's length
int newLength=getLength(matrix, newPath);
//decition
double acceptanceProbability=acceptanceProbability(length, newLength, temperature);
if(acceptanceProbability>Math.random()){
//acceptance
bestPath=newPath;
System.out.print("第"+num+"次: path:");
printPath(bestPath);
System.out.print(" length:"+getLength(matrix, bestPath)+"\n");
num++;
}else{
//give up the new path
}
//cooling
temperature*=coolingRate;
}
return bestPath;
}
//使用贪心算法获得一个初始的遍历序列
public List greedy(int[][] matrix,int start){
int line=matrix.length;
if(start>(line-1)){
System.out.println("输入数据有误!");
return null;
}
List<Integer> already=new ArrayList<Integer>();
int point_now=start;
already.add(start);
for(int i=0;i<(line-1);i++){
int min=999;
int next_point=0;
for(int j=0;j<line;j++){
if(!already.contains(j) && j!=point_now && matrix[point_now][j]<min){
min=matrix[point_now][j];
next_point=j;
}
}
already.add(next_point);
point_now=next_point;
}
already.add(start);
return already;
}
//init the first path
public List getFirstPath(int cityNum,int start){
List path=new ArrayList();
path.add(start);
for(int i=1;i<cityNum;i++){
if(i!=start)
path.add(i);
}
path.add(start);
return path;
}
//计算一个路径的总长度
public int getLength(int[][] matrix,List path){
int path_length=0;
for(int i=0;i<path.size()-1;i++){
path_length+=matrix[(Integer)path.get(i+1)][(Integer)path.get(i)];
}
return path_length;
}
//得到一个测试数据
public static int[][] getData(){
int[][] data=new int[5][5];
data[0]=new int[]{999,48,49,90,42};
data[1]=new int[]{48,999,24,22,49};
data[2]=new int[]{49,24,999,65,65};
data[3]=new int[]{90,22,65,999,92};
data[4]=new int[]{42,49,65,92,999};
return data;
}
//print a path
public void printPath(List path){
for(int i=0;i<path.size();i++){
System.out.print(path.get(i)+" ");
}
}
}
算法评价
模拟退火算法是一种随机算法,并不一定能找到全局的最优解,可以比较快的找到问题的近似最优解。如果参数设置得当,模拟退火算法搜索效率比穷举法要高。
参考文献: