多起點的局部搜索算法(multi-start local search)解決TSP問題(附Java代碼及注釋)



以下文章來源於數據魔術師 ,作者向柯瑋

前言

各位看客老爺們,大家好~

今天要為大家帶來的干貨是multi-start local search算法解決TSP問題(Java的實現)。

大家可不要因為這個算法的名字比較長,就覺得這個這個算法很難,其實沒有哦-

這個算法還是非常簡單的,希望大家能夠通過這個簡單的算法來了解面對NP-hard問題,我們可以采取的策略是什么。

算法簡介

這個算法,其實大家通過名字就可以知道,一定和Iterated local search(迭代局部搜索算法)存在一定的聯系。

(這是當然呀,名字都差不多,還需要你說嗎?)

迭代局部搜索算法公眾號在之前已經介紹過了,有興趣的小伙伴可以再看看~

干貨|迭代局部搜索算法(Iterated local search)探幽(附C++代碼及注釋)

這兩個算法相似的地方我們就不多說了。我們主要介紹這個算法優勢之處。

優勢

這種算法,他是多起點的,初始解的生成和遺傳算法挺類似的。

通過隨機打亂,生成多個隨機新解,以此來增大達到最優解的目的。

可能大家光這么看,沒啥感覺,我們可以通過數學公式來讓大家直觀的感受一下。

我們認為有N個城市,令傳統的LS搜索的次數為A,傳統的MLS搜索次數為A',改進過的MLS搜索次數為A'',可以容易得出下面的公式。

現在讓我們再來看看實際的程序跑出來的結果。

這是傳統的LS。

這是傳統的MLS。

這是咱們優化過的MLS。

從以上兩個例子我們可以看出,MLS確實能夠提高單次程序運行獲得優質解的概率。

那么,下面就讓我們簡單地總結一下MLS的一些優點。

  • 如果是在多線程情況下進行探索,那么速度和LS是差不多的
  • 探尋到最優解的概率更大了
  • 對於新手來說,也可以更好的學習這種多個初始解的思想,便於以后GA等算法的學習

雖然本次代碼的展示仍然是采用單線程,但是只要單線程的明白了,多線程其實很容易就變過去了。

算法流程分析

現在我們先來介紹介紹最普遍的一種multi-start local search(多起點的局部搜索算法)。

大致的流程就是上面這副圖一樣,在讀取數據之后生成第一個解,即按照0-1-2-3……排序的解。

然后將這個解進行打亂,生成N組解,然后分別對這N組解利用2-opt算子進行鄰域搜索。

我個人感覺這一種multi-start local search算法並不是很好。

  • 都是采用的多線程操作,對於新手都不是很友好,代碼不大看得明白
  • 算子太少,單一的2-opts算子很難找到較好的解
  • 對一些比較差的初始解(通過鄰域搜索都無法找到更好的解),沒有進行一些處理

鑒於上面的不足,我對這個算法進行了一定程度的改進。如下圖。

代碼解析

在上面,我們大致的介紹了本次算法的大致實現過程。

接下來,我們對部分代碼進行解讀

啟動函數

這個函數是我們的main函數,我們在這里完成我們所有的操作。

我們在iter函數中完成我們的搜索過程。

public class launch {
    public static void main(String[] args) {
        mls my_solution=new mls();                                      //生成mls對象
        readfile my_file=new readfile();                                //讀取文件
        my_file.buildInstance("F:\\mls\\data\\uy734.tsp.txt");          //讀取文件
        my_solution.setiLSInstance(my_file.getInstance());              //設置好距離矩陣
        my_solution.setsolution();                                      //設置好初始解
        my_solution.iter();                                             //開始迭代
        my_solution.print_best();                                       //輸出最優解
        System.out.println("最佳適應度為:"+my_solution.print_of());      //輸出最佳適應度

    }
}

iter函數

這個函數就是最主要的函數,相當於整個搜索的過程的啟動器。

我們在這個函數中,每次生成一個新的隨機解,然后進行鄰域搜索。這個就是區別於LS的根本之處

並用'tihuan'作為改隨機解是否為一個較好解的標志。

 public void iter() {
        for(int c=0;c<this.iLSInstance.getN();c++)
        {
            Solution localsolution2 = this.currBest.clone();
            for (int j = c; j < this.iLSInstance.getN(); j++) {
                Solution now = ls(localsolution2.clone(), j);
                if (now.getOF() < this.dLSGlobalBest.getOF())
                    this.dLSGlobalBest = now.clone();
            }
        }
        for (int i = 0; i < this.iLSInstance.getN(); i++) {
            tihuan=false;
            Solution localsolution = this.currBest.clone();
            localsolution=restart(localsolution);
            for (int j = 0; j < this.iLSInstance.getN(); j++) {
                Solution now = ls(localsolution.clone(), j);
                if (now.getOF() < this.dLSGlobalBest.getOF())
                    this.dLSGlobalBest = now.clone();
            }
            for(int m=0;m<this.iLSInstance.getN()-1;m++)
                System.out.print(localsolution.getsolution().get(m)+"-->");
            System.out.println(localsolution.getsolution().get(this.iLSInstance.getN()-1));
            if(!tihuan)
                step++;
            if(step==50)
            {
                i--;
                step=0;
            }

        }
    }

LS函數

LS函數,即local search函數,我們通過這個函數,完成我們對每組解的每個位置的城市的鄰域搜索操作。

並用‘tihuan’作為是否生成更好的解(這里是指生成比當前隨機解好的解)的標志。image

public Solution ls(Solution ssolution,int i)  {
            Solution best = ssolution.clone();
        for (int j = i + 1; j < this.iLSInstance.getN() +i; j++) {
                Solution now=ssolution.clone();
                if(j<this.iLSInstance.getN()){
                now.swap(i, j);
                now.setOF(this.cLSCalculator.calc(this.iLSInstance, now));
                if (now.getOF() < best.getOF()) {
                    best = now.clone();
                    tihuan=true;
                }
                if(!tihuan){
                    now.swap(i,j);
                    now.relocate(i,j);
                    now.setOF(this.cLSCalculator.calc(this.iLSInstance, now));
                    if (now.getOF() < best.getOF()) {
                        best = now.clone();
                        tihuan=true;
                    }
                }
            }
                else if(j-this.iLSInstance.getN()<i){
                now.relocate(i,j-i);
                now.setOF(this.cLSCalculator.calc(this.iLSInstance, now));
                if (now.getOF() < best.getOF()) {
                    best = now.clone();
                    tihuan=true;
                    }
                }

        }
        return best;
    }

restart函數

這個是我們用來生成隨機新解的函數。

image

  public Solution restart(Solution solution){
        int[]haveset=new int[iLSInstance.getN()];
        haveset[0]=0;
        for(int i=0;i<iLSInstance.getN();i++){
            int n=rLSRandom.nextInt(iLSInstance.getN());
            while (haveset[n]!=0)
                n=rLSRandom.nextInt(iLSInstance.getN());
            solution.getsolution().set(i,n);
            haveset[n]=1;
        }
        solution.setOF(this.cLSCalculator.calc(this.iLSInstance, solution));
        return solution;
    }

小結

好了,我們現在把算法的大致流程,主要的代碼都展示了一下,大家可以把自己的data輸進去,看看結果怎么樣,T^T,小瑋得到的結果都不是很理想--

該算法的隨機性很大,獲得優質解的難度還是蠻大的。

但是我覺得這個算法從傳統LS變過來給了我們很多啟發,比如說,在尋求最優解的時候,我們可以采用多線程來提高尋求最優解的效率等等。

我希望大家通過本次推文,能夠了解到鄰域解是如何產生的,以及算法不夠好時的我們可以采用哪些改進。

那么在下一次的推文中,會介紹一種船新的組合優化解決VRPTW的算法~讓我們一起期待吧!

本篇推文代碼請在公眾號后台回復【MLS代碼】獲取(不用輸入【】)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM