局部搜索、模拟退火和遗传算法求解TSP问题
模拟退火和遗传算法求解TSP问题
源代码传送门:GITHUB
数据传送门:TSPLIB
文章目录
- 模拟退火和遗传算法求解TSP问题
- 摘要
- 1 导言
- 1.1 问题重述
- 1.2 TSP问题选择
- 1.3 思路设计
- 1.4 结果简览
- 2 实验过程
- 2.1 TSPbase
- 2.2 LocalSearch
- 2.2.1 流程图
- 2.2.2 满意度、活跃度机制
- 2.2.3 邻域操作
- 2.2.4 代码分析
- 2.3 SimulatedAnnealing
- 2.3.1 流程图
- 2.3.2 代码分析
- 2.4 GeneticAlgorithm
- 2.4.1 流程图
- 2.4.2 交叉与变异算子
- 2.4.3 评分机制及精英保留策略
- 2.4.4 代码分析
- 3 结果分析
- 3.1 实验环境
- 3.1.1 系统信息
- 3.1.2 开发工具
- 3.2 编译运行
- 3.2.1 初始状态
- 3.2.2 编译
- 3.2.3 运行及自定参数
- 3.3.4 结果可视化
- 3.3 搜索结果及比较
- 3.3.1 实验数据对比
- 3.3.2 实验数据可视化
- 3.3.3 路线对比
- 3.4 性能分析
- 3.5 探索与展望
- 4 结论
- 5 主要参考文献
摘要
本实验使用局部搜索、模拟退火、遗传算法(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; // DimensionMAP_TYPE citys; // Citys mapdouble* dist_map; // Dists mapdouble opt_cost; // Optimal cost(least distance)int* path; // Current pathint* city2path; // Map a city to its position in pathint* path_bak; // Path backupint* 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问题的自适应领域搜索法及其扩展》
满意度
直观地,对每个城市而言,最佳位置是该城市和与之相距最近的两个城市相连。
在选择城市
的时候,我们自然希望所有城市尽可能在自己的最佳位置。为了判断一个城市所在位置是否足够令人满意,加入了满意度(fif_ifi, 第i个城市的满意度)的定义:
对城市i以及与之相连的城市j、k,以及距离城市i最近的两个城市s和城市t:
fi=exp[−(dij+dik−dis−dit)22δ2]fi:第i个城市的满意度dXY:城市X与城市Y之间的距离δ:参数f_i=exp[-\dfrac{(d_{ij}+d_{ik}-d_{is}-d_{it})^2}{2\delta^2}]\\ f_i:第i个城市的满意度\\ d_{XY}:城市X与城市Y之间的距离\\ \delta:参数 fi=exp[−2δ2(dij+dik−dis−dit)2]fi:第i个城市的满意度dXY:城市X与城市Y之间的距离δ:参数
由此可见,满意度是一个在区间(0, 1]
内变化的数,当满意度为1时,说明该城市已达到最佳状态。
活跃度
为了避免某些满意度低的城市经过多次操作仍然难以提高满意度,从而导致算法始终只操作某些低满意度城市、忽略满意度相对高的城市,陷入局部最优解的情况,引入活跃度进行限制:
对城市i:
Vi(t+1)=ηVi(t)+ΔVhi(t+1)=1−exp[−(Vi(t+1))22σ2]Vi(t):t时刻的信息素η:松弛系数,取值(0,1),未进行操作的城市将缓慢降低活跃度hi(t):t时刻的活跃度ΔV:若城市i进行了反转操作,则ΔV=Nσ(1−η)K,K为反转操作的城市数量否则,ΔV=0V_i(t+1)=\eta V_i(t)+\Delta V\\ h_i(t+1) = 1-exp[-\dfrac{(V_i(t+1))^2}{2\sigma^2}]\\ V_i(t):t时刻的信息素\\ \eta:松弛系数,取值(0,1),未进行操作的城市将缓慢降低活跃度\\ h_i(t):t时刻的活跃度\\ \Delta V:若城市i进行了反转操作,则\Delta V=\dfrac{N\sigma(1-\eta)}{K},K为反转操作的城市数量\\ 否则,\Delta V = 0 Vi(t+1)=ηVi(t)+ΔVhi(t+1)=1−exp[−2σ2(Vi(t+1))2]Vi(t):t时刻的信息素η:松弛系数,取值(0,1),未进行操作的城市将缓慢降低活跃度hi(t):t时刻的活跃度ΔV:若城市i进行了反转操作,则ΔV=KNσ(1−η),K为反转操作的城市数量否则,ΔV=0
城市权重
当某些低满意度的城市被频繁操作后,它们的满意度会大幅上升,此时按照权重函数:
Pi(t)=[1−fi(t)][1−hi(t)]P_i(t)=[1-f_i(t)][1-h_i(t)] Pi(t)=[1−fi(t)][1−hi(t)]
能避免对同一个城市过于频繁操作或根本不对某些城市进行操作的情况。
2.2.3 邻域操作
邻域定义
对每个城市,其邻域定义为:
dij≤dit+ΔdΔd=3N∑i=1Ndidi:城市i与相距最近的城市距离d_{ij}\leq d_{it}+\Delta d\\ \Delta d = \frac{3}{N}\sum^N_{i=1}d_i\\ d_i:城市i与相距最近的城市距离 dij≤dit+ΔdΔd=N3i=1∑Ndidi:城市i与相距最近的城市距离
对城市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 Timesint EARLY_STOP; // Early Stop Epochint VERBOSE; // Verbose Messageint early_stop_counter; // Early Stop Counterdouble LOOSE_COEF; // Coefficient to contorl loose op.double delta_d; // parameterdouble delta_v; // parameterdouble* node_weights; // Weight for selectiondouble* node_satisfication; // Satisfication for citiesdouble* node_active_value; // Activy for citiesvector<Individual> opt_hist; // History of optimal costvector<nop> n_ops; // Vector of neighbour operationsADJ_MAP_TYPE closest_city; // 2 Closest citysADJ_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 temperaturedouble TEMP_END; // Terminal temperatureint LOOPS_PER_TEMP; // Loop times per temperature dodouble 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 - 2get random `prob` by roulette()if prob <= CROSSOVER_PROB:j:= get another unit j randomlycross_point: = choose cross_point randomlycross(population[i], population[j], cross_point);end ifsolveCrossOverConflict(population[i], population[j]);update cost of i and jend while
endFunction cross(UNIT i1, UNIT i2, pos):copy i2[pos:] to i1[pos:]copy i1[pos:] to i2[pos:]
endFunction 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 ifif i < N and i1[i] is not visited:note i1[i] as visited, i++;end ifif j < N and i2[j] is not visited:note i2[j] as visited, j++;end ifend 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 ifend forfor 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 ifend for
endFunction 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 ifupdate cost of unit ind;
end
2.4.3 评分机制及精英保留策略
评分是基于TSP问题的最优解以及当前路径长度设定的:
Score(Unit)=1cur_len−optimal_lenScore(Unit) = \dfrac{1}{cur\_len-optimal\_len} Score(Unit)=cur_len−optimal_len1
精英保留策略:
在每一代中,将表现最好(路径代价最小、评分最高)的个体优先保留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 populationint GENERATIONS; // Iterations the population will doint MUTATION_TIMES; // Times to try to mutate per loopint GA_VERBOSE; // GA Verbose Messagedouble CROSSOVER_PROB; // The probability to crossoverdouble MUTATION_PROB; // The probability to mutatedouble OPT_COST; // The optimal cost of the tspUNIT ELITE; // The best unit in the populationvector<UNIT> elite_hist; // History of elitesvector<UNIT> population; // Population of unitsvector<double> scores; // Scores to evaluate unitvector<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=addressSRC = localsearch_main.cpp TSPbase.cpp LocalSearch.cpp
OBJ = $(SRC:.cpp=.o)
EXEC = ../bin/local_searchall: $(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.
源代码传送门:GITHUB
数据传送门:TSPLIB
Karl 2020/6
Back To Top
局部搜索、模拟退火和遗传算法求解TSP问题相关推荐
- 模拟退火算法 java_转 | 模拟退火算法(SA)和迭代局部搜索(ILS)求解TSP的Java代码分享...
以下文章来源于数据魔术师 ,作者周航 前言 大家好呀!我们你们好久不见的...咳咳,初次见面的小编! 之前重新整理了ILS的代码,有人留言问能不能提供java版. 正好最近在学启发式算法和java,为 ...
- 基于遗传算法求解TSP问题(旅游路径规划,Python实现,超详细,可视化,结果分析)
ps:作者是很用心写的,如果觉得不错,请给作者一点鼓励噢!(点赞收藏评论噢) 基于遗传算法求解TSP问题 摘要 巡回旅行商问题(TSP)是组合优化中的经典问题.常见的TSP问题求解算法例如穷举法.贪心 ...
- 【Matlab】 遗传算法求解TSP问题
[Matlab] 遗传算法求解TSP问题 文章目录 [Matlab] 遗传算法求解TSP问题 前言 一.问题描述 二.实验设计 1.问题案例 2.读入数据 3.适应度计算 4. 选择子代 5. 结果输 ...
- 【建模算法】基于遗传算法求解TSP问题(matlab求解)
[建模算法]基于遗传算法求解TSP问题(matlab求解) TSP (traveling salesman problem,旅行商问题)是典型的NP完全问题,即其最坏情况下的时间复杂度随着问题规模的增 ...
- 人工智能导论——遗传算法求解TSP问题实验
一.实验目的: 熟悉和掌握遗传算法的原理.流程和编码策略,并利用遗传算法求解组合优化问题,理解求解TSP问题的流程并测试主要参数对结果的影响. 二.实验原理: 旅行商问题,即TSP问题(Traveli ...
- 模拟退火和遗传算法解决TSP问题
模拟退火和遗传算法解决TSP问题 数据集介绍 采用数据集FRI26 来自标准数据集,共有26个城市,最优解为933: 数据下载链接 图1:数据矩阵 图2:数据集介绍 算法介绍 模拟退火 介绍: 模拟退 ...
- 基于遗传算法求解TSP问题(JAVA)
一.TSP问题 TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题.货郎担问题,是数学领域中著名问题之一.假设有一个旅行商人要拜访n个城市,他必须选 ...
- 【老生谈算法】matlab实现遗传算法求解TSP问题——TSP问题
遗传算法求解TSP问题MATLAB实现 1.文档下载: 本算法已经整理成文档如下,有需要的朋友可以点击进行下载 序号 文档(点击下载) 本项目文档 [老生谈算法]遗传算法求解TSP问题MATLAB实现 ...
- 【运筹优化】GA遗传算法求解TSP问题(Java实现)
文章目录 代码 Genome基因类 GeneticAlgorithm_TSP遗传算法类 运行结果 代码 Genome基因类 import lombok.Data; import lombok.NoAr ...
最新文章
- tf.log()详解
- 以太网数据帧的报尾封装字段是什么_利用TCP/IP模型理解数据通信过程
- golang协程介绍和理解
- Apache ECharts各种图表页面展示
- java文件中有中文,在windows下因编码不一致,而导致编译失败的处理方法。
- centos6.5 mysql主从_CentosMysql主从备份
- electron开发_基于Electron+React的跨平台应用程序基础开发框架
- 当ASP.NET Forms验证方式遭遇苹果IOS
- Windows自带渗透工具Certutil介绍(免杀、哈希计算、md5、sha256、下载文件、base64编码)
- OpenCL将数组从内存copy到显存
- 枚举也能直接转换为对应的数值输出
- pylot网站压力测试
- paip.软件版本完善计划C426fromVC423
- 百度有啊 真的还有啊
- 生鲜电商、社区团购、团长中心、地址管理、自提点、订单列表、限时折扣、预售、会员储值、钱包、同城配送、门店自提、团长自提、采购、履约、仓储、运输、财务、移动端电商原型、rp源文件、axure电商原型
- javascript渐变色算法
- 360浏览器强制使用极速模式
- winpe加载raid_为WinPE添加RAID卡驱动的几种步骤
- GoJs图表的布局类型
- 【MQTT】在Windows下搭建MQTT服务器
热门文章
- 全面了解三极管——三极管基本参数总结
- 区块链应用_供应链金融
- 重估2020:“黑天鹅”催生的AI新业态
- 基于深度学习的命名实体识别研究综述——论文研读
- 微信小程序html文章添加跳转链接,微信小程序跳转外部链接(h5页面)以及数据交互...
- 算法设计与分析------蛮力法
- 【单片机毕业设计】【mcuclub-jj-026】基于单片机的垃圾桶的设计
- 网络nan的原因_训练网络loss出现Nan解决办法
- STM32F0xx_PWR低功耗配置详细过程
- 机器学习三大基本任务_Task01