ACO蟻羣算法解決TSP旅行商問題

前言

蟻羣算法也是一種利用了大天然規律的啓發式算法,與以前學習過的GA遺傳算法相似,遺傳算法是用了生物進行理論,把更具適應性的基因傳給下一代,最後就能獲得一個最優解,經常用來尋找問題的最優解。固然,本篇文章不會主講GA算法的,想要了解的同窗能夠查看,個人遺傳算法學習遺傳算法在走迷宮中的應。話題從新回到蟻羣算法,蟻羣算法是一個利用了螞蟻尋找食物的原理。不知道小時候有沒有發現,當一個螞蟻發現了地上的食物,而後很是迅速的,就有其餘的螞蟻聚攏過來,最後把食物擡回家,這裏面其實有着很是多的道理的,在ACO中就用到了這個機理用於解決實際生活中的一些問題。java

螞蟻找食物

首先咱們要具體說說一個有意思的事情,就是螞蟻找食物的問題,理解了這個原理以後,對於理解ACO算法就很是容易了。螞蟻做爲那麼小的動物,在地上漫無目的的尋找食物,起初都是沒有目標的,他從螞蟻洞中走出,隨機的爬向各個方向,在這期間他會向外界播撒一種化學物質,姑且就叫作信息素,因此這裏就能夠獲得的一個前提,越多螞蟻走過的路徑,信息素濃度就會越高,那麼某條路徑信息素濃度高了,天然就會有越多的螞蟻感受到了,就會彙集過來了。因此當衆多螞蟻中的一個找到食物以後,他就會在走過的路徑中放出信息素濃度,所以就會有不少的螞蟻趕來了。相似下面的場景:android

至於螞蟻是如何感知這個信息素,這個就得問生物學家了,我也沒作過研究。算法

算法介紹

OK,有了上面這個天然生活中的生物場景以後,咱們再來切入文章主題來學習一下蟻羣算法,百度百科中對應蟻羣算法是這麼介紹的:蟻羣算法是一種在圖中尋找優化路徑的機率型算法。他的靈感就是來自於螞蟻發現食物的行爲。蟻羣算法是一種新的模擬進化優化的算法,與遺傳算法有不少類似的地方。蟻羣算法在比較早的時候成功解決了TSP旅行商的問題(在後面的例子中也會以這個例子)。要用算法去模擬螞蟻的這種行爲,關鍵在於信息素的在算法中的設計,以及路徑中信息素濃度越大的路徑,將會有更高的機率被螞蟻所選擇到。app

算法原理dom

要想實現上面的幾個模擬行爲,須要藉助幾個公式,固然公式不是我本身定義的,主要有3個,以下圖:ide

上圖中所出現的alpha,beita,p等數字都是控制因子,因此可沒必要理會,Tij(n)的意思是在時間爲n的時候,從城市i到城市j的路徑的信息素濃度。相似於nij的字母是城市i到城市j距離的倒數。就是下面這個公式。工具

因此全部的公式都是爲第一個公式服務的,第一個公式的意思是指第k只螞蟻選擇從城市i到城市j的機率,能夠見得,這個受距離和信息素濃度的雙重影響,距離越遠,去此城市的機率天然也低,因此nij會等於距離的倒數,並且在算信息素濃度的時候,也考慮到了信息素濃度衰減的問題,因此會在上次的濃度值上乘以一個衰減因子P。另外還要加上本輪搜索增長的信息素濃度(假若有螞蟻通過此路徑的話),因此這幾個公式的總體設計思想仍是很是棒的。oop

算法的代碼實現

因爲自己我這裏沒有什麼真實的測試數據,就隨便本身構造了一個簡單的數據,輸入以下,分爲城市名稱和城市之間的距離,用#符號作區分標識,你們應該能夠看得懂吧學習

 

[java] view plain copy測試

 print?

  1. # CityName  
  2. 1  
  3. 2  
  4. 3  
  5. 4  
  6. # Distance  
  7. 1 2 1  
  8. 1 3 1.4  
  9. 1 4 1  
  10. 2 3 1  
  11. 2 4 1  
  12. 3 4 1  

 

螞蟻類Ant.Java:

 

[java] view plain copy

 print?

  1. package DataMining_ACO;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5. /** 
  6.  * 螞蟻類,進行路徑搜索的載體 
  7.  *  
  8.  * @author lyq 
  9.  *  
  10.  */  
  11. public class Ant implements Comparable<Ant> {  
  12.     // 螞蟻當前所在城市  
  13.     String currentPos;  
  14.     // 螞蟻遍歷完回到原點所用的總距離  
  15.     Double sumDistance;  
  16.     // 城市間的信息素濃度矩陣,隨着時間的增多而減小  
  17.     double[][] pheromoneMatrix;  
  18.     // 螞蟻已經走過的城市集合  
  19.     ArrayList<String> visitedCitys;  
  20.     // 還未走過的城市集合  
  21.     ArrayList<String> nonVisitedCitys;  
  22.     // 螞蟻當前走過的路徑  
  23.     ArrayList<String> currentPath;  
  24.   
  25.     public Ant(double[][] pheromoneMatrix, ArrayList<String> nonVisitedCitys) {  
  26.         this.pheromoneMatrix = pheromoneMatrix;  
  27.         this.nonVisitedCitys = nonVisitedCitys;  
  28.   
  29.         this.visitedCitys = new ArrayList<>();  
  30.         this.currentPath = new ArrayList<>();  
  31.     }  
  32.   
  33.     /** 
  34.      * 計算路徑的總成本(距離) 
  35.      *  
  36.      * @return 
  37.      */  
  38.     public double calSumDistance() {  
  39.         sumDistance = 0.0;  
  40.         String lastCity;  
  41.         String currentCity;  
  42.   
  43.         for (int i = 0; i < currentPath.size() - 1; i++) {  
  44.             lastCity = currentPath.get(i);  
  45.             currentCity = currentPath.get(i + 1);  
  46.   
  47.             // 經過距離矩陣進行計算  
  48.             sumDistance += ACOTool.disMatrix[Integer.parseInt(lastCity)][Integer  
  49.                     .parseInt(currentCity)];  
  50.         }  
  51.   
  52.         return sumDistance;  
  53.     }  
  54.   
  55.     /** 
  56.      * 螞蟻選擇前往下一個城市 
  57.      *  
  58.      * @param city 
  59.      *            所選的城市 
  60.      */  
  61.     public void goToNextCity(String city) {  
  62.         this.currentPath.add(city);  
  63.         this.currentPos = city;  
  64.         this.nonVisitedCitys.remove(city);  
  65.         this.visitedCitys.add(city);  
  66.     }  
  67.   
  68.     /** 
  69.      * 判斷螞蟻是否已經又從新回到起點 
  70.      *  
  71.      * @return 
  72.      */  
  73.     public boolean isBack() {  
  74.         boolean isBack = false;  
  75.         String startPos;  
  76.         String endPos;  
  77.   
  78.         if (currentPath.size() == 0) {  
  79.             return isBack;  
  80.         }  
  81.   
  82.         startPos = currentPath.get(0);  
  83.         endPos = currentPath.get(currentPath.size() - 1);  
  84.         if (currentPath.size() > 1 && startPos.equals(endPos)) {  
  85.             isBack = true;  
  86.         }  
  87.   
  88.         return isBack;  
  89.     }  
  90.   
  91.     /** 
  92.      * 判斷螞蟻在本次的走過的路徑中是否包含從城市i到城市j 
  93.      *  
  94.      * @param cityI 
  95.      *            城市I 
  96.      * @param cityJ 
  97.      *            城市J 
  98.      * @return 
  99.      */  
  100.     public boolean pathContained(String cityI, String cityJ) {  
  101.         String lastCity;  
  102.         String currentCity;  
  103.         boolean isContained = false;  
  104.   
  105.         for (int i = 0; i < currentPath.size() - 1; i++) {  
  106.             lastCity = currentPath.get(i);  
  107.             currentCity = currentPath.get(i + 1);  
  108.   
  109.             // 若是某一段路徑的始末位置一致,則認爲有通過此城市  
  110.             if ((lastCity.equals(cityI) && currentCity.equals(cityJ))  
  111.                     || (lastCity.equals(cityJ) && currentCity.equals(cityI))) {  
  112.                 isContained = true;  
  113.                 break;  
  114.             }  
  115.         }  
  116.   
  117.         return isContained;  
  118.     }  
  119.   
  120.     @Override  
  121.     public int compareTo(Ant o) {  
  122.         // TODO Auto-generated method stub  
  123.         return this.sumDistance.compareTo(o.sumDistance);  
  124.     }  
  125. }  

 

蟻羣算法工具類ACOTool.java:

[java] view plain copy

 print?

  1. package DataMining_ACO;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileReader;  
  6. import java.io.IOException;  
  7. import java.text.MessageFormat;  
  8. import java.util.ArrayList;  
  9. import java.util.Collections;  
  10. import java.util.HashMap;  
  11. import java.util.Map;  
  12. import java.util.Random;  
  13.   
  14. /** 
  15.  * 蟻羣算法工具類 
  16.  *  
  17.  * @author lyq 
  18.  *  
  19.  */  
  20. public class ACOTool {  
  21.     // 輸入數據類型  
  22.     public static final int INPUT_CITY_NAME = 1;  
  23.     public static final int INPUT_CITY_DIS = 2;  
  24.   
  25.     // 城市間距離鄰接矩陣  
  26.     public static double[][] disMatrix;  
  27.     // 當前時間  
  28.     public static int currentTime;  
  29.   
  30.     // 測試數據地址  
  31.     private String filePath;  
  32.     // 螞蟻數量  
  33.     private int antNum;  
  34.     // 控制參數  
  35.     private double alpha;  
  36.     private double beita;  
  37.     private double p;  
  38.     private double Q;  
  39.     // 隨機數產生器  
  40.     private Random random;  
  41.     // 城市名稱集合,這裏爲了方便,將城市用數字表示  
  42.     private ArrayList<String> totalCitys;  
  43.     // 全部的螞蟻集合  
  44.     private ArrayList<Ant> totalAnts;  
  45.     // 城市間的信息素濃度矩陣,隨着時間的增多而減小  
  46.     private double[][] pheromoneMatrix;  
  47.     // 目標的最短路徑,順序爲從集合的前部日後挪動  
  48.     private ArrayList<String> bestPath;  
  49.     // 信息素矩陣存儲圖,key採用的格式(i,j,t)->value  
  50.     private Map<String, Double> pheromoneTimeMap;  
  51.   
  52.     public ACOTool(String filePath, int antNum, double alpha, double beita,  
  53.             double p, double Q) {  
  54.         this.filePath = filePath;  
  55.         this.antNum = antNum;  
  56.         this.alpha = alpha;  
  57.         this.beita = beita;  
  58.         this.p = p;  
  59.         this.Q = Q;  
  60.         this.currentTime = 0;  
  61.   
  62.         readDataFile();  
  63.     }  
  64.   
  65.     /** 
  66.      * 從文件中讀取數據 
  67.      */  
  68.     private void readDataFile() {  
  69.         File file = new File(filePath);  
  70.         ArrayList<String[]> dataArray = new ArrayList<String[]>();  
  71.   
  72.         try {  
  73.             BufferedReader in = new BufferedReader(new FileReader(file));  
  74.             String str;  
  75.             String[] tempArray;  
  76.             while ((str = in.readLine()) != null) {  
  77.                 tempArray = str.split(" ");  
  78.                 dataArray.add(tempArray);  
  79.             }  
  80.             in.close();  
  81.         } catch (IOException e) {  
  82.             e.getStackTrace();  
  83.         }  
  84.   
  85.         int flag = -1;  
  86.         int src = 0;  
  87.         int des = 0;  
  88.         int size = 0;  
  89.         // 進行城市名稱種數的統計  
  90.         this.totalCitys = new ArrayList<>();  
  91.         for (String[] array : dataArray) {  
  92.             if (array[0].equals("#") && totalCitys.size() == 0) {  
  93.                 flag = INPUT_CITY_NAME;  
  94.   
  95.                 continue;  
  96.             } else if (array[0].equals("#") && totalCitys.size() > 0) {  
  97.                 size = totalCitys.size();  
  98.                 // 初始化距離矩陣  
  99.                 this.disMatrix = new double[size + 1][size + 1];  
  100.                 this.pheromoneMatrix = new double[size + 1][size + 1];  
  101.   
  102.                 // 初始值-1表明此對應位置無值  
  103.                 for (int i = 0; i < size; i++) {  
  104.                     for (int j = 0; j < size; j++) {  
  105.                         this.disMatrix[i][j] = -1;  
  106.                         this.pheromoneMatrix[i][j] = -1;  
  107.                     }  
  108.                 }  
  109.   
  110.                 flag = INPUT_CITY_DIS;  
  111.                 continue;  
  112.             }  
  113.   
  114.             if (flag == INPUT_CITY_NAME) {  
  115.                 this.totalCitys.add(array[0]);  
  116.             } else {  
  117.                 src = Integer.parseInt(array[0]);  
  118.                 des = Integer.parseInt(array[1]);  
  119.   
  120.                 this.disMatrix[src][des] = Double.parseDouble(array[2]);  
  121.                 this.disMatrix[des][src] = Double.parseDouble(array[2]);  
  122.             }  
  123.         }  
  124.     }  
  125.   
  126.     /** 
  127.      * 計算從螞蟻城市i到j的機率 
  128.      *  
  129.      * @param cityI 
  130.      *            城市I 
  131.      * @param cityJ 
  132.      *            城市J 
  133.      * @param currentTime 
  134.      *            當前時間 
  135.      * @return 
  136.      */  
  137.     private double calIToJProbably(String cityI, String cityJ, int currentTime) {  
  138.         double pro = 0;  
  139.         double n = 0;  
  140.         double pheromone;  
  141.         int i;  
  142.         int j;  
  143.   
  144.         i = Integer.parseInt(cityI);  
  145.         j = Integer.parseInt(cityJ);  
  146.   
  147.         pheromone = getPheromone(currentTime, cityI, cityJ);  
  148.         n = 1.0 / disMatrix[i][j];  
  149.   
  150.         if (pheromone == 0) {  
  151.             pheromone = 1;  
  152.         }  
  153.   
  154.         pro = Math.pow(n, alpha) * Math.pow(pheromone, beita);  
  155.   
  156.         return pro;  
  157.     }  
  158.   
  159.     /** 
  160.      * 計算綜合機率螞蟻從I城市走到J城市的機率 
  161.      *  
  162.      * @return 
  163.      */  
  164.     public String selectAntNextCity(Ant ant, int currentTime) {  
  165.         double randomNum;  
  166.         double tempPro;  
  167.         // 總機率指數  
  168.         double proTotal;  
  169.         String nextCity = null;  
  170.         ArrayList<String> allowedCitys;  
  171.         // 各城市機率集  
  172.         double[] proArray;  
  173.   
  174.         // 若是是剛剛開始的時候,沒有路過任何城市,則隨機返回一個城市  
  175.         if (ant.currentPath.size() == 0) {  
  176.             nextCity = String.valueOf(random.nextInt(totalCitys.size()) + 1);  
  177.   
  178.             return nextCity;  
  179.         } else if (ant.nonVisitedCitys.isEmpty()) {  
  180.             // 若是所有遍歷完畢,則再次回到起點  
  181.             nextCity = ant.currentPath.get(0);  
  182.   
  183.             return nextCity;  
  184.         }  
  185.   
  186.         proTotal = 0;  
  187.         allowedCitys = ant.nonVisitedCitys;  
  188.         proArray = new double[allowedCitys.size()];  
  189.   
  190.         for (int i = 0; i < allowedCitys.size(); i++) {  
  191.             nextCity = allowedCitys.get(i);  
  192.             proArray[i] = calIToJProbably(ant.currentPos, nextCity, currentTime);  
  193.             proTotal += proArray[i];  
  194.         }  
  195.   
  196.         for (int i = 0; i < allowedCitys.size(); i++) {  
  197.             // 歸一化處理  
  198.             proArray[i] /= proTotal;  
  199.         }  
  200.   
  201.         // 用隨機數選擇下一個城市  
  202.         randomNum = random.nextInt(100) + 1;  
  203.         randomNum = randomNum / 100;  
  204.         // 由於1.0是沒法判斷到的,,總和會無限接近1.0取爲0.99作判斷  
  205.         if (randomNum == 1) {  
  206.             randomNum = randomNum - 0.01;  
  207.         }  
  208.   
  209.         tempPro = 0;  
  210.         // 肯定區間  
  211.         for (int j = 0; j < allowedCitys.size(); j++) {  
  212.             if (randomNum > tempPro && randomNum <= tempPro + proArray[j]) {  
  213.                 // 採用拷貝的方式避免引用重複  
  214.                 nextCity = allowedCitys.get(j);  
  215.                 break;  
  216.             } else {  
  217.                 tempPro += proArray[j];  
  218.             }  
  219.         }  
  220.   
  221.         return nextCity;  
  222.     }  
  223.   
  224.     /** 
  225.      * 獲取給定時間點上從城市i到城市j的信息素濃度 
  226.      *  
  227.      * @param t 
  228.      * @param cityI 
  229.      * @param cityJ 
  230.      * @return 
  231.      */  
  232.     private double getPheromone(int t, String cityI, String cityJ) {  
  233.         double pheromone = 0;  
  234.         String key;  
  235.   
  236.         // 上一週期需將時間倒回一週期  
  237.         key = MessageFormat.format("{0},{1},{2}", cityI, cityJ, t);  
  238.   
  239.         if (pheromoneTimeMap.containsKey(key)) {  
  240.             pheromone = pheromoneTimeMap.get(key);  
  241.         }  
  242.   
  243.         return pheromone;  
  244.     }  
  245.   
  246.     /** 
  247.      * 每輪結束,刷新信息素濃度矩陣 
  248.      *  
  249.      * @param t 
  250.      */  
  251.     private void refreshPheromone(int t) {  
  252.         double pheromone = 0;  
  253.         // 上一輪週期結束後的信息素濃度,叢信息素濃度圖中查找  
  254.         double lastTimeP = 0;  
  255.         // 本輪信息素濃度增長量  
  256.         double addPheromone;  
  257.         String key;  
  258.   
  259.         for (String i : totalCitys) {  
  260.             for (String j : totalCitys) {  
  261.                 if (!i.equals(j)) {  
  262.                     // 上一週期需將時間倒回一週期  
  263.                     key = MessageFormat.format("{0},{1},{2}", i, j, t - 1);  
  264.   
  265.                     if (pheromoneTimeMap.containsKey(key)) {  
  266.                         lastTimeP = pheromoneTimeMap.get(key);  
  267.                     } else {  
  268.                         lastTimeP = 0;  
  269.                     }  
  270.   
  271.                     addPheromone = 0;  
  272.                     for (Ant ant : totalAnts) {  
  273.                         if(ant.pathContained(i, j)){  
  274.                             // 每隻螞蟻傳播的信息素爲控制因子除以距離總成本  
  275.                             addPheromone += Q / ant.calSumDistance();  
  276.                         }  
  277.                     }  
  278.   
  279.                     // 將上次的結果值加上遞增的量,並存入圖中  
  280.                     pheromone = p * lastTimeP + addPheromone;  
  281.                     key = MessageFormat.format("{0},{1},{2}", i, j, t);  
  282.                     pheromoneTimeMap.put(key, pheromone);  
  283.                 }  
  284.             }  
  285.         }  
  286.   
  287.     }  
  288.   
  289.     /** 
  290.      * 蟻羣算法迭代次數 
  291.      * @param loopCount 
  292.      * 具體遍歷次數 
  293.      */  
  294.     public void antStartSearching(int loopCount) {  
  295.         // 蟻羣尋找的總次數  
  296.         int count = 0;  
  297.         // 選中的下一個城市  
  298.         String selectedCity = "";  
  299.   
  300.         pheromoneTimeMap = new HashMap<String, Double>();  
  301.         totalAnts = new ArrayList<>();  
  302.         random = new Random();  
  303.   
  304.         while (count < loopCount) {  
  305.             initAnts();  
  306.   
  307.             while (true) {  
  308.                 for (Ant ant : totalAnts) {  
  309.                     selectedCity = selectAntNextCity(ant, currentTime);  
  310.                     ant.goToNextCity(selectedCity);  
  311.                 }  
  312.   
  313.                 // 若是已經遍歷完全部城市,則跳出此輪循環  
  314.                 if (totalAnts.get(0).isBack()) {  
  315.                     break;  
  316.                 }  
  317.             }  
  318.   
  319.             // 週期時間疊加  
  320.             currentTime++;  
  321.             refreshPheromone(currentTime);  
  322.             count++;  
  323.         }  
  324.   
  325.         // 根據距離成本,選出所花距離最短的一個路徑  
  326.         Collections.sort(totalAnts);  
  327.         bestPath = totalAnts.get(0).currentPath;  
  328.         System.out.println(MessageFormat.format("通過{0}次循環遍歷,最終得出的最佳路徑:", count));  
  329.         System.out.print("entrance");  
  330.         for (String cityName : bestPath) {  
  331.             System.out.print(MessageFormat.format("-->{0}", cityName));  
  332.         }  
  333.     }  
  334.   
  335.     /** 
  336.      * 初始化蟻羣操做 
  337.      */  
  338.     private void initAnts() {  
  339.         Ant tempAnt;  
  340.         ArrayList<String> nonVisitedCitys;  
  341.         totalAnts.clear();  
  342.   
  343.         // 初始化蟻羣  
  344.         for (int i = 0; i < antNum; i++) {  
  345.             nonVisitedCitys = (ArrayList<String>) totalCitys.clone();  
  346.             tempAnt = new Ant(pheromoneMatrix, nonVisitedCitys);  
  347.   
  348.             totalAnts.add(tempAnt);  
  349.         }  
  350.     }  
  351. }  

場景測試類Client.java:

 

[java] view plain copy

 print?

  1. package DataMining_ACO;  
  2.   
  3. /** 
  4.  * 蟻羣算法測試類 
  5.  * @author lyq 
  6.  * 
  7.  */  
  8. public class Client {  
  9.     public static void main(String[] args){  
  10.         //測試數據  
  11.         String filePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";  
  12.         //螞蟻數量  
  13.         int antNum;  
  14.         //蟻羣算法迭代次數  
  15.         int loopCount;  
  16.         //控制參數  
  17.         double alpha;  
  18.         double beita;  
  19.         double p;  
  20.         double Q;  
  21.           
  22.         antNum = 3;  
  23.         alpha = 0.5;  
  24.         beita = 1;  
  25.         p = 0.5;  
  26.         Q = 5;  
  27.         loopCount = 5;  
  28.           
  29.         ACOTool tool = new ACOTool(filePath, antNum, alpha, beita, p, Q);  
  30.         tool.antStartSearching(loopCount);  
  31.     }  
  32. }  

 

算法的輸出,就是在屢次搜索以後,找到的路徑中最短的一個路徑:

 

[java] view plain copy

 print?

  1. 通過5次循環遍歷,最終得出的最佳路徑:  
  2. entrance-->4-->1-->2-->3-->4  

 

由於數據量比較小,並不能看出蟻羣算法在這方面的優點,博友們能夠再次基礎上自行改造,並用大一點的數據作測試,其中的4個控制因子也能夠調控。蟻羣算法做爲一種啓發式算法,還能夠和遺傳算法結合,創造出更優的算法。蟻羣算法能夠解決許多這樣的連通圖路徑優化問題。可是有的時候也會出現搜索時間過長的問題。

相關文章
相關標籤/搜索