模拟退火算法

本文为整理的介绍爬山算法、模拟退火算法的资料。部分来源网络。

爬山算法(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)+" ");
		}
	}
}

算法评价

模拟退火算法是一种随机算法,并不一定能找到全局的最优解,可以比较快的找到问题的近似最优解。如果参数设置得当,模拟退火算法搜索效率比穷举法要高。


参考文献:

  1. 大白话解析模拟退火算法

  2. http://www.cnblogs.com/heaad/archive/2010/12/23/1914725.html