1. 程式人生 > >代碼 | 自適應大鄰域搜索系列之(2) - ALNS算法主邏輯結構解析

代碼 | 自適應大鄰域搜索系列之(2) - ALNS算法主邏輯結構解析

param 子類 sed 指針 key 化工 bee stats tables

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 = &parameters;
    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算法主邏輯結構解析