写在前面

前面好多篇文章,我们总算是把整个ALNS的代码框架给大家说明白了。不知道大家对整个框架了解了没有。不过打铁要趁热,心急了要吃热豆腐。今天就来实战一下,教大家怎么用ALNS的代码框架,求解一个老生常谈的TSP问题,so,get ready?

01 文件说明

整个项目由多个文件组成,为了大家更好了解各个文件的内容以及他们之间的关系,小编特地做了一份表格说明。

类名或文件名 说明
main 主文件
TSPSolution Solution的定义和各种相关操作
TSP_LS LocalSearch
TSP_Best_Insert repair方法
TSP_Random_Insert repair方法
TSP_History_Removal destroy方法
TSP_Random_Removal destroy方法
TSP_Worst_Removal 主destroy方法

02 主逻辑过程分析

这一篇文章主要分析该程序的主逻辑过程,代码中的相关模块看不懂没关系,后面会详细讲解到的。大家先知道这么一个东西就行了。代码和具体解释贴在下面了,该过程主要是生成相应的模块,并且组装进去然后run起来而已,还算蛮简单的了。

int main(int argc, char* argv[])
{
//构造TSP数据,100个点,坐标随机生成,这里你们可以按照自己的方式输入数据
double* x = new double[100];
double* y = new double[100];
for(int i = 0; i < 100; i++)
{
x[i] = 100*(static_cast<double>(rand()) / RAND_MAX);
y[i] = 100*(static_cast<double>(rand()) / RAND_MAX);
}
double** distances = new double*[100];
for(int i = 0; i < 100; i++)
{
distances[i] = new double[100];
for(int j = 0; j < 100; j++)
{
distances[i][j] = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
} //生成初始空解。参数是距离矩阵和城市数目
TSPSolution initialSol(distances,100);
//生成repair和destroy方法
TSP_Best_Insert bestI("Best Insertion");
TSP_Random_Insert randomI("Random Insertion");
TSP_Random_Removal randomR("Random Removal");
TSP_Worst_Removal worstR("Worst Removal");
TSP_History_Removal historyR("History Removal",100); //对初始空解进行填充,形成初始解
randomI.repairSolution(dynamic_cast<ISolution&>(initialSol)); //加载相关参数
ALNS_Parameters alnsParam;
alnsParam.loadXMLParameters("./param.xml"); CoolingSchedule_Parameters csParam(alnsParam);
csParam.loadXMLParameters("./param.xml");
ICoolingSchedule* cs = CoolingScheduleFactory::makeCoolingSchedule(dynamic_cast<ISolution&>(initialSol),csParam);
SimulatedAnnealing sa(*cs); //添加repair和destroy方法到OperatorManager
OperatorManager opMan(alnsParam);
opMan.addDestroyOperator(dynamic_cast<ADestroyOperator&>(randomR));
opMan.addDestroyOperator(dynamic_cast<ADestroyOperator&>(worstR));
opMan.addDestroyOperator(dynamic_cast<ADestroyOperator&>(historyR));
opMan.addRepairOperator(dynamic_cast<ARepairOperator&>(bestI));
opMan.addRepairOperator(dynamic_cast<ARepairOperator&>(randomI));
//生成SolutionManager和LocalSearchManager对Solution和LocalSearch进行管理
SimpleBestSolutionManager bestSM(alnsParam);
SimpleLocalSearchManager simpleLsManager(alnsParam);
//生成LocalSearch
TSP_LS ls("My LS");
TSP_LS lsB("LS FD");
//将LocalSearch添加到 LocalSearchManager
simpleLsManager.addLocalSearchOperator(dynamic_cast<ILocalSearch&>(ls));
simpleLsManager.addLocalSearchOperator(dynamic_cast<ILocalSearch&>(lsB));
//生成ALNS算法框架
ALNS alns("tspExample",dynamic_cast<ISolution&>(initialSol),dynamic_cast<IAcceptanceModule&>(sa),alnsParam,dynamic_cast<AOperatorManager&>(opMan),dynamic_cast<IBestSolutionManager&>(bestSM),dynamic_cast<ILocalSearchManager&>(simpleLsManager));
//destroy方法TSP_History_Removal需要进行部分内容更新
alns.addUpdatable(dynamic_cast<IUpdatable&>(historyR));
//求解
alns.solve();
//清理
for(int i = 0; i < 100; i++)
{
delete[] distances[i];
}
delete[] distances;
delete[] x;
delete[] y;
delete cs; return 0;
}

03 LocalSearch

前面我们提到,可以用LocalSearch也可以不用LocalSearch。一般用了LocalSearch情况会更好一点,来看看此处的LocalSearch是怎么定义的吧。

其实LocalSearch是继承于ALNS框架里面的ILocalSearch 类的,其中最主要的一个函数就是performLocalSearch执行LocalSearch操作,具体代码如下:

bool TSP_LS::performLocalSearch(ISolution& sol)
{
TSPSolution& tspsol = dynamic_cast<TSPSolution&>(sol);
bool ok = false;
bool toReturn = false;
do
{
ok = false;
//找出下标和该位置存储的城市序列值相同的点,移除
for(int cust = 0; cust < tspsol.getCustomerSequence().size(); cust++)
{
double prevCost = tspsol.getObjectiveValue();
int prevPos = 0;
for(int pos = 0; pos < tspsol.getCustomerSequence().size(); pos++)
{
if(tspsol.getCustomerSequence()[pos] == cust)
{
tspsol.remove(pos);
prevPos = pos;
break;
}
}
//寻找一个更优的位置插入
for(int pos = 0; pos < tspsol.getCustomerSequence().size(); pos++)
{
if(tspsol.evaluateInsert(cust,pos)+tspsol.getObjectiveValue()<prevCost-0.01)
{
tspsol.insert(cust,pos);
prevPos = -1;
ok = true;
toReturn = true;
break;
}
}
if(prevPos != -1)
{
tspsol.insert(cust,prevPos);
}
}
}while(ok);
return toReturn;
}

看不太懂?没关系,小编可是图文并茂的好手。



这就是LocalSearch执行的操作。

04 TSPSolution

这里的TSPSolution继承于之前介绍过的ISolution,其相关接口和说明已经注释在代码里面了,然后再唠叨两句,nonInserted存储的是未插入解的城市,customerSequence存储的是解里面的城市,好了大家看代码把吧:

class TSPSolution: public ISolution {
public:
//! Constructor
TSPSolution(double** distances, int nbNodes);
//! Destructor.
virtual ~TSPSolution();
//! A getter for the value of the objective function.
//! \return the value of the objective function of this solution.
virtual double getObjectiveValue();
//! \return a penalized version of the objective value if the solution
//! is infeasible.
virtual double getPenalizedObjectiveValue();
//! A getter for the feasibility of the current solution.
//! \return true if the solution is feasible, false otherwise.
virtual bool isFeasible();
//! A comparator.
//! \return true if this solution is "better" than the solution it is compared to.
virtual bool operator<(ISolution&);
//! Compute the "distance" between solution.
//! This feature can be used as part of the ALNS to favor the
//! diversification process. If you do not plan to use this feature
//! just implement a method returning 0.
virtual int distance(ISolution&);
//! This method create a copy of the solution.
virtual ISolution* getCopy();
//! Compute a hash key of the solution.
virtual long long getHash();
//! Simple getter.
std::vector<int>& getCustomerSequence(){return customerSequence;};
std::vector<int>& getNonInserted(){return nonInserted;};
void recomputeCost();
void insert(int node, size_t pos);
void remove(size_t pos);
double evaluateInsert(int node, size_t pos);
double evaluateRemove(size_t pos);
private:
int nbNodes;
double** distanceMatrix;
double cost;
std::vector<int> customerSequence;
std::vector<int> nonInserted;
};

关于其CPP文件,挑几个值得将的方法来讲讲吧。

……

……

……

……

……

呃,然后发现好像也没什么可讲的。讲讲一个难点吧,大家在看CPP文件的时候,插入城市和评估插入城市情况的时候会看到大量这样的代码:

				cost -= distanceMatrix[customerSequence[pos-1]][customerSequence[pos]];
cost += distanceMatrix[customerSequence[pos-1]][node];
cost += distanceMatrix[node][customerSequence[pos]];
............
delta -= distanceMatrix[customerSequence[pos-1]][customerSequence[pos]];
delta += distanceMatrix[customerSequence[pos-1]][node];
delta += distanceMatrix[node][customerSequence[pos]];

讲讲具体原理。

假如有以下城市序列:

现在我们把城市5给移除掉了。那么移除以后需要再计算一下该序列的cost怎么办呢?

难道又要重头加到尾吗??? NO!NO!NO!看下面:

new_cost = cost - distance(7, 5) - distance(5, 1) + distance(7, 1)。

懂了吧?这种东西,意会一下就行了,不用我说得太明白。

05 repair和destroy方法

其实,repair和destroy方法组合起来,本质上还是一个LocalSearch的算子,这一点大家还是要理解的。所以,这里挑两个来给大家讲讲就好了,毕竟关于具体的TSP求解算子,在之前的文章中介绍了很多,像什么2opt、2hopt、3opt等等。

5.1 TSP_Best_Insert

TSP_Best_Insert继承于ARepairOperator ,它具体执行的操作如下,其实很简单,找到合适的位置插入,直到把整个解都给修复了为止,那么如何判断该位置是否合适?由evaluateInsert方法评估得出:

void TSP_Best_Insert::repairSolution(ISolution& sol)
{
TSPSolution& tspsol = dynamic_cast<TSPSolution&>(sol);
while(!tspsol.getNonInserted().empty())
{
int pos = 0;
int node = 0;
double best = 100000;
for(vector<int>::iterator it = tspsol.getNonInserted().begin(); it != tspsol.getNonInserted().end(); it++)
{
for(size_t i = 0; i <= tspsol.getCustomerSequence().size(); i++)
{
double cost = tspsol.evaluateInsert(*it,i);
if(cost < best)
{
best = cost;
pos = i;
node = *it;
}
}
}
tspsol.insert(node, pos);
}
}

5.2 TSP_Random_Removal

这个destroy方法也很简单,它也继承于ADestroyOperator。和TSP_Best_Insert不同的是,它实现的是从解的城市序列里面随机移除多个城市,具体代码如下:

void TSP_Random_Removal::destroySolution(ISolution& sol)
{
TSPSolution& tspsol = dynamic_cast<TSPSolution&>(sol);
int randomDest = (rand() % static_cast<int>(0.1 * static_cast<double>(tspsol.getCustomerSequence().size()))) + static_cast<int>(0.1 * static_cast<double>(tspsol.getCustomerSequence().size()));
for(int i = 0; i < randomDest; i++)
{
int pos = rand() % tspsol.getCustomerSequence().size();
tspsol.remove(pos);
}
}

05 小结

这次介绍了具体怎么在ALNS的基础上定制自己的代码求解一个TSP问题,有了前面的理解,相信这里对大家来说简直小菜一碟。至此,整个ALNS系列就完结了,谢谢大家的一路跟随。希望这些代码能给你萌带来意想不到的收获。

代码及相关内容可关注公众号。更多精彩尽在微信公众号【程序猿声】

代码 | 用ALNS框架求解一个TSP问题 - 代码详解的更多相关文章

  1. Farseer.net轻量级开源框架 入门篇:修改数据详解

    导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 入门篇: 添加数据详解 下一篇:Farseer.net轻量级开源框架 入门篇: 删除数据详解 ...

  2. 《手把手教你》系列基础篇(八十三)-java+ selenium自动化测试-框架设计基础-TestNG测试报告-下篇(详解教程)

    1.简介 其实前边好像简单的提到过测试报告,宏哥觉得这部分比较重要,就着重讲解和介绍一下.报告是任何测试执行中最重要的部分,因为它可以帮助用户了解测试执行的结果.失败点和失败原因.另一方面,日志记录对 ...

  3. 提高Java代码质量的Eclipse插件之Checkstyle的使用详解

    提高Java代码质量的Eclipse插件之Checkstyle的使用详解 CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发人员遵守某些编码规范的工具.它能够自动化代 ...

  4. Farseer.net轻量级开源框架 入门篇:添加数据详解

    导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 入门篇: 分类逻辑层 下一篇:Farseer.net轻量级开源框架 入门篇: 修改数据详解 ...

  5. 【山外笔记-工具框架】iperf3网络性能测试工具详解教程

    [山外笔记-工具框架]iperf3网络性能测试工具详解教程   本文下载链接 [学习笔记]iperf3网络性能测试工具.pdf 网络性能评估主要是监测网络带宽的使用率,将网络带宽利用最大化是保证网络性 ...

  6. Farseer.net轻量级开源框架 入门篇:删除数据详解

    导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 入门篇: 修改数据详解 下一篇:Farseer.net轻量级开源框架 入门篇: 查询数据详解 ...

  7. Farseer.net轻量级开源框架 入门篇:查询数据详解

    导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 入门篇: 删除数据详解 下一篇:Farseer.net轻量级开源框架 中级篇: Where条 ...

  8. 【CPLEX教程03】java调用cplex求解一个TSP问题模型

    00 前言 前面我们已经搭建好cplex的java环境了,相信大家已经跃跃欲试,想动手写几个模型了.今天就来拿一个TSP的问题模型来给大家演示一下吧~ CPLEX系列教程可以关注我们的公众号哦!获取更 ...

  9. 第一节:框架前期准备篇之Log4Net日志详解

    一. Log4Net简介 Log4net是从Java中的Log4j迁移过来的一个.Net版的开源日志框架,它的功能很强大,可以将日志分为不同的等级,以不同的格式输出到不同的存储介质中,比如:数据库.t ...

随机推荐

  1. pb笔记之数据窗口设置操作

    1 使DataWindow列只能追加不能修改如何使DataWindow中的数据只能追加新记录而不能修改,利用 Column 的 Protect 属性可以很方便的做到这一点,方法如下:将每一列的 Pro ...

  2. spring源码学习(一)--AOP初探

    LZ以前一直觉得,学习spring源码,起码要把人家的代码整体上通读一遍,现在想想这是很愚蠢的,spring作为一个应用平台,不是那么好研究透彻的,而且也不太可能有人把spring的源码全部清楚的过上 ...

  3. vue npm run build 失败

    之前删除过 node-moudel 文件夹,然后 npm install 重新安装,一切OK.打包的时候,报错,找不到caniuse什么的.再删除node-moudel,重新cnpm install ...

  4. XPATH中text()和string()的使用区别

    <table style="WIDTH: 95.45%; BORDER-COLLAPSE: collapse; EMPTY-CELLS: show; MARGIN-LEFT: 4.55 ...

  5. python3 访问 rabbitmq 示例

    关于 rabbitmq 之前用过 kafka,要是拿这两者做对比的话,大概有以下异同: 两者都是一个分布式架构 kafka 具有较高的吞吐量,rabbimq 吞吐量较小 rabbitmq 的可靠性更好 ...

  6. 遇到 GLFW 我的demo可以运行 但是公司的程序调用我的so运行不起来

    //to do 原       因:  发现 自身demo的程序的shaders更新了  但是公司程序却没有更新 解决办法:更新公司程序的shaders 为最新版本 吸取的教训: 不仅仅要更新公司程序 ...

  7. js实现图片的Blob base64 ArrayBuffer 的各种转换

    一.相关基础知识 构造函数 FileReader() 返回一个新构造的FileReader 事件处理 FileReader.onabort  处理abort事件.该事件在读取操作被中断时触发. Fil ...

  8. php权限管理

    首先权限管理肯定是需要登陆的,这里就简单的写一个登陆页面. 简单的登陆页面login.php <h1>登录页面</h1> <form action="login ...

  9. 【已解决】老型号电脑需要按F1键才能进入系统

    [已解决]老型号电脑需要按F1键才能进入系统 本文作者:天析 作者邮箱:2200475850@qq.com 发布时间: Tue, 16 Jul 2019 20:49:00 +0800 问题描述:电脑因 ...

  10. 常见User-Agent大全

    window.navigator.userAgent 1) Chrome Win7: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KH ...