1.算法思想
蚂蚁沿不同的路径出去寻找食物,找到食物就马上返回。这样短路径的蚂蚁来回一次时间短,单位时间内走过的蚂蚁数目就多,洒下的信息素自然会多,从而吸引更多蚂蚁,洒下更多信息素。而长路径恰好相反。因此,越来越多的蚂蚁会聚集到短路径上来,从而找到最短路径。即问题的找到问题的近似最优解。
2.算法设计
对于蚁群算法求解TSP问题,很容易陷入局部最优的情况,求解的结果经度低。为了改进这一缺点,通过查阅参考文献,找到了一些方法去提升全局搜索能力,有效规避蚁群算法陷入局部最优。
对于 TSP问题,设蚁群中蚂蚁的数量为m,城市的数量为n,结点i与结点j之间的欧式距离为 dij(t) ,t时刻结点i与结点j之间相连接的路径上的信息素浓度为τij,启发因子为ηij,信息素权重因子为α=1,启发因子β=5。初始时刻,蚂蚁放置在不同结点里,且各结点连接路径上的信息素浓度相同,然后蚂蚁按一定的概率选择路线。不妨将 Pkij(t) 设为 t时刻蚂蚁k从结点i转移到结点j的概率。“蚂蚁TSP”策略收到两方面的左右,首先是访问某结点的概率,这个概率的大小依赖于其他蚂蚁释放的信息素浓度。所以定义:
Jk(i)表示城市i可以直接到达的且又不在蚂蚁访问的城市系列里的城市的集合,算法实验开始之前找到了31个城市的坐标信息,构造出蚁群算法所要求解的问题,根据坐标信息计算出两两城市之间的距离信息dij(t),然后初始化蚂蚁数量m=50并根据距离信息初始化信息素分布,将蚂蚁随机并尽可能均匀地分配在各个城市,找到信息素发散点[1]增强信息素的挥发性以及增加量,信息素发散点即是一段时间最优解没有发生变化的时刻,如果一段时间最优解依然没有发生变化,重置信息素矩阵。传统的蚁群算法使得各个路径上的信息素浓度都相等,这很容易造成信息素的浓度不具有指导性,让算法陷入局部最优,改进后的初始信息素浓度和重置信息素浓度的具体表达式为:
信息素发散点之后信息素的更新策略公式为:
其中ρ为信息素的挥发率,a,b为常数,控制当时间t过了信息素发散点之后的增加量和挥发量。
整个算法的流程图如下:
图 1 :算法执行流程图
3. 详细代码
import javafx.scene.shape.SVGPath;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
/**
* @author Jiao Tiancai
* @version 2.0
* @description: 优化信息素更新策略, 每次重置矩阵
* @date 2021-4-18 09:56
*/
public class ACO_TSP {
private float a = 1.0f;//α信息素权重,通常为1
private float b = 5.0f;//路径权重,通常为2-5
private float C = 0f;//经过贪心算法求得的一条路径的长度
private float t0 = 0.0f;//t0所有边上的信息素初始浓度为t0,根据AS的随机比例规则对信息素浓度初始化的值
private ArrayList<ArrayList<Float>> t;//t储存信息素浓度
private float p = 0.1f;//挥发因子
private int m = 50;//蚂蚁数量
private int Q = 100;//信息素增强因子
private float Ta = 0.002f;//信息素变化率增加的系数
private float pb = 0.002f;//信息素挥发率增加的系数
private int T = 0;
private int TMax = 50;//一段时间没有变化后,就是信息素的发散点,当一段时间没有变化,那么重置
private int T0 = 0;//信息素发散点
private ArrayList<ArrayList<Integer>> citys;//城市坐标
private ArrayList<ArrayList<Float>> distance;//两两城市之间的距离
private Random random = new Random();
private int iterMax = 800;//最大迭代次数
private int iter = 0;//迭代次数
private ArrayList<ArrayList<Integer>> antHistory = new ArrayList<ArrayList<Integer>>();
ArrayList<Float> dis;//每一个蚂蚁走的总距离
private ArrayList<Float> plotPath = new ArrayList<Float>(); //记录迭代800次后的最短距离的结果
private ArrayList<Integer> antStart = new ArrayList<Integer>(); ///所有蚂蚁起始位置的设置
private ArrayList<Float> pathBest = new ArrayList<Float>();
private boolean faSan = false;
private ArrayList<Integer> routerBest;
private ArrayList<Float> times = new ArrayList<Float>();//用于储存十次的最短距离
private ArrayList<Float> avgIterMinDis = new ArrayList<Float>();//用于计算10次或更多结果
public ACO_TSP(float a, float b, float p, int m, int Q, ArrayList<ArrayList<Integer>> citys) {
this.a = a;//信息素因子
this.b = b;//启发因子
this.p = p;//挥发因子
this.m = m;//蚂蚁数量
this.Q = Q;//信息素增强因子
this.citys = citys;
}
public ACO_TSP(ArrayList<ArrayList<Integer>> citys) {
this.citys = citys;
}
/**
* @description: 更新城市坐标信息
* @param: [citys]
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 22:07
*/
public static void updateLoc(List<ArrayList<Integer>> citys) throws IOException {
File file = new File("src/data/citys.txt");
if (!file.exists())
throw new RuntimeException("Not File!");
BufferedReader br = new BufferedReader(new FileReader(file));
String str = ""; //读取每一行的值
String[] Location;//每一个城市的坐标
ArrayList<Integer> Loc;
while ((str = br.readLine()) != null) {
Loc = new ArrayList<Integer>();
Location = str.split("\\s+");
Loc.add(Integer.parseInt(Location[0]));
Loc.add(Integer.parseInt(Location[1]));
citys.add(Loc);
}
}
/**
* @description: 贪心算法初始化一条路径, 目前已经优化成用距离的倒数乘以信息素增强因子表示信息素初始浓度
* @param: []
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 22:39
*/
private void t_init() {
/*int firstCity = random.nextInt(citys.size());
int cityLength = citys.size();
t= new ArrayList<ArrayList<Float>>();
ArrayList<Integer> visited = new ArrayList<Integer>();
visited.add(firstCity);
int j = 0;
C=0;
Float min = 9999999f;
int minIndex = firstCity;
while (visited.size() < cityLength) {//不断扩充访问到的城市
for (int i = 0; i < cityLength; i++) {//不断尝试下一个城市是不是很短
if (!visited.contains(i)) {
if (distance.get(visited.get(j)).get(i) < min) {
min = distance.get(visited.get(j)).get(i);
minIndex = i; //记录最小的城市
}
}
if (i == cityLength - 1) {
min = 99999999f;
j = j + 1;
visited.add(minIndex);
}
}
}
for (int i = 0; i < cityLength; i++) {
C += distance.get(visited.get(i)).get(visited.get((i + 1) % cityLength));
}
t0 = Q / C;*/
t = new ArrayList<ArrayList<Float>>();
ArrayList<Float> rawT0;
for (int i = 0, cityLength = citys.size(); i < cityLength; i++) {
rawT0 = new ArrayList<Float>();
for (int k = 0; k < cityLength; k++) {
if (i == k) {
t0 = 0;
} else {
t0 = Q / distance.get(i).get(k);
}
rawT0.add(t0);
}
t.add(rawT0);
}
}
/**
* @description: 更新两两城市之间距离
* @param: []
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 22:23
*/
private void updateDis() {
ArrayList<Float> rowDis;
distance = new ArrayList<ArrayList<Float>>();
for (int i = 0, length = citys.size(); i < length; i++) {
rowDis = new ArrayList<Float>();
for (int j = 0; j < length; j++) {
rowDis.add((float) Math.sqrt((citys.get(i).get(0) - citys.get(j).get(0))
* (citys.get(i).get(0) - citys.get(j).get(0)) +
(citys.get(i).get(1) - citys.get(j).get(1))
* (citys.get(i).get(1) - citys.get(j).get(1))));//逐行获取两两城市之间的距离
}
distance.add(rowDis);//将获取完的距离信息添加到数组中去
}
}
/**
* @description: ACO解TSP
* @param: []
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 22:13
*/
private void solve() {
float minDis = 999999;//用于更新最短距离
updateDis();//更新城市两点之间的距离信息
t_init();//信息素浓度初始化
ArrayList<Integer> antVisited; //用于记录每个蚂蚁走过的路径
ArrayList<Integer> betCity;//存访问的下一个城市
ArrayList<Double> bet;//存访问下一个城市的概率
Float lastMinDis = 0f;//记录上一次的距离,用于判断最优解有多节没有更新了
plotPath = new ArrayList<Float>();
while (iter < iterMax) {
iter++;
antHistory = new ArrayList<ArrayList<Integer>>();
for (int i = 0; i < m; i++) { //蚂蚁(随机)或者均匀地分布在各个城市
if (i < 31) {
antStart.add(i);//random.nextInt(31)
} else {
antStart.add(random.nextInt(31));
}
}
for (int i = 0, length = citys.size(); i < m; i++) {
antVisited = new ArrayList<Integer>();
antVisited.add(antStart.get(0)); //先把第一个城市加入以访问城市
//计算蚂蚁访问下一个城市的概率
while (antVisited.size() < length) {
betCity = new ArrayList<Integer>();//存放城市j和概率p的键值对
bet = new ArrayList<Double>();
for (int j = 0; j < length; j++) {
if (!antVisited.contains(j)) {//选择没有访问过的城市
double p = (Math.pow(t.get(antVisited.get(antVisited.size() - 1)).get(j), a)
* Math.pow(1 / distance.get(antVisited
.get(antVisited.size() - 1)).get(j), b));//计算概率
betCity.add(j);//添加城市
bet.add(p);//添加概率
}
if (j == length - 1) {
if (bet.size() == 0) {
break;
}
Double sum = 0.0;
for (int k = 0; k < bet.size(); k++) {
sum += bet.get(k);
}
for (int k = 0; k < bet.size(); k++) {
bet.set(k, bet.get(k) / sum);
}
int nextCity = betCity.get(0);//轮盘赌方法选择下一个城市
if (bet.size() > 1) {
nextCity = selectNextByBet(betCity, bet);
}
antVisited.add(nextCity);
}
}
}
antHistory.add(antVisited);//m个蚂蚁构建成功
}
updateInft(antHistory, iter, T0);//更新信息素
int minIndex = 0;
boolean updataDisFlag = false;
for (int i = 1, disLength = dis.size(); i < disLength; i++) {
if (dis.get(i) < minDis) {
minDis = dis.get(i);
minIndex = i;
updataDisFlag = true;
}
}
if (updataDisFlag) {
routerBest = new ArrayList<Integer>();
routerBest = antHistory.get(minIndex);
}
if (lastMinDis < minDis) {
T = 0;
} else {
T++;//记录特定次数下最优路径没有更新
}
lastMinDis = minDis;
if (faSan) {
if (T > TMax) {//记录发散之后多少次解依然没变化的话,那么就重置信息素矩阵
T = 0;
faSan = false;
t_init();
p = 0.1f;
}
}
if (T > TMax) {// 次数大于指定值即到了信息素的发散点
T = 0;
T0 = iter;
p =0.1f;
faSan = true;
}
plotPath.add(minDis);
}
float sum = 0f;
for(int i=0;i<800;i++){
avgIterMinDis.set(i,avgIterMinDis.get(i)+plotPath.get(i));
}
}
/**
* @param antHistory
* @param iter
* @param t0
* @description: 信息素更新,一段时间最优解不变采用增强挥发性和增加量的方法对信息素进行更新
* @param: []
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-10 18:17
*/
private void updateInft(ArrayList<ArrayList<Integer>> antHistory, int iter, int t0) {
float k = 0.0f;
if (faSan) {
//k = iter - t0;
} else {
k = 0;
}
p = (pb * k + 1) * p;
if (p > 0.9) {
p = 0.9f;
}
//信息素挥发
for (int i = 0, length = citys.size(); i < length; i++) {
for (int j = 0; j < length; j++) {
t.get(i).set(j, t.get(i).get(j) * (1 - p));
}
}
dis = new ArrayList<Float>();
for (int i = 0; i < m; i++) {
dis.add(getRouteDis(antHistory.get(i)));//计算每一行的距离
}
//更新信息素浓度
for (int i = 0; i < m; i++) {
for (int j = 0, length = citys.size(); j < length; j++) {
t.get(antHistory.get(i).get(j)).set(antHistory.get(i).get((j + 1) % length),
(t.get(antHistory.get(i).get(j)).get(antHistory.get(i).get((j + 1) % length))
+ (Ta * k + 1) * Q / dis.get(i)));
}
}
}
/**
* @description: 得到路径长度
* @param: [ans]
* @return: java.lang.Float
* @author Jiao Tiancai
* @date: 2021-4-9 19:02
*/
private Float getRouteDis(ArrayList<Integer> ans) {
//System.out.println(ans);
Float dis = 0f;
int i, length;
for (i = 0, length = citys.size(); i < length; i++) {
dis += distance.get(ans.get(i)).get(ans.get((i + 1) % length));
}
return dis;
}
/**
* @param betCity
* @param bet
* @description: 用哈希表存各个下一跳的概率,现在找出蚂蚁轮盘赌后的下一跳
* @param: [hashtable]
* @return: int
* @author Jiao Tiancai
* @date: 2021-4-9 23:22
*/
private int selectNextByBet(ArrayList<Integer> betCity, ArrayList<Double> bet) {
Double betP = Double.valueOf(random.nextFloat());
int i = 0;
int returnCity = -1;
if (bet.size() == 1) {
return betCity.get(0);
}
for (i = 0; i < bet.size(); i++) {
if (betP - bet.get(i) <= 0) {
returnCity = betCity.get(i);
break;
} else {
betP -= bet.get(i);
}
}
return returnCity;
}
/**
* @description: 找到最好的一条路径
* @param: [antHistory]
* @return: java.util.ArrayList<java.lang.Integer>
* @author Jiao Tiancai
* @date: 2021-4-17 13:00
*/
private ArrayList<Integer> getBestRouter(ArrayList<ArrayList<Integer>> antHistory) {
float min = getRouteDis(antHistory.get(0));
int minIndex = 0;
float dis = 0.0f;
for (int i = 1; i < antHistory.size(); i++) {
if ((dis = getRouteDis(antHistory.get(i))) < min) {
min = dis;
minIndex = i;
}
}
return antHistory.get(minIndex);
}
public static void main(String[] args) throws IOException {
//创建一个空的城市
ArrayList<ArrayList<Integer>> citys = new ArrayList<ArrayList<Integer>>();
//读取城市位置信息
ACO_TSP.updateLoc(citys);
//初始化城市距离信息
ACO_TSP aco_tsp = new ACO_TSP(1.0f, 5.0f, 0.1f, 50, 100, citys);
long startTime = System.currentTimeMillis(); //程序开始记录时间
System.out.println("正在求解~~~");
for(int i=0;i<800;i++){
aco_tsp.avgIterMinDis.add(0f);
}
for(int i=0;i<10;i++){
aco_tsp.iter = 0;
aco_tsp.solve();
aco_tsp.times.add(aco_tsp.getRouteDis(aco_tsp.routerBest));
}
for (int i =0;i<800;i++){
aco_tsp.avgIterMinDis.set(i,aco_tsp.avgIterMinDis.get(i)/10);
}
System.out.println("求解10次之后的平均结果");
System.out.println(aco_tsp.avgIterMinDis);
System.out.println("求解10次后的结果");
System.out.println(aco_tsp.times);
long endTime = System.currentTimeMillis(); //程序结束记录时间
long TotalTime = endTime - startTime; //总消耗时间
System.out.println("总消耗时长" + TotalTime / 1000.0 + "s");
System.out.println("存储800次迭代最短距离更新结果");
System.out.println(aco_tsp.plotPath);
System.out.println("求得最短路径为:");
System.out.println(aco_tsp.routerBest);
System.out.println("求得最短距离为:");
System.out.println(aco_tsp.getRouteDis(aco_tsp.routerBest));
}
}
4. 测试结果
测试数据
测试数据是我在网上找的31个城市的坐标信息,31那估计可能是中国的31个省市信息吧。
测试结果
各取10次中最好结果进行分析
图 3:改进的蚁群结果图
上图为运行改进后的蚁群算法10次之后取最好结果进行分析,从上图可以看到大约50次后,最短路径开始收敛,迭代400次后依然会有最短路径的下降,明显提升了全局搜索能力,改进了蚁群算法容易陷入局部最优解的缺点。
图 4:绘制出的最优路径的结果
如果不提升全局搜索能力,仅仅按照传统蚁群算法进行求解,由下图可以看出效果明显不如改进过后的结果,其中蓝色折线图为未经优化的蚁群算法取10次之中最好结果的分析图。
图 5: 优化与未优化蚁群算法对比图
下图为未经优化的蚁群算法取10次之中最好结果的最短路径图。
图 6未经优化的蚁群算法求得的最短路径
取10次结果的平均进行分析
在取10次结果最优结果进行分析之后,为了更充分地证明改进后的蚁群算法让结果得到了优化,实验过程中再次对10次结果的平均最短距离进行分析。下图为迭代800次的蚁群算法循环运行10次,将800次中每次的最短距离10个值取平均得到平均最短距离,结果如下图所示:
图 7平均最短距离随迭代次数的变化图
再次对比距离随迭代次数的变化图可以发现,改进后的蚁群算法一段时间后就会跳出局部最优解,去寻找全局最优解。而未经优化的蚁群算法在80次后就收敛到一个较差的局部最优解。从平均距离的结果中,更能直观地感受到经过优化的蚁群算法强大的全局搜索能力。
优化和未经优化的蚁群算法求解10次后的最短距离结果如下图所示:
图 8优化与未经优化求10次的结果
尝试找出是哪种机制对改进的蚁群算法影响最大,实验分析结果如下图所示:
图 9不同优化方案对比图
从上图结果分析可知,熔断机制对蚁群算法的改进作用较大,信息素发散机制也会有一定的改进,但改进效果明显不如熔断机制的改进效果明显。
结果分析
由图3可以看出随着迭代次数的增加,蚁群算法逐渐收敛,收敛的结果很有可能只是问题的局部最优解。通过找到信息素发散点以及引入熔断机制,算法的效果提升很多。图5即为优化与未优化结果的对比图。由图7可以明显地看出改进后的蚁群算法在不断地跳出局部最优解,尝试寻找全局最优解。图8可以看出求解的结果的优化程度,图9可以看出熔断机制比信息素发散机制更能优化蚁群算法。
5.结论
邻域函数的选取直接影响收敛结果。通过改进邻域函数的方法让模拟退 火的性能取得了极强的优化。 降温系数对模拟退火的性能也有明显的影响。一个合适的降温系数可以 使算法的运行时间得到优化,本次实验采用了指数降温方式,实验过后将尝 试不同的降温方式观察算法的结果。
参考文献:
[1]徐书扬,王海江.改进蚁群算法在TSP问题中的应用[J].现代计算机,2020(25):22-26.
[2]向永靖.蚁群算法中参数设置的研究——以TSP为例[J].现代信息科技,2020,4(22):95-98+102.