前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >代码 | 自适应大邻域搜索系列之(2) - ALNS算法主逻辑结构解析

代码 | 自适应大邻域搜索系列之(2) - ALNS算法主逻辑结构解析

作者头像
用户1621951
发布2019-10-18 15:11:54
7590
发布2019-10-18 15:11:54
举报
文章被收录于专栏:数据魔术师

在上一篇推文中,教大家利用了ALNS的lib库求解了一个TSP问题作为实例。不知道你萌把代码跑起来了没有。

那么,今天咱们再接再厉。跑完代码以后,小编再给大家深入讲解具体的代码内容。

01 总体概述

前排高能预警,在下面的讲解中,会涉及很多C++语言的知识,特别是类与派生这一块的内容。

如果C++基础比较薄弱的同学则需要回去(洗洗睡再好好补一补啦,在这里小编就不再过多科普基础知识了。

默认大家都是C++大佬,能一口说出虚函数表是什么的内种……

唉……小编还是不太放心大家,给大家多科普点吧:

C++中虚函数的作用

1、简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。

2、实现多态性,多态性是将接口与实现进行分离。

3、当基类指针指向一个子类对象,通过这个指针调用子类和基类同名成员函数的时候,基类声明为虚函数就会调子类的这个函数,不声明就会调用基类的。

纯虚函数

纯虚函数指明一个虚拟函数只是提供了一个可被子类型改写的接口。纯虚函数是在基类中声明的虚函数,它可以在基类中有定义,而且派生类必须定义自己的实现方法。基类不能生成对象,可以使用指针或者引用派生类对象。基类不在基类中实现纯虚函数的方法是在函数原型后加“=0”,virtual void funtion1()=0。

引入原因/纯虚函数的作用

为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

科普END

描述整一个ALNS算法逻辑过程的是一个叫ALNS的C++类。下面对其成员变量和成员函数讲解一下。

1.1 成员变量

下面的成员变量类型用的都是抽象类的指针,因为在实际写代码的过程中,coder们肯定还要对solution、localsearch等类进行继承和派生,接口重写等。

用抽象类的指针好处就是在于当它指向子类对象时也能正确调用。

再说明一点,上面的ISolution啊IAcceptanceModule等都是一些抽象类的类型,以后会进行介绍和讲解的,在这里大家知道它代表什么就行了。

代码语言:javascript
复制
 1 //! The current solution.
 2 ISolution* currentSolution;
 3
 4 //! The acceptance criteria to be used.
 5 IAcceptanceModule* acceptanceCriterion;
 6
 7 //! The parameters to be used.
 8 ALNS_Parameters* param;
 9
10 //! Manager of the operators.
11 AOperatorManager* opManager;
12
13 //! Manager of the best solutions.
14 IBestSolutionManager* bestSolManager;
15
16 //! Manager of the local search operators.
17 ILocalSearchManager* lsManager;
18
19 //! The number of iterations since last weight recomputation.
20 size_t nbIterationsWC;
21
22 //! The current number of iterations.
23 size_t nbIterations;
24
25 //! The current number of iterations without improvement.
26 size_t nbIterationsWithoutImprovement;
27
28 //! The number of iterations without improvement of the current solution.
29 size_t nbIterationsWithoutImprovementCurrent;
30
31 //! The number of iterations without acceptation of a transition.
32 size_t nbIterationsWithoutTransition;
33
34 //! The number of iterations since the last call to a local search
35 //! operator.
36 size_t nbIterationsWithoutLocalSearch;
37
38 //! The time the optimization process started.
39 clock_t startingTime;
40
41 //! A lower bound on the optimum solution cost.
42 double lowerBound;
43
44 //! A set containing the hash keys of the encountred solutions.
45 std::set<long long> knownKeys;
46
47 //! An object to compute some statistics about the solving process.
48 Statistics stats;
49
50 //! An object representing the status of the last iteration.
51 ALNS_Iteration_Status status;
52
53 //! A list of object that we need to update at the end of each iteration. 不懂往下看
54 std::vector<IUpdatable*> updatableStructures;
55
56 std::string name;

1.2 成员函数

ALNS类的成员函数以及参数说明、函数说明等都在注释里面了。这么简单的英文相信大家都能看懂,小编就不作翻译了。

代码语言:javascript
复制
 1 //! Constructor.
 2 //! \param name the name of the instance.
 3 //! \param initialSolution the starting solution that is going to be optimized.
 4 //! \param acceptanceCrit the module that determine whether or not a new solution
 5 //! is accepted as the current solution.
 6 //! \param parameters the set of parameters to be use by the ALNS.
 7 //! \param opMan an operator manager.
 8 ALNS(std::string instanceName,
 9     ISolution& initialSolution,
10     IAcceptanceModule& acceptanceCrit,
11      ALNS_Parameters& parameters,
12      AOperatorManager& opMan,
13      IBestSolutionManager& solMan,
14      ILocalSearchManager& lsMan);
15
16 //! Destructor.
17 virtual ~ALNS();
18
19 //! This method launch the solving process.
20 //! \return true if a feasible solution is found,
21 //! false otherwise.
22 bool solve();
23
24 //! This method seeks if a solution is already known,
25 //! if not it is added to the set of known solutions.
26 //! \param sol the solution to be checked.
27 //! \return true if the solution was unknown, false otherwise.
28 bool checkAgainstKnownSolution(ISolution& sol);
29
30 //! This method perform one iteration of the ALNS solving
31 //! process.
32 void performOneIteration();
33
34 //! This method check whether or not the stopping criteria is met.
35 bool isStoppingCriterionMet();
36
37 //! Determine whether or not the new solution is better than the
38 //! best known solution.
39 bool isNewBest(ISolution* newSol);
40
41 //! \return the number of known solutions.
42 size_t getNumberKnownSolutions(){return knownKeys.size();};
43
44 //! Determine whether or not the new solution should be accepted
45 //! as the current solution.
46 bool transitionCurrentSolution(ISolution* newSol);
47
48 //! Return a pointer to the best known solution.
49 IBestSolutionManager* getBestSolutionManager(){return bestSolManager;};
50
51 //! Add an object to the list of object to be updated at the end of each
52 //! iteration of the ALNS.
53 //! \param up the updatable object to be added.  不懂往下看
54 void addUpdatable(IUpdatable& up){updatableStructures.push_back(&up);};
55
56 //! Destroy the manager that have been provided at the construction of
57 //! the instance.
58 void end();

注:代码块可左右滑动哦。

有同学可能反映,前面一些成员变量和函数还好,可是这个IUpdatable是什么东西?这个addUpdatable又是什么东西?

IUpdatable是一个抽象类,这俩货其实在这提供了一个接口。因为在某些问题中,对解进行repair和destroy操作以后,需要立即对某些信息进行更新。

这两个接口就是提供这样的更新功能的。

还不懂吗?举一个后面要讲的例子:

比如下面这个求解TSP问题的destroy操作,就继承了IUpdatable这个抽象类,并对其update接口进行了重写,以便在该destroy操作以后更新某些信息。

再来看其update的内容,其更新的是scores矩阵的内容,该矩阵主要是在求解TSP问题中起到辅助作用的。具体内容我们后续再讲哈~

02 具体实现

在看完ALNS类的总体概述以后,我们现在来研究一下各个成员函数的具体实现代码和过程。

2.1 ALNS::构造和析构函数

构造函数主要做的是一些初始化工作,用传进来的参数对成员变量进行初始化,或者直接初始化相关的成员变量等。

而析构函数主要做的是清理工作,释放动态申请的堆内存。

代码语言:javascript
复制
 1 //构造
 2 ALNS::ALNS(string instanceName,
 3          ISolution& initialSolution,
 4          IAcceptanceModule& acceptanceCrit,
 5          ALNS_Parameters& parameters,
 6          AOperatorManager& opMan,
 7          IBestSolutionManager& bestSolMan,
 8          ILocalSearchManager& lsMan)
 9 {
10     name = instanceName;
11     currentSolution = initialSolution.getCopy();
12     acceptanceCriterion = &acceptanceCrit;
13     param = &parameters;
14     lowerBound = -DBL_MAX;
15     nbIterationsWC = 0;
16     nbIterations = 0;
17     nbIterationsWithoutImprovement = 0;
18     opManager = &opMan;
19     bestSolManager = &bestSolMan;
20     lsManager = &lsMan;
21
22     opManager->setStatistics(&stats);
23
24     // We add the initial solution in the best solution manager.
25     bestSolManager->isNewBestSolution(initialSolution);
26
27     nbIterationsWithoutImprovementCurrent = 0;
28 
29     nbIterationsWithoutTransition = 0;
30
31     nbIterationsWithoutLocalSearch = 0;
32 }
33 //析构
34 ALNS::~ALNS()
35 {
36     delete currentSolution;
37 }

2.2 ALNS::performOneIteration()

该方法执行一次迭代操作。也是整个流程比较核心的部分。大概过程是:

1)进行destroy操作和进行repair操作,然后判断新解质量。

2)而后看情况要不要使用LocalSearch进行搜索,再用接受准则看是否要接受新解。

3)最后更新destroy操作和repair操作的成绩。再做一些状态量的处理等。

具体注释我已经标注在代码里了,理解起来不难。

代码语言:javascript
复制
  1 void ALNS::performOneIteration()
  2 {
  3     //重新初始化一些状态量。
  4     status.partialReinit();
  5     //选择Repair和Destroy方法
  6     ARepairOperator& repair = opManager->selectRepairOperator();
  7     ADestroyOperator& destroy = opManager->selectDestroyOperator();
  8 
  9     ISolution* newSolution = currentSolution->getCopy();
 10     //输出迭代次数等信息。 param->getLogFrequency()获取的是logFrequency变量的值,logFrequency变量表示的意思是每隔多少次输出一下相关信息。
 11     if(nbIterations % param->getLogFrequency() == 0)
 12     {
 13         cout << "[ALNS] it. " << nbIterations << " best sol: " << (*(bestSolManager->begin()))->getObjectiveValue() << " nb known solutions: " << knownKeys.size() << endl;
 14     }
 15     //destroy操作
 16     destroy.destroySolution(*newSolution);
 17     //更新状态
 18     status.setAlreadyDestroyed(ALNS_Iteration_Status::TRUE);
 19     status.setAlreadyRepaired(ALNS_Iteration_Status::FALSE);//未进行repair操作
 20     //表示newSolution还是status的信息对解进行更新。这里只提供接口,后面应用的时候要具体重写。
 21     for(vector<IUpdatable*>::iterator it = updatableStructures.begin(); it != updatableStructures.end(); it++)
 22     {
 23         (*it)->update(*newSolution,status);
 24     }
 25     //进行repair操作
 26     repair.repairSolution(*newSolution);
 27     status.setAlreadyRepaired(ALNS_Iteration_Status::TRUE);
 28     //更新迭代次数
 29     nbIterations++;
 30     status.setIterationId(nbIterations);
 31     nbIterationsWC++;
 32
 33     double newCost = newSolution->getObjectiveValue();
 34     //判断新生产的解是不是新的最优解
 35     isNewBest(newSolution);
 36     //判断新生成的解之前有没有出现过
 37     checkAgainstKnownSolution(*newSolution);
 38     //判断新生成的解和当前解谁更优
 39     bool betterThanCurrent = (*newSolution)<(*currentSolution);
 40     //如果新生成的解更优
 41     if(betterThanCurrent)
 42     {
 43         nbIterationsWithoutImprovementCurrent = 0;//清0
 44         status.setImproveCurrentSolution(ALNS_Iteration_Status::TRUE);
 45     }
 46     //否则
 47     else
 48     {
 49         nbIterationsWithoutImprovementCurrent++;
 50         status.setImproveCurrentSolution(ALNS_Iteration_Status::FALSE);//解没有得到提高
 51     }
 52     //更新状态
 53     status.setNbIterationWithoutImprovementCurrent(nbIterationsWithoutImprovementCurrent);
 54     //param->getPerformLocalSearch()指出要不要用LocalSearch,然后再用LocalSearch对新生成的解进行搜索。lsManager->useLocalSearch(*newSolution,status)将返回true如果经过LocalSearch之后的解有改进的话。
 55     if(param->getPerformLocalSearch() && lsManager->useLocalSearch(*newSolution,status))
 56     {
 57     //判断LocalSearch之后的新解是不是最优解。
 58         bestSolManager->isNewBestSolution(*newSolution);
 59     }
 60     //判断是否接受当前的解。
 61     bool transitionAccepted = transitionCurrentSolution(newSolution);
 62     //如果接受
 63     if(transitionAccepted)
 64     {
 65         status.setAcceptedAsCurrentSolution(ALNS_Iteration_Status::TRUE);
 66         nbIterationsWithoutTransition = 0;
 67     }
 68     //否则
 69     else
 70     {
 71         status.setAcceptedAsCurrentSolution(ALNS_Iteration_Status::FALSE);
 72         nbIterationsWithoutTransition++;
 73     }
 74     //更新信息
 75     status.setNbIterationWithoutTransition(nbIterationsWithoutTransition);
 76     //再一次进行LocalSearch操作,以取得更好的效果。
 77     if(param->getPerformLocalSearch() && lsManager->useLocalSearch(*newSolution,status))
 78     {
 79         bestSolManager->isNewBestSolution(*newSolution);
 80         if(status.getAcceptedAsCurrentSolution() == ALNS_Iteration_Status::TRUE)
 81         {
 82             transitionCurrentSolution(newSolution);
 83         }
 84     }
 85     //对destroy,repair方法进行成绩更新。
 86     opManager->updateScores(destroy,repair,status);
 87 
 88     //记录本次迭代过程的一些信息。
 89     stats.addEntry(static_cast<double>(clock()-startingTime)/CLOCKS_PER_SEC,nbIterations,destroy.getName(),repair.getName(),newCost,currentSolution->getObjectiveValue(),(*(bestSolManager->begin()))->getObjectiveValue(),knownKeys.size());
 90
 91     //更新destroy,repair方法的权重。是在进行了一定迭代次数以后才更新的,具体次数由param->getTimeSegmentsIt()获得。
 92     if(nbIterationsWC % param->getTimeSegmentsIt() == 0)
 93     {
 94         opManager->recomputeWeights();
 95         nbIterationsWC = 0;
 96     }
 97     //接口,在求解的过程中某些内容需要更新。
 98     for(vector<IUpdatable*>::iterator it = updatableStructures.begin(); it != updatableStructures.end(); it++)
 99     {
100         (*it)->update(*newSolution,status);
101     }
102     //如果有需要,将当前解转变成最优解再进行下一次迭代操作。
103     currentSolution = bestSolManager->reloadBestSolution(currentSolution,status);
104
105     delete newSolution;
106 }

2.3 ALNS::checkAgainstKnownSolution()

检查该解是否是之前出现过的解。主要原理是利用一个解的哈希表,所有第一次出现的解都会生成一个唯一的哈希值存于哈希表中。

将传入解的哈希值在哈希表中进行匹配,如果存在,那么这个解之前已经出现过了,否则就是独一无二的新解。

代码语言:javascript
复制
 1 bool ALNS::checkAgainstKnownSolution(ISolution& sol)
 2 {
 3     bool notKnownSolution = false;
 4     long long keySol = sol.getHash();
 5     //哈希值匹配
 6     if(knownKeys.find(keySol) == knownKeys.end())
 7     {
 8         notKnownSolution = true;
 9         knownKeys.insert(keySol);
10     }
11     //之前已经出现过的解。
12     if(!notKnownSolution)
13     {
14         status.setAlreadyKnownSolution(ALNS_Iteration_Status::TRUE);
15     }
16     //全新的解,之前没有出现过。
17     else
18     {
19         status.setAlreadyKnownSolution(ALNS_Iteration_Status::FALSE);
20     }
21     return notKnownSolution;
22 }

2.4 ALNS::isNewBest(...)

用来判断解是否为新的最优解,并做出相应的设置。

代码语言:javascript
复制
 1 bool ALNS::isNewBest(ISolution* newSol)
 2 {
 3     //如果是新的最优解
 4     if(bestSolManager->isNewBestSolution(*newSol))
 5     {
 6         status.setNewBestSolution(ALNS_Iteration_Status::TRUE);
 7         nbIterationsWithoutImprovement = 0;
 8         status.setNbIterationWithoutImprovement(nbIterationsWithoutImprovement);
 9         status.setNbIterationWithoutImprovementSinceLastReload(0);
10         return true;
11     }
12     //如果不是。
13     else
14     {
15         status.setNewBestSolution(ALNS_Iteration_Status::FALSE);
16         nbIterationsWithoutImprovement++;
17         status.setNbIterationWithoutImprovement(nbIterationsWithoutImprovement);
18         status.setNbIterationWithoutImprovementSinceLastReload(status.getNbIterationWithoutImprovementSinceLastReload()+1);
19         return false;
20     }
21 }

2.5 ALNS::transitionCurrentSolution(...)

利用接受准则判断是否要接受当前的解作为新的解。

代码语言:javascript
复制
 1 bool ALNS::transitionCurrentSolution(ISolution* newSol)
 2 {
 3     //如果接受。
 4     if(acceptanceCriterion->transitionAccepted(*bestSolManager,*currentSolution,*newSol,status))
 5     {
 6         delete currentSolution;
 7         currentSolution = newSol->getCopy();
 8         return true;
 9     }
10     //不接受,原来解不变,什么也不做。
11     else
12     {
13         return false;
14     }
15 }

2.6 ALNS::isStoppingCriterionMet()

判断算法迭代是否遇到终止条件。各种条件说明已经注释在代码:

代码语言:javascript
复制
 1 bool ALNS::isStoppingCriterionMet()
 2 {
 3     //是否找到最优可行解。
 4     if((*(bestSolManager->begin()))->isFeasible() && (*(bestSolManager->begin()))->getObjectiveValue() == lowerBound)
 5     {
 6         return true;
 7     }
 8     else
 9     {
10         switch(param->getStopCrit())
11         {
12             //是否达到最大迭代次数。
13             case ALNS_Parameters::MAX_IT: {
14                 return nbIterations >= param->getMaxNbIterations();
15             }
16             //是否达到最大限制运行时间。
17             case ALNS_Parameters::MAX_RT: {
18                 clock_t currentTime = clock();
19                 double elapsed = (static_cast<double>(currentTime - startingTime)) / CLOCKS_PER_SEC;
20                 return elapsed >= param->getMaxRunningTime();
21             }
22             //the maximum number of iterations without improvement. 
23             case ALNS_Parameters::MAX_IT_NO_IMP: {
24                 return nbIterationsWithoutImprovement >= param->getMaxNbIterationsNoImp();
25             }
26            //a mix of the MAX_IT, MAX_RT and MAX_IT_NO_IMP.
27             case ALNS_Parameters::ALL: {
28                 if(nbIterations >= param->getMaxNbIterations())
29                 {
30                     return true;
31                 }
32                 if(nbIterationsWithoutImprovement >= param->getMaxNbIterationsNoImp())
33                 {
34                     return true;
35                 }
36                 clock_t currentTime = clock();
37                 double elapsed = (static_cast<double>(currentTime - startingTime)) / CLOCKS_PER_SEC;
38                 if(elapsed >= param->getMaxRunningTime())
39                 {
40                     return true;
41                 }
42                 return false;
43             }
44
45             default: {
46                 assert(false);
47                 return false;
48             }
49         }
50     }
51
52 }

2.7 ALNS::solve()

开始ALNS算法的迭代过程。这是将上面的模块组装起来,然后跑算法求解的过程了。

代码语言:javascript
复制
 1 bool ALNS::solve()
 2 {
 3     startingTime = clock();
 4     param->setLock();
 5     acceptanceCriterion->startSignal();
 6     opManager->startSignal();
 7     stats.setStart();
 8     //如果没有遇到终止条件,那么将一次次迭代下去。
 9     while(!isStoppingCriterionMet())
10     {
11         performOneIteration();
12     }
13     //获取运行结果,返回解是否可行。
14     string pathGlob = param->getStatsGlobPath();
15     pathGlob += name;
16     pathGlob += ".txt";
17     string pathOp = param->getStatsOpPath();
18     pathOp += name;
19     pathOp += ".txt";
20     stats.generateStatsFile(pathGlob,pathOp);
21     return (*(bestSolManager->begin()))->isFeasible();
22 }

03 小结

至此,ALNS主逻辑的代码已经讲完了,大家还是以整体为重点,整体把握为主。

细枝末节我们后期还会讲的。并且……后面还有一大波代码有得大家酸爽。

不过还是先把碗里的吃完吧~咱们下期代码再见!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-02-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据魔术师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档