代碼 | 用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起來而已,還算蠻簡單的了。
```C++
int main(int argc, char argv[])
{
//構造TSP數據,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操作,具體代碼如下:
```C++
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存儲的是解裏面的城市,好了大家看代碼把吧:
```C++
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文件的時候,插入城市和評估插入城市情況的時候會看到大量這樣的代碼:
```C++
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方法評估得出:
```C++
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不同的是,它實現的是從解的城市序列裏面隨機移除多個城市,具體代碼如下:
```C++
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問題 - 代碼詳解