代码 | 自适应大邻域搜索系列之(2) - ALNS算法主逻辑结构解析
00 前言
在上一篇推文中,教大家利用了ALNS的lib库求解了一个TSP问题作为实例。不知道你萌把代码跑起来了没有。那么,今天咱们再接再厉。跑完代码以后,小编再给大家深入讲解具体的代码内容。大家快去搬个小板凳一起过来围观学习吧~
01 总体概述
前排高能预警,在下面的讲解中,会涉及很多C++语言的知识,特别是类与派生这一块的内容,如果C++基础比较薄弱的同学则需要回去(洗洗睡)再好好补一补啦,在这里小编就不再过多科普基础知识了。默认大家都是C++大佬,能一口说出虚函数表是什么的内种……
描述整一个ALNS算法逻辑过程的是一个叫ALNS的C++类。下面对其成员变量和成员函数讲解一下。
1.1 成员变量
//! 当前解。
ISolution* currentSolution;
//! 判断接受准则。
IAcceptanceModule* acceptanceCriterion;
//! ALNS算法运行的相关参数。
ALNS_Parameters* param;
//! destroy和repair方法的管理者。
AOperatorManager* opManager;
//! 最优解的管理者。
IBestSolutionManager* bestSolManager;
//! 局部搜索的管理者。
ILocalSearchManager* lsManager;
//! 自上次重新计算重新的权重以来的迭代次数。
size_t nbIterationsWC;
//! 当前迭代次数。
size_t nbIterations;
//! The current number of iterations without improvement.
size_t nbIterationsWithoutImprovement;
//! The number of iterations without improvement of the current solution.
size_t nbIterationsWithoutImprovementCurrent;
//! The number of iterations without acceptation of a transition.
size_t nbIterationsWithoutTransition;
//! The number of iterations since the last call to a local search
//! operator.
size_t nbIterationsWithoutLocalSearch;
//! 求解的总时间。
clock_t startingTime;
//! 最优解的下界。
double lowerBound;
//! A set containing the hash keys of the encountred solutions.
std::set<long long> knownKeys;
//! 用于计算求解过程的一些状态量。
Statistics stats;
//! 最近一次迭代的状态。
ALNS_Iteration_Status status;
//! 每次迭代完成后需要更新的对象。
std::vector<IUpdatable*> updatableStructures;
//! ALNS实例的名字。
std::string name;
上面的成员变量类型用的都是抽象类的指针,因为在实际写代码的过程中,coder们肯定还要对solution、localsearch等类进行继承和派生,接口重写等。用抽象类的指针好处就是在于当它指向子类对象时也能正确调用。再说明一点,上面的ISolution啊IAcceptanceModule等都是一些抽象类的类型,以后会进行介绍和讲解的,在这里大家知道它代表什么就行了。
1.2 成员函数
//! Constructor.
//! \param name the name of the instance.
//! \param initialSolution the starting solution that is going to be optimized.
//! \param acceptanceCrit the module that determine whether or not a new solution
//! is accepted as the current solution.
//! \param parameters the set of parameters to be use by the ALNS.
//! \param opMan an operator manager.
ALNS(std::string instanceName,
ISolution& initialSolution,
IAcceptanceModule& acceptanceCrit,
ALNS_Parameters& parameters,
AOperatorManager& opMan,
IBestSolutionManager& solMan,
ILocalSearchManager& lsMan);
//! Destructor.
virtual ~ALNS();
//! This method launch the solving process.
//! \return true if a feasible solution is found,
//! false otherwise.
bool solve();
//! This method seeks if a solution is already known,
//! if not it is added to the set of known solutions.
//! \param sol the solution to be checked.
//! \return true if the solution was unknown, false otherwise.
bool checkAgainstKnownSolution(ISolution& sol);
//! This method perform one iteration of the ALNS solving
//! process.
void performOneIteration();
//! This method check whether or not the stopping criteria is met.
bool isStoppingCriterionMet();
//! Determine whether or not the new solution is better than the
//! best known solution.
bool isNewBest(ISolution* newSol);
//! \return the number of known solutions.
size_t getNumberKnownSolutions(){return knownKeys.size();};
//! Determine whether or not the new solution should be accepted
//! as the current solution.
bool transitionCurrentSolution(ISolution* newSol);
//! Return a pointer to the best known solution.
IBestSolutionManager* getBestSolutionManager(){return bestSolManager;};
//! Add an object to the list of object to be updated at the end of each
//! iteration of the ALNS.
//! \param up the updatable object to be added.
void addUpdatable(IUpdatable& up){updatableStructures.push_back(&up);};
//! Destroy the manager that have been provided at the construction of
//! the instance.
void end();
ALNS类的成员函数以及参数说明、函数说明等都在注释里面了。这么简单的英文相信大家都能看懂,小编就不作翻译了。
02 具体实现
在看完ALNS类的总体概述以后,我们现在来研究一下各个成员函数的具体实现代码和过程。
2.1 ALNS::构造和析构函数
构造函数主要做的是一些初始化工作,用传进来的参数对成员变量进行初始化,或者直接初始化相关的成员变量等。而析构函数主要做的是清理工作,释放动态申请的堆内存。
//构造
ALNS::ALNS(string instanceName,
ISolution& initialSolution,
IAcceptanceModule& acceptanceCrit,
ALNS_Parameters& parameters,
AOperatorManager& opMan,
IBestSolutionManager& bestSolMan,
ILocalSearchManager& lsMan)
{
name = instanceName;
currentSolution = initialSolution.getCopy();
acceptanceCriterion = &acceptanceCrit;
param = ¶meters;
lowerBound = -DBL_MAX;
nbIterationsWC = 0;
nbIterations = 0;
nbIterationsWithoutImprovement = 0;
opManager = &opMan;
bestSolManager = &bestSolMan;
lsManager = &lsMan;
opManager->setStatistics(&stats);
// We add the initial solution in the best solution manager.
bestSolManager->isNewBestSolution(initialSolution);
nbIterationsWithoutImprovementCurrent = 0;
nbIterationsWithoutTransition = 0;
nbIterationsWithoutLocalSearch = 0;
}
//析构
ALNS::~ALNS()
{
delete currentSolution;
}
2.2 ALNS::performOneIteration()
该方法执行一次迭代操作。也是整个流程比较核心的部分。大概过程是先进行destroy操作和进行repair操作,然后判断新解质量。而后看情况要不要使用LocalSearch进行搜索,再用判断接受准则看是否要接受新解。最后更新destroy操作和repair操作的成绩。再做一些状态量的处理等。具体注释我已经标注在代码里了,理解起来不难。
void ALNS::performOneIteration()
{
//重新初始化一些状态量。
status.partialReinit();
//选择Repair和Destroy方法
ARepairOperator& repair = opManager->selectRepairOperator();
ADestroyOperator& destroy = opManager->selectDestroyOperator();
ISolution* newSolution = currentSolution->getCopy();
//输出迭代次数等信息。 param->getLogFrequency()获取的是logFrequency变量的值,logFrequency变量表示的意思是每隔多少次输出一下相关信息。
if(nbIterations % param->getLogFrequency() == 0)
{
cout << "[ALNS] it. " << nbIterations << " best sol: " << (*(bestSolManager->begin()))->getObjectiveValue() << " nb known solutions: " << knownKeys.size() << endl;
}
//destroy操作
destroy.destroySolution(*newSolution);
//更新状态
status.setAlreadyDestroyed(ALNS_Iteration_Status::TRUE);
status.setAlreadyRepaired(ALNS_Iteration_Status::FALSE);//未进行repair操作
//表示newSolution还是status的信息对解进行更新。这里只提供接口,后面应用的时候要具体重写。
for(vector<IUpdatable*>::iterator it = updatableStructures.begin(); it != updatableStructures.end(); it++)
{
(*it)->update(*newSolution,status);
}
//进行repair操作
repair.repairSolution(*newSolution);
status.setAlreadyRepaired(ALNS_Iteration_Status::TRUE);
//更新迭代次数
nbIterations++;
status.setIterationId(nbIterations);
nbIterationsWC++;
double newCost = newSolution->getObjectiveValue();
//判断新生产的解是不是新的最优解
isNewBest(newSolution);
//判断新生成的解之前有没有出现过
checkAgainstKnownSolution(*newSolution);
//判断新生成的解和当前解谁更优
bool betterThanCurrent = (*newSolution)<(*currentSolution);
//如果新生成的解更优
if(betterThanCurrent)
{
nbIterationsWithoutImprovementCurrent = 0;//清0
status.setImproveCurrentSolution(ALNS_Iteration_Status::TRUE);
}
//否则
else
{
nbIterationsWithoutImprovementCurrent++;
status.setImproveCurrentSolution(ALNS_Iteration_Status::FALSE);//解没有得到提高
}
//更新状态
status.setNbIterationWithoutImprovementCurrent(nbIterationsWithoutImprovementCurrent);
//param->getPerformLocalSearch()指出要不要用LocalSearch,然后再用LocalSearch对新生成的解进行搜索。lsManager->useLocalSearch(*newSolution,status)将返回true如果经过LocalSearch之后的解有改进的话。
if(param->getPerformLocalSearch() && lsManager->useLocalSearch(*newSolution,status))
{
//判断LocalSearch之后的新解是不是最优解。
bestSolManager->isNewBestSolution(*newSolution);
}
//判断是否接受当前的解。
bool transitionAccepted = transitionCurrentSolution(newSolution);
//如果接受
if(transitionAccepted)
{
status.setAcceptedAsCurrentSolution(ALNS_Iteration_Status::TRUE);
nbIterationsWithoutTransition = 0;
}
//否则
else
{
status.setAcceptedAsCurrentSolution(ALNS_Iteration_Status::FALSE);
nbIterationsWithoutTransition++;
}
//更新信息
status.setNbIterationWithoutTransition(nbIterationsWithoutTransition);
//再一次进行LocalSearch操作,以取得更好的效果。
if(param->getPerformLocalSearch() && lsManager->useLocalSearch(*newSolution,status))
{
bestSolManager->isNewBestSolution(*newSolution);
if(status.getAcceptedAsCurrentSolution() == ALNS_Iteration_Status::TRUE)
{
transitionCurrentSolution(newSolution);
}
}
//对destroy,repair方法进行成绩更新。
opManager->updateScores(destroy,repair,status);
//记录本次迭代过程的一些信息。
stats.addEntry(static_cast<double>(clock()-startingTime)/CLOCKS_PER_SEC,nbIterations,destroy.getName(),repair.getName(),newCost,currentSolution->getObjectiveValue(),(*(bestSolManager->begin()))->getObjectiveValue(),knownKeys.size());
//更新destroy,repair方法的权重。是在进行了一定迭代次数以后才更新的,具体次数由param->getTimeSegmentsIt()获得。
if(nbIterationsWC % param->getTimeSegmentsIt() == 0)
{
opManager->recomputeWeights();
nbIterationsWC = 0;
}
//接口,对解的某些部分再次更新。
for(vector<IUpdatable*>::iterator it = updatableStructures.begin(); it != updatableStructures.end(); it++)
{
(*it)->update(*newSolution,status);
}
//如果有需要,将当前解转变成最优解再进行下一次迭代操作。
currentSolution = bestSolManager->reloadBestSolution(currentSolution,status);
delete newSolution;
}
2.3 ALNS::checkAgainstKnownSolution(ISolution& sol)
检查该解是否是之前出现过的解。主要原理是利用一个解的哈希表,所有第一次出现的解都会生成一个唯一的哈希值存于哈希表中。将传入解的哈希值在哈希表中进行匹配,如果存在,那么这个解之前已经出现过了,否则就是独一无二的新解。
bool ALNS::checkAgainstKnownSolution(ISolution& sol)
{
bool notKnownSolution = false;
long long keySol = sol.getHash();
//哈希值匹配
if(knownKeys.find(keySol) == knownKeys.end())
{
notKnownSolution = true;
knownKeys.insert(keySol);
}
//之前已经出现过的解。
if(!notKnownSolution)
{
status.setAlreadyKnownSolution(ALNS_Iteration_Status::TRUE);
}
//全新的解,之前没有出现过。
else
{
status.setAlreadyKnownSolution(ALNS_Iteration_Status::FALSE);
}
return notKnownSolution;
}
2.4 ALNS::isNewBest(ISolution* newSol)
用来判断解是否为新的最优解,并做出相应的设置。
bool ALNS::isNewBest(ISolution* newSol)
{
//如果是新的最优解
if(bestSolManager->isNewBestSolution(*newSol))
{
status.setNewBestSolution(ALNS_Iteration_Status::TRUE);
nbIterationsWithoutImprovement = 0;
status.setNbIterationWithoutImprovement(nbIterationsWithoutImprovement);
status.setNbIterationWithoutImprovementSinceLastReload(0);
return true;
}
//如果不是。
else
{
status.setNewBestSolution(ALNS_Iteration_Status::FALSE);
nbIterationsWithoutImprovement++;
status.setNbIterationWithoutImprovement(nbIterationsWithoutImprovement);
status.setNbIterationWithoutImprovementSinceLastReload(status.getNbIterationWithoutImprovementSinceLastReload()+1);
return false;
}
}
2.5 ALNS::transitionCurrentSolution(ISolution* newSol)
利用判断准则判断是否要接受当前的解作为新的解。
bool ALNS::transitionCurrentSolution(ISolution* newSol)
{
//如果接受。
if(acceptanceCriterion->transitionAccepted(*bestSolManager,*currentSolution,*newSol,status))
{
delete currentSolution;
currentSolution = newSol->getCopy();
return true;
}
//不接受,原来解不变,什么也不做。
else
{
return false;
}
}
2.6 ALNS::isStoppingCriterionMet()
判断算法迭代是否遇到终止条件。各种条件说明已经注释在代码:
bool ALNS::isStoppingCriterionMet()
{
//是否找到最优可行解。
if((*(bestSolManager->begin()))->isFeasible() && (*(bestSolManager->begin()))->getObjectiveValue() == lowerBound)
{
return true;
}
else
{
switch(param->getStopCrit())
{
//是否达到最大迭代次数。
case ALNS_Parameters::MAX_IT: {
return nbIterations >= param->getMaxNbIterations();
}
//是否达到最大限制运行时间。
case ALNS_Parameters::MAX_RT: {
clock_t currentTime = clock();
double elapsed = (static_cast<double>(currentTime - startingTime)) / CLOCKS_PER_SEC;
return elapsed >= param->getMaxRunningTime();
}
//the maximum number of iterations without improvement.
case ALNS_Parameters::MAX_IT_NO_IMP: {
return nbIterationsWithoutImprovement >= param->getMaxNbIterationsNoImp();
}
//a mix of the MAX_IT, MAX_RT and MAX_IT_NO_IMP.
case ALNS_Parameters::ALL: {
if(nbIterations >= param->getMaxNbIterations())
{
return true;
}
if(nbIterationsWithoutImprovement >= param->getMaxNbIterationsNoImp())
{
return true;
}
clock_t currentTime = clock();
double elapsed = (static_cast<double>(currentTime - startingTime)) / CLOCKS_PER_SEC;
if(elapsed >= param->getMaxRunningTime())
{
return true;
}
return false;
}
default: {
assert(false);
return false;
}
}
}
}
2.7 ALNS::solve()
开始ALNS算法的迭代过程。这是将上面的模块组装起来,然后跑算法求解的过程了。
bool ALNS::solve()
{
startingTime = clock();
param->setLock();
acceptanceCriterion->startSignal();
opManager->startSignal();
stats.setStart();
//如果没有遇到终止条件,那么将一次次迭代下去。
while(!isStoppingCriterionMet())
{
performOneIteration();
}
//获取运行结果,返回解是否可行。
string pathGlob = param->getStatsGlobPath();
pathGlob += name;
pathGlob += ".txt";
string pathOp = param->getStatsOpPath();
pathOp += name;
pathOp += ".txt";
stats.generateStatsFile(pathGlob,pathOp);
return (*(bestSolManager->begin()))->isFeasible();
}
03 小结
至此,ALNS主逻辑的代码已经讲完了,大家还是以整体为重点,整体把握为主。
细枝末节我们后期还会讲的。并且……后面还有一大波代码有得大家酸爽。
不过还是先把碗里的吃完吧~咱们下期代码再见!
代码 | 自适应大邻域搜索系列之(2) - ALNS算法主逻辑结构解析的更多相关文章
- 代码 | 自适应大邻域搜索系列之(7) - 局部搜索LocalSearch的代码解析
前言 好了小伙伴们我们又见面了,咳咳没错还是我.不知道你萌接连被这么多篇代码文章刷屏是什么感受,不过,酸爽归酸爽.今天咱们依然讲代码哈~不过今天讲的依然很简单,关于局部搜索LocalSearch的代码 ...
- 代码 | 自适应大邻域搜索系列之(3) - Destroy和Repair方法代码实现解析
前言 上一篇文章中我们具体解剖了ALNS类的具体代码实现过程,不过也留下了很多大坑.接下来的文章基本都是"填坑"了,把各个模块一一展现解析给大家.不过碍于文章篇幅等原因呢,也不会每 ...
- 代码 | 自适应大邻域搜索系列之(5) - ALNS_Iteration_Status和ALNS_Parameters的代码解析
前言 上一篇推文说了,后面的代码难度直线下降,各位小伙伴可以放去n的100次方心了.今天讲讲一些细枝末节,就是前面一直有提到的参数和一些状态的记录代码.这个简单啦,小编也不作过多解释了.大家直接看代码 ...
- 代码 | 自适应大邻域搜索系列之(4) - Solution定义和管理的代码实现解析
前言 上一篇讲解了destroy和repair方法的具体实现代码,好多读者都在喊酸爽和得劲儿--今天这篇就讲点简单的,关于solution的定义和管理的代码实现,让大家回回神吧--哈哈. 01 总体概 ...
- 代码 | 自适应大邻域搜索系列之(6) - 判断接受准则SimulatedAnnealing的代码解析
前言 前面三篇文章对大家来说应该很简单吧?不过轻松了这么久,今天再来看点刺激的.关于判断接受准则的代码.其实,判断接受准则有很多种,效果也因代码而异.今天介绍的是模拟退火的判断接受准则.那么,相关的原 ...
- 自适应大邻域搜索代码系列之(1) - 使用ALNS代码框架求解TSP问题
前言 上次出了邻域搜索的各种概念科普,尤其是LNS和ALNS的具体过程更是描述得一清二楚.不知道你萌都懂了吗?小编相信大家早就get到啦.不过有个别不愿意透露姓名的热心网友表示上次没有代码,遂不过瘾啊 ...
- 干货 | 自适应大邻域搜索(Adaptive Large Neighborhood Search)入门到精通超详细解析-概念篇
01 首先来区分几个概念 关于neighborhood serach,这里有好多种衍生和变种出来的胡里花俏的算法.大家在上网搜索的过程中可能看到什么Large Neighborhood Serach, ...
- 大数据学习系列之九---- Hive整合Spark和HBase以及相关测试
前言 在之前的大数据学习系列之七 ----- Hadoop+Spark+Zookeeper+HBase+Hive集群搭建 中介绍了集群的环境搭建,但是在使用hive进行数据查询的时候会非常的慢,因为h ...
- 大数据小白系列——HDFS(2)
这里是大数据小白系列,这是本系列的第二篇,介绍一下HDFS中SecondaryNameNode.单点失败(SPOF).以及高可用(HA)等概念. 上一篇我们说到了大数据.分布式存储,以及HDFS中的一 ...
随机推荐
- jQuery的基本事件
1.用法 <!--jquery通过bind()这个方法来为元素绑定事件,可以传三个参数,type.data.fn--> <!--type表示一个或者多个事件的名称--> < ...
- golang之map数据类型
先上代码…… package main import "fmt" func testMap() { //两种声明map方式,切记,必须初始化才能用,否则panic //var a ...
- [Excel]拾取杂志图表的配色
ColorPix是一款绿色的小软件,可以取到杂志图表所用到的颜色. 下载地址: http://www.colorschemer.com/colorpix_info.php
- Linux之chmod使用
Linux文件分三种身份和四中权限. u:文件的拥有者 g:文件所属的群组 o:其他用户 对于每个身份,又有四种权限,分别为: r:读取文件的权限(read) w:写入文件的权限(write) x:执 ...
- crt key转p12, jks p12互转,windows生成jks,
crt key转p12, jks p12互转,windows生成jks, 摘自:https://blog.csdn.net/u010801696/article/details/86546191 20 ...
- C# Redis Server分布式缓存编程(二)(转)
出处;http://www.cnblogs.com/davidgu/p/3263485.html 在Redis编程中, 实体和集合类型则更加有趣和实用 namespace Zeus.Cache.Red ...
- Asp.NetCore MVC Web 应用
Asp.NetCore MVC 与 普通的MVC 基本一致, 只是代码结构稍有改动 一.创建项目 1. 2. 3. 项目结构 二. 构建数据模型 1. Startup类中配置EF Core MySql ...
- 作业3:PSP记录耗时情况
PSP2.1 Personal Software Process Stage Time planning 计划 10min Estimate 估计这个任务多久完成 150min Developing ...
- Object对象方法 cheet sheet
defineProperty create Object.create(prototype [, propertiesObject ]) prototype:没什么可说的,指定对象的原型 proper ...
- .net core 使用 redis
.net core 使用 redis 个人感觉.net core 对于微软技术而言有很重要的意义 ,所以最近已有时间就想看一看关于.net core 的文章. 今天我就来写一写如何在.net core ...