前言
蚁群算法也是一种利用了大自然规律的启发式算法,与之前学习过的GA遗传算法类似,遗传算法是用了生物进行理论,把更具适应性的基因传给下一代,最后就能得到一个最优解,常常用来寻找问题的最优解。当然,本篇文章不会主讲GA算法的,想要了解的同学可以查看,我的遗传算法学习和遗传算法在走迷宫中的应用。话题重新回到蚁群算法,蚁群算法是一个利用了蚂蚁寻找食物的原理。不知道小时候有没有发现,当一个蚂蚁发现了地上的食物,然后非常迅速的,就有其他的蚂蚁聚拢过来,最后把食物抬回家,这里面其实有着非常多的道理的,在ACO中就用到了这个机理用于解决实际生活中的一些问题。
蚂蚁找食物
首先我们要具体说说一个有意思的事情,就是蚂蚁找食物的问题,理解了这个原理之后,对于理解ACO算法就非常容易了。蚂蚁作为那么小的动物,在地上漫无目的的寻找食物,起初都是没有目标的,他从蚂蚁洞中走出,随机的爬向各个方向,在这期间他会向外界播撒一种化学物质,姑且就叫做信息素,所以这里就可以得到的一个前提,越多蚂蚁走过的路径,信息素浓度就会越高,那么某条路径信息素浓度高了,自然就会有越多的蚂蚁感觉到了,就会聚集过来了。所以当众多蚂蚁中的一个找到食物之后,他就会在走过的路径中放出信息素浓度,因此就会有很多的蚂蚁赶来了。类似下面的场景:
至于蚂蚁是如何感知这个信息素,这个就得问生物学家了,我也没做过研究。
算法介绍
OK,有了上面这个自然生活中的生物场景之后,我们再来切入文章主题来学习一下蚁群算法,百度百科中对应蚁群算法是这么介绍的:蚁群算法是一种在图中寻找优化路径的机率型算法。他的灵感就是来自于蚂蚁发现食物的行为。蚁群算法是一种新的模拟进化优化的算法,与遗传算法有很多相似的地方。蚁群算法在比较早的时候成功解决了TSP旅行商的问题(在后面的例子中也会以这个例子)。要用算法去模拟蚂蚁的这种行为,关键在于信息素的在算法中的设计,以及路径中信息素浓度越大的路径,将会有更高的概率被蚂蚁所选择到。
算法原理
要想实现上面的几个模拟行为,需要借助几个公式,当然公式不是我自己定义的,主要有3个,如下图:
上图中所出现的alpha,beita,p等数字都是控制因子,所以可不必理会,Tij(n)的意思是在时间为n的时候,从城市i到城市j的路径的信息素浓度。类似于nij的字母是城市i到城市j距离的倒数。就是下面这个公式。
所以所有的公式都是为第一个公式服务的,第一个公式的意思是指第k只蚂蚁选择从城市i到城市j的概率,可以见得,这个受距离和信息素浓度的双重影响,距离越远,去此城市的概率自然也低,所以nij会等于距离的倒数,而且在算信息素浓度的时候,也考虑到了信息素浓度衰减的问题,所以会在上次的浓度值上乘以一个衰减因子P。另外还要加上本轮搜索增加的信息素浓度(假如有蚂蚁经过此路径的话),所以这几个公式的整体设计思想还是非常棒的。
算法的代码实现
由于本身我这里没有什么真实的测试数据,就随便自己构造了一个简单的数据,输入如下,分为城市名称和城市之间的距离,用#符号做区分标识,大家应该可以看得懂吧
- # CityName
- 1
- 2
- 3
- 4
- # Distance
- 1 2 1
- 1 3 1.4
- 1 4 1
- 2 3 1
- 2 4 1
- 3 4 1
蚂蚁类Ant.java:
蚁群算法工具类ACOTool.java:
- package DataMining_ACO;
-
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileReader;
- import java.io.IOException;
- import java.text.MessageFormat;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Random;
-
-
-
-
-
-
-
- public class ACOTool {
-
- public static final int INPUT_CITY_NAME = 1;
- public static final int INPUT_CITY_DIS = 2;
-
-
- public static double[][] disMatrix;
-
- public static int currentTime;
-
-
- private String filePath;
-
- private int antNum;
-
- private double alpha;
- private double beita;
- private double p;
- private double Q;
-
- private Random random;
-
- private ArrayList<String> totalCitys;
-
- private ArrayList<Ant> totalAnts;
-
- private double[][] pheromoneMatrix;
-
- private ArrayList<String> bestPath;
-
- private Map<String, Double> pheromoneTimeMap;
-
- public ACOTool(String filePath, int antNum, double alpha, double beita,
- double p, double Q) {
- this.filePath = filePath;
- this.antNum = antNum;
- this.alpha = alpha;
- this.beita = beita;
- this.p = p;
- this.Q = Q;
- this.currentTime = 0;
-
- readDataFile();
- }
-
-
-
-
- private void readDataFile() {
- File file = new File(filePath);
- ArrayList<String[]> dataArray = new ArrayList<String[]>();
-
- try {
- BufferedReader in = new BufferedReader(new FileReader(file));
- String str;
- String[] tempArray;
- while ((str = in.readLine()) != null) {
- tempArray = str.split(" ");
- dataArray.add(tempArray);
- }
- in.close();
- } catch (IOException e) {
- e.getStackTrace();
- }
-
- int flag = -1;
- int src = 0;
- int des = 0;
- int size = 0;
-
- this.totalCitys = new ArrayList<>();
- for (String[] array : dataArray) {
- if (array[0].equals("#") && totalCitys.size() == 0) {
- flag = INPUT_CITY_NAME;
-
- continue;
- } else if (array[0].equals("#") && totalCitys.size() > 0) {
- size = totalCitys.size();
-
- this.disMatrix = new double[size + 1][size + 1];
- this.pheromoneMatrix = new double[size + 1][size + 1];
-
-
- for (int i = 0; i < size; i++) {
- for (int j = 0; j < size; j++) {
- this.disMatrix[i][j] = -1;
- this.pheromoneMatrix[i][j] = -1;
- }
- }
-
- flag = INPUT_CITY_DIS;
- continue;
- }
-
- if (flag == INPUT_CITY_NAME) {
- this.totalCitys.add(array[0]);
- } else {
- src = Integer.parseInt(array[0]);
- des = Integer.parseInt(array[1]);
-
- this.disMatrix[src][des] = Double.parseDouble(array[2]);
- this.disMatrix[des][src] = Double.parseDouble(array[2]);
- }
- }
- }
-
-
-
-
-
-
-
-
-
-
-
-
- private double calIToJProbably(String cityI, String cityJ, int currentTime) {
- double pro = 0;
- double n = 0;
- double pheromone;
- int i;
- int j;
-
- i = Integer.parseInt(cityI);
- j = Integer.parseInt(cityJ);
-
- pheromone = getPheromone(currentTime, cityI, cityJ);
- n = 1.0 / disMatrix[i][j];
-
- if (pheromone == 0) {
- pheromone = 1;
- }
-
- pro = Math.pow(n, alpha) * Math.pow(pheromone, beita);
-
- return pro;
- }
-
-
-
-
-
-
- public String selectAntNextCity(Ant ant, int currentTime) {
- double randomNum;
- double tempPro;
-
- double proTotal;
- String nextCity = null;
- ArrayList<String> allowedCitys;
-
- double[] proArray;
-
-
- if (ant.currentPath.size() == 0) {
- nextCity = String.valueOf(random.nextInt(totalCitys.size()) + 1);
-
- return nextCity;
- } else if (ant.nonVisitedCitys.isEmpty()) {
-
- nextCity = ant.currentPath.get(0);
-
- return nextCity;
- }
-
- proTotal = 0;
- allowedCitys = ant.nonVisitedCitys;
- proArray = new double[allowedCitys.size()];
-
- for (int i = 0; i < allowedCitys.size(); i++) {
- nextCity = allowedCitys.get(i);
- proArray[i] = calIToJProbably(ant.currentPos, nextCity, currentTime);
- proTotal += proArray[i];
- }
-
- for (int i = 0; i < allowedCitys.size(); i++) {
-
- proArray[i] /= proTotal;
- }
-
-
- randomNum = random.nextInt(100) + 1;
- randomNum = randomNum / 100;
-
- if (randomNum == 1) {
- randomNum = randomNum - 0.01;
- }
-
- tempPro = 0;
-
- for (int j = 0; j < allowedCitys.size(); j++) {
- if (randomNum > tempPro && randomNum <= tempPro + proArray[j]) {
-
- nextCity = allowedCitys.get(j);
- break;
- } else {
- tempPro += proArray[j];
- }
- }
-
- return nextCity;
- }
-
-
-
-
-
-
-
-
-
- private double getPheromone(int t, String cityI, String cityJ) {
- double pheromone = 0;
- String key;
-
-
- key = MessageFormat.format("{0},{1},{2}", cityI, cityJ, t);
-
- if (pheromoneTimeMap.containsKey(key)) {
- pheromone = pheromoneTimeMap.get(key);
- }
-
- return pheromone;
- }
-
-
-
-
-
-
- private void refreshPheromone(int t) {
- double pheromone = 0;
-
- double lastTimeP = 0;
-
- double addPheromone;
- String key;
-
- for (String i : totalCitys) {
- for (String j : totalCitys) {
- if (!i.equals(j)) {
-
- key = MessageFormat.format("{0},{1},{2}", i, j, t - 1);
-
- if (pheromoneTimeMap.containsKey(key)) {
- lastTimeP = pheromoneTimeMap.get(key);
- } else {
- lastTimeP = 0;
- }
-
- addPheromone = 0;
- for (Ant ant : totalAnts) {
- if(ant.pathContained(i, j)){
-
- addPheromone += Q / ant.calSumDistance();
- }
- }
-
-
- pheromone = p * lastTimeP + addPheromone;
- key = MessageFormat.format("{0},{1},{2}", i, j, t);
- pheromoneTimeMap.put(key, pheromone);
- }
- }
- }
-
- }
-
-
-
-
-
-
- public void antStartSearching(int loopCount) {
-
- int count = 0;
-
- String selectedCity = "";
-
- pheromoneTimeMap = new HashMap<String, Double>();
- totalAnts = new ArrayList<>();
- random = new Random();
-
- while (count < loopCount) {
- initAnts();
-
- while (true) {
- for (Ant ant : totalAnts) {
- selectedCity = selectAntNextCity(ant, currentTime);
- ant.goToNextCity(selectedCity);
- }
-
-
- if (totalAnts.get(0).isBack()) {
- break;
- }
- }
-
-
- currentTime++;
- refreshPheromone(currentTime);
- count++;
- }
-
-
- Collections.sort(totalAnts);
- bestPath = totalAnts.get(0).currentPath;
- System.out.println(MessageFormat.format("经过{0}次循环遍历,最终得出的最佳路径:", count));
- System.out.print("entrance");
- for (String cityName : bestPath) {
- System.out.print(MessageFormat.format("-->{0}", cityName));
- }
- }
-
-
-
-
- private void initAnts() {
- Ant tempAnt;
- ArrayList<String> nonVisitedCitys;
- totalAnts.clear();
-
-
- for (int i = 0; i < antNum; i++) {
- nonVisitedCitys = (ArrayList<String>) totalCitys.clone();
- tempAnt = new Ant(pheromoneMatrix, nonVisitedCitys);
-
- totalAnts.add(tempAnt);
- }
- }
- }
场景测试类Client.java:
- package DataMining_ACO;
-
-
-
-
-
-
- public class Client {
- public static void main(String[] args){
-
- String filePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";
-
- int antNum;
-
- int loopCount;
-
- double alpha;
- double beita;
- double p;
- double Q;
-
- antNum = 3;
- alpha = 0.5;
- beita = 1;
- p = 0.5;
- Q = 5;
- loopCount = 5;
-
- ACOTool tool = new ACOTool(filePath, antNum, alpha, beita, p, Q);
- tool.antStartSearching(loopCount);
- }
- }
算法的输出,就是在多次搜索之后,找到的路径中最短的一个路径:
- 经过5次循环遍历,最终得出的最佳路径:
- entrance-->4-->1-->2-->3-->4
因为数据量比较小,并不能看出蚁群算法在这方面的优势,博友们可以再次基础上自行改造,并用大一点的数据做测试,其中的4个控制因子也可以调控。蚁群算法作为一种启发式算法,还可以和遗传算法结合,创造出更优的算法。蚁群算法可以解决许多这样的连通图路径优化问题。但是有的时候也会出现搜索时间过长的问题。
参考文献:百度百科.蚁群算法
我的数据挖掘算法库:https://github.com/linyiqun/DataMiningAlgorithm
我的算法库:https://github.com/linyiqun/lyq-algorithms-lib