• 代码 | 用ALNS框架求解一个TSP问题


    写在前面

    前面好多篇文章,我们总算是把整个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.
    	//! 
    eturn the value of the objective function of this solution.
    	virtual double getObjectiveValue();
    	//! 
    eturn a penalized version of the objective value if the solution
    	//! is infeasible.
    	virtual double getPenalizedObjectiveValue();
    	//! A getter for the feasibility of the current solution.
    	//! 
    eturn true if the solution is feasible, false otherwise.
    	virtual bool isFeasible();
    	//! A comparator.
    	//! 
    eturn 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系列就完结了,谢谢大家的一路跟随。希望这些代码能给你萌带来意想不到的收获。

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

  • 相关阅读:
    远程调用丢失请求头与定义RequestInterceptor
    RabbitMQ 高级特性
    注解@ConfigurationProperties使用方法
    Redisson
    分布式缓存
    DEA 无法显示 Run Dashboard 的解决方法
    node多版本切换
    springboot整合amazonS3,封装上传文件接口
    Maven报错:The packaging for this project did not assign a file to the build artifact
    Nodejs介绍及npm工具使用
  • 原文地址:https://www.cnblogs.com/dengfaheng/p/10846582.html
Copyright © 2020-2023  润新知