1. 程式人生 > >代碼 | 用ALNS框架求解一個TSP問題 - 代碼詳解

代碼 | 用ALNS框架求解一個TSP問題 - 代碼詳解

har 而已 current while 介紹 air 這一 emp sea

寫在前面

前面好多篇文章,我們總算是把整個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 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操作,具體代碼如下:
```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問題 - 代碼詳解