前言
蟻羣算法也是一種利用了大天然規律的啓發式算法,與以前學習過的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?
- # 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:
[java] view plain copy
print?
- package DataMining_ACO;
-
- import java.util.ArrayList;
-
- /**
- * 螞蟻類,進行路徑搜索的載體
- *
- * @author lyq
- *
- */
- public class Ant implements Comparable<Ant> {
- // 螞蟻當前所在城市
- String currentPos;
- // 螞蟻遍歷完回到原點所用的總距離
- Double sumDistance;
- // 城市間的信息素濃度矩陣,隨着時間的增多而減小
- double[][] pheromoneMatrix;
- // 螞蟻已經走過的城市集合
- ArrayList<String> visitedCitys;
- // 還未走過的城市集合
- ArrayList<String> nonVisitedCitys;
- // 螞蟻當前走過的路徑
- ArrayList<String> currentPath;
-
- public Ant(double[][] pheromoneMatrix, ArrayList<String> nonVisitedCitys) {
- this.pheromoneMatrix = pheromoneMatrix;
- this.nonVisitedCitys = nonVisitedCitys;
-
- this.visitedCitys = new ArrayList<>();
- this.currentPath = new ArrayList<>();
- }
-
- /**
- * 計算路徑的總成本(距離)
- *
- * @return
- */
- public double calSumDistance() {
- sumDistance = 0.0;
- String lastCity;
- String currentCity;
-
- for (int i = 0; i < currentPath.size() - 1; i++) {
- lastCity = currentPath.get(i);
- currentCity = currentPath.get(i + 1);
-
- // 經過距離矩陣進行計算
- sumDistance += ACOTool.disMatrix[Integer.parseInt(lastCity)][Integer
- .parseInt(currentCity)];
- }
-
- return sumDistance;
- }
-
- /**
- * 螞蟻選擇前往下一個城市
- *
- * @param city
- * 所選的城市
- */
- public void goToNextCity(String city) {
- this.currentPath.add(city);
- this.currentPos = city;
- this.nonVisitedCitys.remove(city);
- this.visitedCitys.add(city);
- }
-
- /**
- * 判斷螞蟻是否已經又從新回到起點
- *
- * @return
- */
- public boolean isBack() {
- boolean isBack = false;
- String startPos;
- String endPos;
-
- if (currentPath.size() == 0) {
- return isBack;
- }
-
- startPos = currentPath.get(0);
- endPos = currentPath.get(currentPath.size() - 1);
- if (currentPath.size() > 1 && startPos.equals(endPos)) {
- isBack = true;
- }
-
- return isBack;
- }
-
- /**
- * 判斷螞蟻在本次的走過的路徑中是否包含從城市i到城市j
- *
- * @param cityI
- * 城市I
- * @param cityJ
- * 城市J
- * @return
- */
- public boolean pathContained(String cityI, String cityJ) {
- String lastCity;
- String currentCity;
- boolean isContained = false;
-
- for (int i = 0; i < currentPath.size() - 1; i++) {
- lastCity = currentPath.get(i);
- currentCity = currentPath.get(i + 1);
-
- // 若是某一段路徑的始末位置一致,則認爲有通過此城市
- if ((lastCity.equals(cityI) && currentCity.equals(cityJ))
- || (lastCity.equals(cityJ) && currentCity.equals(cityI))) {
- isContained = true;
- break;
- }
- }
-
- return isContained;
- }
-
- @Override
- public int compareTo(Ant o) {
- // TODO Auto-generated method stub
- return this.sumDistance.compareTo(o.sumDistance);
- }
- }
蟻羣算法工具類ACOTool.java:
[java] view plain copy
print?
- 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;
-
- /**
- * 蟻羣算法工具類
- *
- * @author lyq
- *
- */
- 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;
- // 信息素矩陣存儲圖,key採用的格式(i,j,t)->value
- 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];
-
- // 初始值-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]);
- }
- }
- }
-
- /**
- * 計算從螞蟻城市i到j的機率
- *
- * @param cityI
- * 城市I
- * @param cityJ
- * 城市J
- * @param currentTime
- * 當前時間
- * @return
- */
- 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;
- }
-
- /**
- * 計算綜合機率螞蟻從I城市走到J城市的機率
- *
- * @return
- */
- 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;
- // 由於1.0是沒法判斷到的,,總和會無限接近1.0取爲0.99作判斷
- 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;
- }
-
- /**
- * 獲取給定時間點上從城市i到城市j的信息素濃度
- *
- * @param t
- * @param cityI
- * @param cityJ
- * @return
- */
- 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;
- }
-
- /**
- * 每輪結束,刷新信息素濃度矩陣
- *
- * @param t
- */
- 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);
- }
- }
- }
-
- }
-
- /**
- * 蟻羣算法迭代次數
- * @param loopCount
- * 具體遍歷次數
- */
- 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:
[java] view plain copy
print?
- package DataMining_ACO;
-
- /**
- * 蟻羣算法測試類
- * @author lyq
- *
- */
- 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);
- }
- }
算法的輸出,就是在屢次搜索以後,找到的路徑中最短的一個路徑:
[java] view plain copy
print?
- 通過5次循環遍歷,最終得出的最佳路徑:
- entrance-->4-->1-->2-->3-->4
由於數據量比較小,並不能看出蟻羣算法在這方面的優點,博友們能夠再次基礎上自行改造,並用大一點的數據作測試,其中的4個控制因子也能夠調控。蟻羣算法做爲一種啓發式算法,還能夠和遺傳算法結合,創造出更優的算法。蟻羣算法能夠解決許多這樣的連通圖路徑優化問題。可是有的時候也會出現搜索時間過長的問題。