何為TSP問題?
旅行商問題 TSP(Travelling Salesman Problem)是數(shù)學(xué)領(lǐng)域中著名問題之一后众。
- 場景:一個旅行商人需要拜訪n個城市
- 條件:要選擇一個路徑能夠拜訪到所有的城市放闺,且每個城市只能被拜訪一次祟昭,最終回到起點
- 目標(biāo):求得的路徑路程為所有路徑可能之中的最小值
TSP問題被證明是NP完全問題,這類問題不能用精確算法實現(xiàn)怖侦,而需要使用相似算法篡悟。
TSP問題分為兩類:對稱TSP(Symmetric TSP)以及非對稱TSP(Asymmetric TSP)
對稱與非對稱TSP區(qū)別
本文解決的是對稱TSP
假設(shè):A表示城市A,B表示城市B础钠,D(A->B)為城市A到城市B的距離恰力,同理D(B->A)為城市B到城市A的距離
對稱TSP中,D(A->B) = D(B->A)旗吁,城市間形成無向圖
非對稱TSP中踩萎,D(A->B) ≠ D(B->A),城市間形成有向圖
什么情況下會出現(xiàn)非對稱TSP呢很钓?
現(xiàn)實生活中香府,可能出現(xiàn)單行線董栽、交通事故、機票往返價格不同等情況企孩,均可以打破對稱性锭碳。
何為爬山算法(Hill Climbing)?
爬山算法是一種局部擇優(yōu)的方法勿璃,采用啟發(fā)式方法擒抛。直觀的解釋如下圖:
爬山算法,顧名思義就是爬山补疑,找到第一個山峰的時候就停止歧沪,作為算法的輸出結(jié)果。所以莲组,爬山算法容易把局部最優(yōu)解A作為算法的輸出诊胞,而我們的目的是找到全局最優(yōu)解B。
如何讓算法有更大的可能性搜索到全局最優(yōu)解B呢锹杈?
如下圖所示撵孤,盡管在這個圖中的許多局部極大值,仍然可以使用模擬退火算法(Simulated Annealing)發(fā)現(xiàn)全局最大值竭望。
對于模擬退火的內(nèi)容在此不予贅述邪码,有機會另寫一文詳述,如感興趣請訪問大白話解析模擬退火算法
Java實現(xiàn)爬山算法解決旅行商問題
必要解釋詳見注釋
/**
* 城市實體類
* 實現(xiàn)計算城市間距離方法
* @author Frank CHEN
*
*/
public class City {
// 地球赤道半徑
private static final double ERATH_EQUATORIAL_RADIUS = 6378.1370D;
private static final double CONCVERT_DEGREES_TO_RADIANS = Math.PI / 180;
// 經(jīng)度
private double longitude;
// 緯度
private double latitude;
// 城市名
private String name;
public City(double longitude, double latitude, String name) {
super();
this.longitude = longitude;
this.latitude = latitude;
this.name = name;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
/**
* 計算傳入城市與當(dāng)前城市的實際距離
* @param city
* @return
*/
public double measureDistance(City city) {
double deltaLongitude = deg2rad(city.getLongitude() - this.getLongitude());
double deltaLatitude = deg2rad(city.getLatitude() - this.getLatitude());
double a = Math.pow(Math.sin(deltaLatitude / 2D), 2D)
+ Math.cos(deg2rad(this.getLatitude()))
* Math.cos(deg2rad(city.getLatitude()))
* Math.pow(Math.sin(deltaLongitude / 2D), 2D);
return ERATH_EQUATORIAL_RADIUS * 2D * Math.atan2(Math.sqrt(a), Math.sqrt(1D - a));
}
// 轉(zhuǎn)化為弧度
private double deg2rad(double deg) {
return deg * CONCVERT_DEGREES_TO_RADIANS;
}
}
此處根據(jù)經(jīng)緯度計算城市間距離的公式市框,請參考Calculate distance between two latitude-longitude points? (Haversine formula)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
/**
* 路徑實體類
* @author Frank CHEN
*
*/
public class Route {
private ArrayList<City> cities = new ArrayList<>();
/**
* 構(gòu)造方法
* 隨機打亂cities排序
* @param cities
*/
public Route(ArrayList<City> cities) {
this.cities.addAll(cities);
Collections.shuffle(this.cities);
}
public Route(Route route) {
for(City city : route.getCities()) {
this.cities.add(city);
}
}
/**
* 計算城市間總距離
* @return
*/
public double getTotalDistance() {
int citiesSize = this.cities.size();
double sum = 0D;
int index = 0;
while(index < citiesSize - 1) {
sum += this.cities.get(index).measureDistance(this.cities.get(index + 1));
index++;
}
sum += this.cities.get(citiesSize - 1).measureDistance(this.cities.get(0));
return sum;
}
public String getTotalStringDistance() {
String returnString = String.format("%.2f", this.getTotalDistance());
return returnString;
}
public ArrayList<City> getCities() {
return cities;
}
public void setCities(ArrayList<City> cities) {
this.cities = cities;
}
@Override
public String toString() {
return Arrays.toString(cities.toArray());
}
}
/**
* 實現(xiàn)爬山算法
* @author Frank CHEN
*
*/
public class HillClimbing {
// 最大迭代次數(shù)
public static final int ITERATIONS_BEFORE_MAXIMUM = 1500;
/**
* 尋找最短路徑
* @param currentRoute
* @return
*/
public Route findShortestRoute(Route currentRoute) {
Route adjacentRoute;
int iterToMaximumCounter = 0;
String compareRoutes = null;
while(iterToMaximumCounter < ITERATIONS_BEFORE_MAXIMUM) {
adjacentRoute = obtainAdjacentRoute(new Route(currentRoute));
if(adjacentRoute.getTotalDistance() <= currentRoute.getTotalDistance()) {
compareRoutes = "<= (更新)";
iterToMaximumCounter = 0;
currentRoute = new Route(adjacentRoute);
} else {
compareRoutes = "> (保持) - 迭代次數(shù) # " + iterToMaximumCounter;
iterToMaximumCounter++;
}
System.out.println(" | " + compareRoutes);
System.out.print(currentRoute + " | " + currentRoute.getTotalStringDistance());
}
System.out.println(" | 可能的最優(yōu)解");
return currentRoute;
}
/**
* 隨機交換兩個城市位置
* @param route
* @return
*/
public Route obtainAdjacentRoute(Route route) {
int x1 = 0, x2 = 0;
while(x1 == x2) {
x1 = (int) (route.getCities().size() * Math.random());
x2 = (int) (route.getCities().size() * Math.random());
}
City city1 = route.getCities().get(x1);
City city2 = route.getCities().get(x2);
// swap two stochastic cities
route.getCities().set(x2, city1);
route.getCities().set(x1, city2);
return route;
}
}
import java.util.ArrayList;
import java.util.Arrays;
/**
* 初始化城市數(shù)據(jù)
* main方法
* @author Frank CHEN
*
*/
public class Driver {
private ArrayList<City> initialCities = new ArrayList<City>(Arrays.asList(
new City(116.41667, 39.91667, "北京"),
new City(121.43333, 34.50000, "上海"),
new City(113.00000, 28.21667, "長沙"),
new City(106.26667, 38.46667, "銀川"),
new City(109.50000, 18.20000, "三亞"),
new City(112.53333, 37.86667, "太原"),
new City(91.00000, 29.60000, "拉薩"),
new City(102.73333, 25.05000, "昆明"),
new City(126.63333, 45.75000, "哈爾濱"),
new City(113.65000, 34.76667, "鄭州"),
new City(113.50000, 22.20000, "澳門")));
public static void main(String[] args) {
Driver driver = new Driver();
Route route = new Route(driver.initialCities);
new HillClimbing().findShortestRoute(route);
}
}
此處初始化數(shù)據(jù)源可以使用TSPLIB中所提供的數(shù)據(jù)霞扬,此程序大致闡述爬山算法的實現(xiàn)。
編寫于一個失眠夜
菜鳥一枚枫振,歡迎評論區(qū)相互交流喻圃,加速你我成長???。