ACO蟻群演算法解決TSP旅行商問題
前言
蟻群演算法也是一種利用了大自然規律的啟發式演算法,與之前學習過的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:
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: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:
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);
}
}
演算法的輸出,就是在多次搜尋之後,找到的路徑中最短的一個路徑:
經過5次迴圈遍歷,最終得出的最佳路徑:
entrance-->4-->1-->2-->3-->4
因為資料量比較小,並不能看出蟻群演算法在這方面的優勢,博友們可以再次基礎上自行改造,並用大一點的資料做測試,其中的4個控制因子也可以調控。蟻群演算法作為一種啟發式演算法,還可以和遺傳演算法結合,創造出更優的演算法。蟻群演算法可以解決許多這樣的連通圖路徑優化問題。但是有的時候也會出現搜尋時間過長的問題。
參考文獻:百度百科.蟻群演算法
我的資料探勘演算法庫:https://github.com/linyiqun/DataMiningAlgorithm
我的演算法庫:https://github.com/linyiqun/lyq-algorithms-lib
相關文章
- 蟻群演算法實現TSP(旅行商)問題(java)演算法Java
- 蟻群演算法(ACO)演算法
- 蟻群演算法java實現以及TSP問題蟻群演算法求解演算法Java
- 遺傳演算法解決旅行商問題(TSP)演算法
- 蟻群演算法介紹(以TSP問題為例)演算法
- 演算法問題基於蟻群演算法求解求解TSP問題(JAVA)演算法Java
- 旅行商問題(TSP)概述
- 遺傳演算法解決TSP問題演算法
- 【蟻群演算法】演算法
- 驚群問題|復現|解決
- 基於ACO蟻群最佳化演算法的WSN網路路由最佳化matlab模擬演算法路由Matlab
- 遺傳演算法求解TSP問題(python版)演算法Python
- 解密非凡的蟻群演算法解密演算法
- 蟻群演算法的特點演算法
- 基於ACO蟻群最佳化的UAV最優巡檢路線規劃演算法matlab模擬演算法Matlab
- 智慧演算法---蟻群演算法介紹演算法
- 蟻群演算法原理以及應用演算法
- 蟻群演算法理論介紹演算法
- 10分鐘搞懂蟻群演算法演算法
- 攬貨最短路徑解決方案演算法 - C# 蟻群優化演算法實現演算法C#優化
- Python程式設計實現蟻群演算法詳解Python程式設計演算法
- HDU 5067 Harry And Dig Machine:TSP(旅行商)Mac
- 基於ACO蟻群最佳化的VRPSD問題求解matlab模擬,輸出規劃路徑結果和滿載率VRMatlab
- 蟻群演算法的基本原理演算法
- 基於免疫演算法的TSP問題求解matlab模擬演算法Matlab
- 蟻群演算法原理及Matlab實現演算法Matlab
- 蟻群演算法原理及其實現(python)演算法Python
- hdu 4568 spfa 最短路演算法+旅行商問題演算法
- 解決「問題」,不要解決問題
- 普利姆演算法解決最短修路問題演算法
- 徹底解決pidgin群顯示null問題及無法輸入中文的問題Null
- 解決問題
- 模擬退火演算法Python程式設計(4)旅行商問題演算法Python程式設計
- 發現問題,解決問題
- 使用回溯演算法解決N皇后問題以及間隔排列問題演算法
- POJ 3311 Hie with the Pie:TSP(旅行商)【節點可多次經過】
- 使用貪心演算法解決集合覆蓋問題演算法
- “裝箱”問題的貪婪法解決演算法演算法