模擬退火和遺傳算法求解TSP問題
摘要
本實驗使用局部搜索、模擬退火、遺傳算法(C/C++實現),對TSP問題進行搜索求解。
在實現算法、適當調參後,在選用的5個TSP問題中皆搜索得到誤差小於10%的路線。
1 導言
1.1 問題重述
選一個大於100個城市數的TSP問題:
-
局部搜索與模擬退火:
- 採用多種鄰域操作的局部搜索local search策略求解;
- 在局部搜索策略的基礎上,加入模擬退火simulated annealing策略,並比較兩者的效果;
- 要求求得的解不要超過最優值的10%,並能夠提供可視化圖形界面,觀察路徑的變化和交叉程度。
-
用遺傳算法求解TSP問題(問題規模等和模擬退火求解TSP實驗同),要求:
-
設計較好的交叉操作,並且引入多種局部搜索操作(可替換通常遺傳算法的變異操作)
-
和之前的模擬退火算法(採用相同的局部搜索操作)進行比較
-
得出設計高效遺傳算法的一些經驗,並比較單點搜索和多點搜索的優缺點。
-
1.2 TSP問題選擇
本實驗從TSPLIB中選擇了5個不同的TSP問題,分別爲:
TSP Name | Comment | DIMENSION | Optimal Solution |
---|---|---|---|
kroC100 | 100-city problem C (Krolak/Felts/Nelson) | 100 | 20749 |
ch150 | 150 city Problem (churritz) | 150 | 6528 |
tsp225 | A TSP problem (Reinelt) | 225 | 3919 |
a280 | drilling problem (Ludwig) | 280 | 2579 |
pcb442 | Drilling problem (Groetschel/Juenger/Reinelt) | 442 | 50778 |
1.3 思路設計
主要思想即上圖所示,詳細算法內容將在之後的部分說明。
1.4 結果簡覽
以下實驗結果以5次實驗取平均值,詳細結果分析將在之後的部分說明。
Summary
TSP Name | LocalSearch | Simulated Annealing | Genetic Algorithm |
---|---|---|---|
kroC100 | 22792.88 (Loss 9.85%) | 20851.4 (Loss 0.49%) | 21903.34(Loss 5.56%) |
ch150 | 7260.656 (Loss 11.22%) | 6560.936 (Loss 0.50%) | 7124.162 (Loss 9.13%) |
tsp225 | 4308.558 (Loss 9.94%) | 3968.232 (Loss 1.26%) | 4236.946 (Loss 8.11%) |
a280 | 2906.766 (Loss 12.7%) | 2612.91 (Loss 1.31%) | 2922.06 (Loss 13.3%) |
pcb442 | 57557.38(Loss 13.35%) | 51877.96 (Loss 2.17%) | 57649.48 (Loss 13.5%) |
在本次實驗實現的三種算法中,可以看到模擬退火算法的表現極佳,遺傳算法稍遜,局部搜索算法較次。
實驗中的數據比較也進行了可視化處理,將在之後的實驗結果中詳細分析。
2 實驗過程
2.1 TSPbase
TSPbase
是一個用於預處理TSP問題數據、爲高級搜索方法提供API的基類。
這裏給出頭部文件的實例,詳細實現可參見src/TSPbase.cpp
。
/****************************************************************
* FileName : TSPbase.h *
* Author : Karl *
* Created Date : June 5, 2020 *
* Updated : June 8, 2020 - Add Function *
* June 9, 2020 - Fix Bug *
* June 9, 2020 - Modify Perfomance *
* June 22, 2020 - Last Check *
*==============================================================*
* @Functions: *
* TSPbase::TSPbase(MAP_TYPE& origin) - Consructor. *
* TSPbase::distance(COORD city1, COORD city2) *
* - calculate EUC2D distance between city1 and city2. *
* TSPbase::currentCost() - calculate cost of `private` path. *
* TSPbase::currentCost(vector<int> path) *
* - calculate cost of path. *
* TSPbase::roulette() - generate a random decimal in (0, 1). *
* TSPbase::generateRandomPath() - generate a random path. *
* TSPbase::getOptCost() - return the best(least) cost so far.*
* TSPbase::updateOptCost(double new_dist) *
* - update current best cost with new_dist. *
* TSPbase::backUp() - back up current path. *
* TSPbase::recover() - recover current path with backup. *
*==============================================================*/
class TSPbase {
public:
...
protected:
int N; // Dimension
MAP_TYPE citys; // Citys map
double* dist_map; // Dists map
double opt_cost; // Optimal cost(least distance)
int* path; // Current path
int* city2path; // Map a city to its position in path
int* path_bak; // Path backup
int* city2path_bak; // Map backup
};
首先,對於一個TSP問題,分析需要考慮的內容:
- 城市總數,即問題的維度:
N
- 每個城市編號以及對應座標:
citys
- 路徑:
path
在讀入問題文件後,分析需要記錄、預計算的內容:
-
城市間距離:
dist_map
在搜索過程中,會非常頻繁地計算整條路徑的長度(歐氏距離),如果每次都要進行運算,將大大影響我們程序的效率,因此,預計算城市之間距離,靜態存到數組中,在計算路徑長度時以查表代替計算,能有效提高效率。
-
最優解:
opt_cost
-
城市編號到路徑的映射:
city2path
在搜索過程中,可能涉及城市權重等需要按城市順序存儲的數據,因此,在選中一個城市後,想在O(1)時間內定位到它在路徑中的位置,就需要建立一個從城市編號到路徑位置的對應關係。
對每一次搜索進行備份:
類似於常見的搜索算法,噹噹前搜索結果並不如意,我們需要回溯到上一狀態,這就要求TSPbase能夠提供備份、恢復的功能。
- 路徑的備份:
path_bak
- 城市編號到路徑的映射的備份:
city2path_bak
注:
TSPbase提供的方法API在文件頭部註釋中有詳細介紹,且命名是具有可讀性的,在此不展開篇幅進行介紹。
2.2 LocalSearch
LocalSearch
是基於TSPbase實現的局部搜索算法,爲優化效果,加入了以下特性:
- 加入了滿意度、活躍度機制
- 提前終止
以下對局部搜索算法進行詳細介紹:
2.2.1 流程圖
2.2.2 滿意度、活躍度機制
該機制參考於論文《求解TSP問題的自適應領域搜索法及其擴展》
滿意度
直觀地,對每個城市而言,最佳位置是該城市和與之相距最近的兩個城市相連。
在選擇城市
的時候,我們自然希望所有城市儘可能在自己的最佳位置。爲了判斷一個城市所在位置是否足夠令人滿意,加入了滿意度(, 第i個城市的滿意度)的定義:
對城市i以及與之相連的城市j、k,以及距離城市i最近的兩個城市s和城市t:
由此可見,滿意度是一個在區間(0, 1]
內變化的數,當滿意度爲1時,說明該城市已達到最佳狀態。
活躍度
爲了避免某些滿意度低的城市經過多次操作仍然難以提高滿意度,從而導致算法始終只操作某些低滿意度城市、忽略滿意度相對高的城市,陷入局部最優解的情況,引入活躍度進行限制:
對城市i:
城市權重
當某些低滿意度的城市被頻繁操作後,它們的滿意度會大幅上升,此時按照權重函數:
能避免對同一個城市過於頻繁操作或根本不對某些城市進行操作的情況。
2.2.3 鄰域操作
鄰域定義
對每個城市,其鄰域定義爲:
對城市i,找到距離它次近的城市t,對於其他城市j,若滿足上述條件,則判定城市j在其鄰域中。
操作 - 交換兩個城市
樸素操作,當變換能夠優化路徑,則保留。
因此容易陷入局部最優解。
操作 - 逆轉一段序列
通過逆轉序列操作,能夠優化上述情況。
操作 - 3-opt
3-opt操作能產生以上7種新情況,從中選擇效果最好的一種。
2.2.4 代碼分析
這裏給出頭部文件的實例,詳細實現可參見src/LocalSearch.cpp
。
/****************************************************************
* FileName : LocalSearch.h *
* Author : Karl *
* Created Date : June 5, 2020 *
* Updated : June 8, 2020 - Add Function *
* June 9, 2020 - Fix Bug *
* June 9, 2020 - Modify Perfomance *
* June 12, 2020 - Add satification *
* and activity *
*==============================================================*
* @Functions: *
* LocalSearch::LocalSearch(MAP_TYPE& origin, int LOOP_LIMITS,*
* int LOOSE_COEF, int EARLY_STOP, *
* int VERBOSE) - Consructor. *
* LocalSearch::chooseNode() *
* - choose a Node according to its weight. *
* LocalSearch::chooseNode(int base, int range) *
* - choose a Node within (base - range, base + range) *
* LocalSearch::propagateWeightChange(int node_num, int value)*
* - propagate weight change of node`node_num`. *
* LocalSearch::search() *
* - do neighbour operation to search for a new solution. *
* LocalSearch::checkPath() - check `private` path's validity.*
* LocalSearch::checkPath(vector<int> path) *
* - check path's validity. *
* LocalSearch::earlyStop() - check whether to early stop. *
* LocalSearch::run() - start LocalSearch process. *
* LocalSearch::report() - save result to files. *
* LocalSearch::exchangeTwoNode(int pos, vector<int>& p) *
* - Neighbour operation: exchange two node(city). *
* LocalSearch::reverseSequence(int pos, vector<int>& p) *
* - Neighbour operation: reverse a sub-sequence. *
* LocalSearch::popToFront(int pos, vector<int>& p) *
* - Neighbour operation: pop a node to front. *
*==============================================================*/
class LocalSearch: public TSPbase {
public:
...
protected:
int LOOP_LIMITS; // Maximum Loop Times
int EARLY_STOP; // Early Stop Epoch
int VERBOSE; // Verbose Message
int early_stop_counter; // Early Stop Counter
double LOOSE_COEF; // Coefficient to contorl loose op.
double delta_d; // parameter
double delta_v; // parameter
double* node_weights; // Weight for selection
double* node_satisfication; // Satisfication for cities
double* node_active_value; // Activy for cities
vector<Individual> opt_hist; // History of optimal cost
vector<nop> n_ops; // Vector of neighbour operations
ADJ_MAP_TYPE closest_city; // 2 Closest citys
ADJ_MAP_TYPE adj_city; // Adjacent citys
};
2.3 SimulatedAnnealing
SimulatedAnnealing
是基於LocalSearch
實現的模擬退火算法。
與局部搜索不同的是:
- 算法的結束由溫度控制
- 對於比當前解差的解也有機會選用,選用概率與兩個解差值以及溫度有關,溫度越低,接受概率越低
- 在高溫狀態下將會劇烈震盪,只有溫度降低後才呈現收斂趨勢並逐漸收斂。
2.3.1 流程圖
2.3.2 代碼分析
這裏給出頭部文件的實例,詳細實現可參見src/LocalSearch.cpp
。
/****************************************************************
* FileName : SimulatedAnnealing.h *
* Author : Karl *
* Created Date : June 6, 2020 *
* Updated : June 11, 2020 - Add Function *
* June 11, 2020 - Fix Bug *
* June 12, 2020 - Modify Perfomance*
* June 22, 2020 - Last Check *
*==============================================================*
* @Functions: *
* SimulatedAnnealing::SimulatedAnnealing(MAP_TYPE& origin, *
* double LOOSE_COEF, double TEMP_INIT, *
* double TEMP_END, int LOOPS_PER_TEMP, *
* double ANNEALING_COEF, int VERBOSE) *
* - Consructor. *
* SimulatedAnnealing::runSA() - start Simulated Annealing. *
* SimulatedAnnealing::run() - override run() in LocalSearch. *
* SimulatedAnnealing::report() - save result to files. *
* *
* SimulatedAnnealing::Metropolis(int pos, vector<int>& p) *
* - criterion for acceptance of worse solution. *
*==============================================================*/
class SimulatedAnnealing: public LocalSearch {
public:
...
private:
double TEMP_INIT; // Initial temperature
double TEMP_END; // Terminal temperature
int LOOPS_PER_TEMP; // Loop times per temperature do
double ANNEALING_COEF; // Coefficient to control annealing speed
};
模擬退火的溫度控制機制由以下屬性控制:
- 初始溫度:
TEMP_INIT
- 終止溫度:
TEMP_END
- 退火係數:
ANNEALING_COEF
每次循環結束後,新溫度 = 當前溫度 * 退火係數
,實現降溫。
2.4 GeneticAlgorithm
2.4.1 流程圖
2.4.2 交叉與變異算子
遺傳算法是模擬生物種羣繁衍的一種搜索策略,其中最關鍵的即爲基因
的交叉與變異操作。
交叉算子
基因的交叉即,兩個個體基因的片段進行交換,在TSP問題中,基因即是一種可能的路徑排列,基因交叉的過程,即將兩條路徑的一部分進行交換。
按以下流程進行交叉:
- 遍歷整個羣體,每個個體隨機選擇一個其他個體進行交叉(不與相鄰的個體進行交叉以避免近親交配)
- 每個個體按輪盤賭策略,隨機產生一個概率,只有該概率超過預定的交叉概率時才允許交叉
- 交叉後處理衝突
- 更新新個體的路徑代價
衝突處理:
由於一條路徑中,城市是唯一的,所以基因的交叉可能導致非法路徑的出現:
Path1 - 1 2 3 4 5 6 7 8 9
Path2 - 1 2 9 6 3 4 7 8 5
在位置2到4進行交叉:
Path1 - 1 2 9 6 5 6 7 8 9 - 城市9,6衝突
Path2 - 1 2 3 4 3 4 7 8 5 - 城市3,4衝突
分別遍歷交叉後的兩條路徑,每條路徑在訪問到重複城市時,暫停等候另一條路徑也訪問到重複城市(原路徑內城市編號唯一,因此當一條路徑出現重複時,另一路徑一定也會出現重複);兩條路徑都暫停後,交換這兩個城市即可。
Function crossOver:
elite_size:= POPULATION_SIZE * 0.2;
i:= elite_size;
while i < POPULATION_SIZE - 2
get random `prob` by roulette()
if prob <= CROSSOVER_PROB:
j:= get another unit j randomly
cross_point: = choose cross_point randomly
cross(population[i], population[j], cross_point);
end if
solveCrossOverConflict(population[i], population[j]);
update cost of i and j
end while
end
Function cross(UNIT i1, UNIT i2, pos):
copy i2[pos:] to i1[pos:]
copy i1[pos:] to i2[pos:]
end
Function solveCrossOverConflict(UNIT i1, UNIT i2):
i:= 0, j:= 0, N:= Numbers of City;
while i < N and j < N:
if i1[i] is visited and i2[j] is visited:
swap i1[i] and i2[j]
i++, j++;
end if
if i < N and i1[i] is not visited:
note i1[i] as visited, i++;
end if
if j < N and i2[j] is not visited:
note i2[j] as visited, j++;
end if
end while
end
變異算子
基因的變異即,路徑內部發生非正常的變化,本實驗中採用局部搜索中鄰域操作作爲變異的操作。
爲提高收斂速度,只有優秀的變異會被採納。
按以下流程進行變異:
- 遍歷整個羣體
- 每個個體按輪盤賭策略,隨機產生一個概率,只有該概率超過預定的變異概率時才允許變異
- 全部個體完成變異後,只有使個體變優秀的變異被採用
Function mutation(last_population):
elite_size:= POPULATION_SIZE * 0.2;
for i from elite_size to POPULATION_SIZE-1:
get random `prob` by roulette();
if prob <= MUTATION_PROB:
mutate(population[i]);
end if
end for
for i from elite_size to POPULATION_SIZE-1:
if newcost[i] is not better than oldcost[i]:
Unit i is recoverd by old unit;
end if
end for
end
Function mutate(UNIT ind):
get random `prob` by roulette();
buildCity2Path(city2path, ind.second);
get random `pos` by roulette();
if prob < 0.25:
neighbour operation 0 at `pos`;
else if prob < 0.5:
neighbour operation 1 at `pos`;
else if prob < 1:
neighbour operation 2 at `pos`;
end if
update cost of unit ind;
end
2.4.3 評分機制及精英保留策略
評分是基於TSP問題的最優解以及當前路徑長度設定的:
精英保留策略:
在每一代中,將表現最好(路徑代價最小、評分最高)的個體優先保留20%,在後續隨機選取個體時就不再進行選擇。這些精英個體不會主動進行交叉、變異操作,但可以接受其他個體的交叉請求。
2.4.4 代碼分析
/****************************************************************
* FileName : GeneticAlgorithm.h *
* Author : Karl *
* Created Date : June 12, 2020 *
* Updated : June 13, 2020 - Modify Perfomance*
* June 23, 2020 - Last Check *
*==============================================================*
* @Functions: *
* GeneticAlgorithm::GeneticAlgorithm(MAP_TYPE& origin, *
* int LOOP_LIMITS, int POPULATION_SIZE, *
* int GENERATIONS, int MUTATION_TIMES = 3, *
* double CROSSOVER_PROB = 0.7, *
* double MUTATION_PROB = 0.2, *
* double OPT_COST = 0.0, int VERBOSE = 0) *
* - Consructor. *
* GeneticAlgorithm::runGA() - start Genetic Algorithm. *
* GeneticAlgorithm::run() - override run() in LocalSearch. *
* GeneticAlgorithm::init() - Initialization. *
* GeneticAlgorithm::keepBest() - Keep the elite, the elite *
* never crossover or mutate. *
* GeneticAlgorithm::selectUnit() - select from population. *
* GeneticAlgorithm::crossOver() - crossoveer genetically. *
* GeneticAlgorithm::cross(UNIT& i1, UNIT& i2, int pos) *
* - crossover between i1 and i2. *
* GeneticAlgorithm::solveCrossOverConflict() *
* - solve conflicts(same city in 1 path)*
* GeneticAlgorithm::mutation(vector<UNIT> last_population) *
* - mutate genetically. *
* GeneticAlgorithm::mutate(UNIT& ind) - unit ind mutates. *
* GeneticAlgorithm::score(vector<int>& p) *
* - calculate score of path `p`. *
* GeneticAlgorithm::evaluate() - evaluate current population.*
* GeneticAlgorithm::checkPath() - check path's validity. *
* GeneticAlgorithm::getElite() - return elite solution. *
* GeneticAlgorithm::report() - save result to files. *
*==============================================================*/
class GeneticAlgorithm: public LocalSearch {
public:
...
private:
int POPULATION_SIZE; // The size of the population
int GENERATIONS; // Iterations the population will do
int MUTATION_TIMES; // Times to try to mutate per loop
int GA_VERBOSE; // GA Verbose Message
double CROSSOVER_PROB; // The probability to crossover
double MUTATION_PROB; // The probability to mutate
double OPT_COST; // The optimal cost of the tsp
UNIT ELITE; // The best unit in the population
vector<UNIT> elite_hist; // History of elites
vector<UNIT> population; // Population of units
vector<double> scores; // Scores to evaluate unit
vector<double> chance_by_score; // Accumulation of scores
};
3 結果分析
3.1 實驗環境
3.1.1 系統信息
OS | Ubuntu 18.04.4 LTS |
---|---|
CPU | Intel® Core™ i7-7700HQ CPU @ 2.80GHz |
3.1.2 開發工具
- Vscode + make
- gcc/g++ 7
- python 3.7
3.2 編譯運行
3.2.1 初始狀態
$ tree ./LocalSearch
.
├── bin
├── src
│ ├── LocalSearch.cpp
│ ├── LocalSearch.h
│ ├── localsearch_main.cpp
│ ├── Makefile
│ ├── TSPbase.cpp
│ └── TSPbase.h
└── testcases
└── ...
9 directories, 21 files
---
$ tree ./SimulatedAnnealing
.
├── bin
├── src
│ ├── LocalSearch.cpp
│ ├── LocalSearch.h
│ ├── Makefile
│ ├── SimulatedAnnealing.cpp
│ ├── SimulatedAnnealing.h
│ ├── simulatedannealing_main.cpp
│ ├── TSPbase.cpp
│ └── TSPbase.h
└── testcases
└── ...
7 directories, 26 files
---
$ tree ./GeneticAlgorithm
.
├── bin
├── src
│ ├── GeneticAlgorithm.cpp
│ ├── GeneticAlgorithm.h
│ ├── geneticalgorithm_main.cpp
│ ├── LocalSearch.cpp
│ ├── LocalSearch.h
│ ├── Makefile
│ ├── TSPbase.cpp
│ └── TSPbase.h
└── testcases
└── ...
5 directories, 32 files
3.2.2 編譯
在三種算法各自文件夾內:
$ cd src/ && make
示例:LocalSearch的Makefile
CXX = g++-7
CXXFLAGS = -Wall -Werror -Wextra -pedantic -std=c++17 -g -fsanitize=address
LDFLAGS = -fsanitize=address
SRC = localsearch_main.cpp TSPbase.cpp LocalSearch.cpp
OBJ = $(SRC:.cpp=.o)
EXEC = ../bin/local_search
all: $(EXEC)
$(EXEC): $(OBJ)
$(CXX) $(LDFLAGS) -o $@ $(OBJ) $(LBLIBS)
clean:
rm -rf $(OBJ) $(EXEC)
g++-7 -Wall -Werror -Wextra -pedantic -std=c++17 -g -fsanitize=address -c -o localsearch_main.o localsearch_main.cpp
g++-7 -Wall -Werror -Wextra -pedantic -std=c++17 -g -fsanitize=address -c -o TSPbase.o TSPbase.cpp
g++-7 -Wall -Werror -Wextra -pedantic -std=c++17 -g -fsanitize=address -c -o LocalSearch.o LocalSearch.cpp
g++-7 -fsanitize=address -o ../bin/local_search localsearch_main.o TSPbase.o LocalSearch.o
3.2.3 運行及自定參數
-
切換到
bin/
文件夾$ ./local_search Invalid Usage. >> ./local_search [TSP_FILE_NAME] e.g.: >> ./local_search a280
-
按照使用格式調用,此處以a280問題爲例
需要進行搜索的問題需要提前將文件
[tsp_name].tsp
以及[tsp_name].opt.tour
複製到testcases/
中。
$ ./local_search a280
<img src="https://img-blog.csdnimg.cn/2020062509291792.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDAwNTMyOQ==,size_16,color_FFFFFF,t_70" alt="在這裏插入圖片描述" style="zoom:50%;" /> <img src="https://img-blog.csdnimg.cn/20200625092916899.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDAwNTMyOQ==,size_16,color_FFFFFF,t_70" alt="在這裏插入圖片描述" style="zoom: 67%;" />
在程序運行後,接受參數或回車使用參數缺省值,再次回車確認將開始搜索。
相關參數已在命令行中說明。
```bash
$ ./simulated_annealing a280
$ ./genetic_algorithm a280
3.3.4 結果可視化
每次運行完程序,結果將保存在 testcases/result.[method].tour
(搜索得到的最佳路徑)以及testcases/result.[method].hist
(搜索記錄)中。
本實驗採用Python + matplotlib進行數據可視化:
$ cd testcases/
$ python Benchmarker.py [Method(ls/sa/ga)] [TSP_FILE_NAME]
# e.g. python Benchmarker.py ls a280
Python腳本會創建文件夾:[TSP_FILE_NAME].[method].benchmarker.out/
,其中包括歷史記錄圖示、路線變化GIF,以及最優解對比圖等。
3.3 搜索結果及比較
3.3.1 實驗數據對比
Local Search
TSP | 1st | 2nd | 3rd | 4rd | 5rd | AVE |
---|---|---|---|---|---|---|
kroC100 | 22523.3 | 22667.5 | 24123.4 | 22938.9 | 21711.3 | 22792.88 (Loss 9.85%) |
ch150 | 6929.42 | 7696.9 | 7076.38 | 6996.5 | 7604.08 | 7260.656 (Loss 11.22%) |
tsp225 | 4276.71 | 4404.6 | 4344.26 | 4229.38 | 4287.84 | 4308.558 (Loss 9.94%) |
a280 | 2951.92 | 2888.39 | 2902.9 | 2918.46 | 2872.16 | 2906.766 (Loss 12.7%) |
pcb442 | 58725.1 | 57542.1 | 57856.8 | 56568.1 | 57094.8 | 57557.38(Loss 13.35%) |
Simulated Annealing
TSP | 1st | 2nd | 3rd | 4rd | 5rd | AVE |
---|---|---|---|---|---|---|
kroC100 | 20871.4 | 20871.4 | 20750.8 | 20993.5 | 20769.9 | 20851.4 (Loss 0.49%) |
ch150 | 6553.66 | 6563.02 | 6563.02 | 6559.6 | 6565.38 | 6560.936 (Loss 0.50%) |
tsp225 | 3973.63 | 3948.07 | 3964.74 | 3972.09 | 3973.63 | 3968.232 (Loss 1.26%) |
a280 | 2590.18 | 2634.79 | 2590.18 | 2597.54 | 2651.87 | 2612.91 (Loss 1.31%) |
pcb442 | 51468.7 | 52130.3 | 51981.3 | 52061.9 | 51747.6 | 51877.96 (Loss 2.17%) |
Genetic Algorithm
TSP | 1st | 2nd | 3rd | 4rd | 5rd | AVE |
---|---|---|---|---|---|---|
kroC100 | 21817.3 | 22212.8 | 22108.4 | 21560.9 | 21817.3 | 21903.34(Loss 5.56%) |
ch150 | 7162.96 | 7205.97 | 7033.58 | 7109.15 | 7109.15 | 7124.162 (Loss 9.13%) |
tsp225 | 4222.78 | 4205.8 | 4258.63 | 4256.1 | 4241.42 | 4236.946 (Loss 8.11%) |
a280 | 3005.36 | 2907.37 | 2865.11 | 2931.69 | 2900.76 | 2922.06 (Loss 13.3%) |
pcb442 | 58931 | 56844.7 | 57073.4 | 58810 | 56588.3 | 57649.48 (Loss 13.5%) |
summary
TSP Name | LocalSearch | Simulated Annealing | Genetic Algorithm |
---|---|---|---|
kroC100 | 22792.88 (Loss 9.85%) | 20851.4 (Loss 0.49%) | 21903.34(Loss 5.56%) |
ch150 | 7260.656 (Loss 11.22%) | 6560.936 (Loss 0.50%) | 7124.162 (Loss 9.13%) |
tsp225 | 4308.558 (Loss 9.94%) | 3968.232 (Loss 1.26%) | 4236.946 (Loss 8.11%) |
a280 | 2906.766 (Loss 12.7%) | 2612.91 (Loss 1.31%) | 2922.06 (Loss 13.3%) |
pcb442 | 57557.38(Loss 13.35%) | 51877.96 (Loss 2.17%) | 57649.48 (Loss 13.5%) |
3.3.2 實驗數據可視化
3.3.3 路線對比
Local Search
Simulated Annealing
Genetic Algorithm
3.4 性能分析
TSP Name | LocalSearch | Simulated Annealing | Genetic Algorithm |
---|---|---|---|
kroC100 | 22792.88 (Loss 9.85%) | 20851.4 (Loss 0.49%) | 21903.34(Loss 5.56%) |
ch150 | 7260.656 (Loss 11.22%) | 6560.936 (Loss 0.50%) | 7124.162 (Loss 9.13%) |
tsp225 | 4308.558 (Loss 9.94%) | 3968.232 (Loss 1.26%) | 4236.946 (Loss 8.11%) |
a280 | 2906.766 (Loss 12.7%) | 2612.91 (Loss 1.31%) | 2922.06 (Loss 13.3%) |
pcb442 | 57557.38(Loss 13.35%) | 51877.96 (Loss 2.17%) | 57649.48 (Loss 13.5%) |
Time | around 10s | 150 ~ 300s | 300s |
[回顧實驗數據](####3.3 搜索結果及比較)
算法精度
再次參考實驗結果表,可以看到,模擬退火算法的效果最爲突出,誤差都在**3%**以內,另外兩個算法表現一般,誤差子10%上下波動。
算法效率
同時,除去精度,三個算法的速度也各有區別,模擬退火以及遺傳算法的耗時較高,局部搜索耗時較低;
這是由於算法性質導致的,存在優化空間。
路徑的變化與交叉
首先,隨機初始化的路徑往往是充滿大量交叉路徑,使得路徑代價大大提高,各種領域操作搜索的原理實質上是在儘量消除路徑代價。
爲了充分觀察在搜索過程中路徑的變化,我將實驗過程中的路徑變化取樣並且壓在GIF圖中,並建立了網頁示例,可訪問DEMO中查看(GIF文件較大,需要時間加載)。
3.5 探索與展望
-
嘗試更多鄰域操作。
-
嘗試不同的參數而不是設定的默認參數(默認參數已經過調試、修訂)。
-
嘗試將搜索並行化,遺傳算法顯然是可並行的。
-
探究遺傳算法效果不佳的原因(概率的設定?種羣的大小?迭代次數?交叉變異操作?精英策略以外的策略?如何避免陷入局部最優?)
-
提高模擬退火算法的效率(如何選擇一個更合適的退火係數?)
對於默認初始溫度50000度,默認終止溫度1e-5,以0.99的退火係數需要約2200次降溫,如何設定退火係數值得推敲。
4 結論
模擬退火算法、遺傳算法,對於計算機專業的學生來說並不陌生,只是在實驗前都是紙上談兵。
在本次實驗中,從邏輯設計開始,完成了三個搜索算法的C/C++實現,雖然細節手法仍然稚嫩,性能、效率上都沒有做到最優,但對了解算法、瞭解TSP問題幫助極大。
實驗要求中相關問題
-
(模擬退火)求得的解不要超過最優值的10%,並能夠提供可視化圖形界面,觀察路徑的變化和交叉程度。
求得的解精度在3%以內,效果很好。
可視化圖形界面由python實現,路徑的變化和交叉程度在上文已經進行了介紹。
-
(遺傳算法)和之前的模擬退火算法(採用相同的局部搜索操作)進行比較
效果不如模擬退火算法好,可能出現的問題已在上文進行了介紹。
-
(遺傳算法)得出設計高效遺傳算法的一些經驗,並比較單點搜索和多點搜索的優缺點。
在概率的設定、種羣的大小、迭代次數、交叉變異操作、精英策略以外的策略、避免陷入局部最優上需要多下功夫。
單點搜索優點:速度快、易收斂、實現簡單。
單點搜索缺點:容易陷入局部最優解。
多點搜索優點:模擬自然規律,直觀上更可信,能更好避免局部最優解。
多點搜索缺點:速度慢、實現複雜、魯棒性不足(不同TSP問題應該對應的自然模型可能不同)。
關於實驗評分
-
所有實驗成果必須原創。允許提交半成品、失敗品但絕不允許提交複製品。
本實驗代碼原創,基本完成功能,仍有改進空間(並行化、算法改進等)。
-
實驗程序對正確的輸入有正確的輸出。
程序要求tsp問題文件置於
testcases/
中,並在調用程序是使用正確的tsp問題名,如:$ ls testcases/ a280.tsp a280.opt.tour $ ./local_search a280
-
提交實驗結果,完整齊全。
實驗結果已在前文展示。
-
源代碼編寫符合編碼規範,可讀性高。
源代碼符合C/C++ google style,函數方法在文件頭有說明,且部分函數是self-explainable的。
-
源代碼邏輯清晰。
邏輯已在前文說明。
-
程序具有魯棒性。
對於任意挑出的五個tsp問題,程序都能很好處理,具有足夠的魯棒性。
同時,對於一些可能的錯誤輸入、文件格式問題,都在程序內進行了提示性報錯。
-
界面友好。
未單獨繪製GUI界面,而是以CMD形式提供交互界面;對於輸出的結果可視化提供了python腳本,無需另外處理實驗結果。
5 主要參考文獻
- 趙雪梅. 遺傳算法及其在TSP問題求解中的應用[J]. 四川兵工學報(11):22-27.
- 王銀年. 遺傳算法的研究與應用[D]. 江南大學.
- 範展, 梁國龍, 林旺生,等. 求解TSP問題的自適應鄰域搜索法及其擴展[J]. 計算機工程與應用, 2008(12):75-78.
- Song, Chi-Hwa, Kyunghee Lee, and Won Don Lee. “Extended simulated annealing for augmented TSP and multi-salesmen TSP.” Proceedings of the International Joint Conference on Neural Networks, 2003.. Vol. 3. IEEE, 2003.
- Chi-Hwa Song, Kyunghee Lee and Won Don Lee, “Extended simulated annealing for augmented TSP and multi-salesmen TSP,” Proceedings of the International Joint Conference on Neural Networks, 2003., Portland, OR, 2003, pp. 2340-2343 vol.3, doi: 10.1109/IJCNN.2003.1223777.
Karl 2020/6