1. 程式人生 > >粒子群演算法求解旅行商問題TSP (JAVA實現)

粒子群演算法求解旅行商問題TSP (JAVA實現)

粒子群演算法求解旅行商問題TSP

寫在開頭:
最近師妹的結課作業問我,關於使用粒子群求解TSP問題的思路。我想了想,自己去年的作業用的是遺傳演算法,貌似有些關聯,索性給看了看程式碼。重新學習了一遍粒子群演算法,在這裡記錄一下,算是對知識的總結,鞏固一下。

正文部分

本文主要是使用粒子群來求解旅行商問題,即TSP問題,這裡主要講解程式碼和實現思路,原理會簡單帶過。詳細的具體原理,請讀者移步參考連結。
本文將從如下幾個方面進行描述:

  • 1. 粒子群簡單介紹
  • 2. TSP問題簡單介紹
  • 3. JAVA程式碼實現
  • 4. 執行結果展示
  • 5. 原始碼下載連結
1. 粒子群簡單介紹

粒子群演算法簡稱PSO,它的基本思想是模擬鳥群的捕食行為。設想這樣一個場景:一群鳥在隨機搜尋食物。在這個區域裡只有一塊食物。所有的鳥都不知道食物在那裡。但是他們知道當前的位置離食物還有多遠。那麼找到食物的最優策略是什麼呢。最簡單有效的就是搜尋目前離食物最近的鳥的周圍區域。

PSO從這種模型中得到啟示並用於解決優化問題。PSO中,每個優化問題的解都是搜尋空間中的一隻鳥。我們稱之為“粒子”。所有的粒子都有一個由被優化的函式決定的適應值(fitness value),每個粒子還有一個速度決定他們飛翔的方向和距離。然後粒子們就追隨當前的最優粒子在解空間中搜索。

PSO 初始化為一群隨機粒子(隨機解)。然後通過迭代找到最優解。在每一次迭代中,粒子通過跟蹤兩個”極值”來更新自己。第一個就是粒子本身所找到的最優解,這個解叫做個體極值pBest。另一個極值是整個種群目前找到的最優解,這個極值是全域性極值gBest。另外也可以不用整個種群而只是用其中一部分作為粒子的鄰居,那麼在所有鄰居中的極值就是區域性極值。

2. TSP問題簡單介紹

TSP問題(Travelling Salesman Problem)即旅行商問題,又譯為旅行推銷員問題、貨郎擔問題,是數學領域中著名問題之一。假設有一個旅行商人要拜訪n個城市,他必須選擇所要走的路徑,路徑的限制是每個城市只能拜訪一次,而且最後要回到原來出發的城市。路徑的選擇目標是要求得的路徑路程為所有路徑之中的最小值。

TSP問題是一個組合優化問題。該問題可以被證明具有NPC計算複雜性。TSP問題可以分為兩類,一類是對稱TSP問題(Symmetric TSP),另一類是非對稱問題(Asymmetric TSP)。

3. JAVA程式碼實現

首先整理一下具體實現邏輯,具體流程如下:

  • 初始化種群
  • 初始化慣性因子,即自身的交換序列
  • 尋找每個粒子的歷代中的最優解
  • 尋找全域性最優解
  • 計算每個個體的自身最優解的交換序列
  • 計算每個個體對全域性最優解的交換序列
  • 進化——其實就是交換
    交換序列對由3部分組成:自身交換序列、自身最優解的交換序列、全域性最優解的交換序列。

  • 列印結果

使用面向物件的思想,首先定義一個city.java城市實體類,有id、name、經度和緯度屬性。

public class City {
    private int mId;       //城市編號
    private String mName;  //城市名稱
    private float mLongitude; //經度
    private float mLatitude; //經度

    public City(int id, String name, float longitude, float latitude) {
        mId = id;
        mName = name;
        mLongitude = longitude;
        mLatitude = latitude;
    }

定義一個粒子實體類Unit.java,包括行走城市的路徑path和適應度fitness。並新增兩個方法,輸出城市序列和計算自己的適應度。

private int[] mPath;  //行走的城市路徑,儲存城市編號
    private int mFitness; //適應度值,為當前個體走這個路徑的總距離。越小越好。

    public Unit(int[] path) {
        mPath = path;
        mFitness = calculateFitness();
    }
        public void printPath() {
        if (mPath == null) {
            System.out.println("mPath為null,當前個體的路徑為空");
        } else {
            for (int i = 0; i < mPath.length - 1; i++) {
                System.out.print(CityLab.getInstance().getmCities().get(mPath[i]).getName() + "——》");
            }
            System.out.println(CityLab.getInstance().getmCities().get(mPath[mPath.length - 1]).getName());
        }
    }

    public void upDateFitness() {
        this.mFitness = calculateFitness();
    }

    /**
     * 計算當前路徑的適應值,即為路徑長度
     */
    public int calculateFitness() {
        //根據經緯度計算距離
        //近似計算:0.00001度,距離相差約1米;0.01,距離相差1000米.1度,距離相差100km
        int distance = 0;  //單位千米(km)
        int n = mPath.length;
        for (int i = 1; i < n; i++) {
            City c1 = CityLab.getInstance().getmCities().get(mPath[i - 1]);
            City c2 = CityLab.getInstance().getmCities().get(mPath[i]);
            distance += Math.sqrt(Math.pow(100 * (c1.getLatitude() - c2.getLatitude()), 2) + Math.pow(100 * (c1.getLongitude() - c2.getLongitude()), 2));
        }
        distance += Math.sqrt(Math.pow(100 * (CityLab.getInstance().getmCities().get(mPath[0]).getLatitude() - CityLab.getInstance().getmCities().get(mPath[n - 1]).getLatitude()), 2) + Math.pow(100 * (CityLab.getInstance().getmCities().get(mPath[0]).getLongitude() - CityLab.getInstance().getmCities().get(mPath[n - 1]).getLongitude()), 2));
        return distance;
    }

接下來,定義交換對實體類,SO.java,包含兩個屬性x和y,用來做交換的。

public class SO {
    private int x;
    private int y;

    public SO(int x, int y) {
        this.x = x;
        this.y = y;
    }

最後使用單例模式來管理所有的城市列表CityLab.java

/**
 * 城市單例
 */
public class CityLab {
    private static CityLab mCityLab = null;
    private ArrayList<City> mCities = new ArrayList<>();

    private CityLab() {
        //讀取檔案,新增城市資訊到mcities中
        String filename = "d://city.csv";
        String strbuff;
        BufferedReader data = null;
        try {
            data = new BufferedReader(new InputStreamReader(
                    new FileInputStream(filename)));
            int id = 1;
            while (true) {
                //讀取一行資料:北京,116.41667,39.91667
                strbuff = data.readLine();
                if (strbuff == null) {
                    break;
                }
                String[] arr = strbuff.split(",");
                City c = new City(id, arr[1], valueOf(arr[2]), valueOf(arr[3]));
                id++;
                mCities.add(c);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static CityLab getInstance() {
        if (mCityLab == null) {
            mCityLab = new CityLab();
        }
        return mCityLab;
    }

    public ArrayList<City> getmCities() {
        return mCities;
    }
}

實體類我們定義好了,接下來看具體實現粒子群求解TSP問題的過程。
具體PSO.java類如下:

/**
 * 粒子群求解TSP旅行商問題
 * 更新公式:Vii=wVi+ra(Pid-Xid)+rb(Pgd-Xid)
 */
public class PSO {
    private int scale; //種群規模
    private ArrayList<Unit> mUnits = new ArrayList<>();   //粒子群

    private int MAX_GEN;// 迭代次數
    private float w;    //慣性權重
    private int cityNum = 25;    //城市數量

    private HashMap<Integer, Unit> Pd = new HashMap<>();    //一顆粒子歷代中出現最好的解
    private Unit Pgd;   // 整個粒子群經歷過的的最好的解,每個粒子都能記住自己搜尋到的最好解
    private int bestT;// 最佳出現代數

    private ArrayList<ArrayList<SO>> listV = new ArrayList<>();    //自身交換序列,即所謂的慣性因子

    Random random = new Random();

    /**
     * 構造方法
     *
     * @param scale
     * @param MAX_GEN
     * @param w
     */
    public PSO(int scale, int MAX_GEN, float w) {
        this.scale = scale;
        this.MAX_GEN = MAX_GEN;
        this.w = w;
    }

    /**
     * 初始化引數配置
     */
    private void init() {
        cityNum = CityLab.getInstance().getmCities().size();
        random = new Random(System.currentTimeMillis());

    }

    /**
     * 初始化種群
     */
    private void initGroup() {
        for (int k = 0; k < scale; k++) {
            int[] path = new int[cityNum];
            for (int i = 0; i < cityNum; ) {
                //隨機生成一個城市路徑
                //int s = random.nextInt(max)%(max-min+1) + min;
                int s = random.nextInt(65535) % cityNum;
                int j;
                for (j = 0; j < i; j++) {
                    if (s == path[j]) {
                        break;
                    }
                }
                if (i == j) {
                    path[i] = s;
                    i++;
                }
            }
            Unit unit = new Unit(path);
            mUnits.add(unit);
        }
    }

    /**
     * 初始化自身的交換序列即慣性因子
     */
    private void initListV() {
        for (int i = 0; i < scale; i++) {
            ArrayList<SO> list = new ArrayList<>();
            int n = random.nextInt(cityNum - 1) % (cityNum);    //隨機生成一個數,表示當前粒子需要交換的對數
            for (int j = 0; j < n; j++) {
                //生成兩個不相等的城市編號x,y
                int x = random.nextInt(cityNum - 1) % (cityNum);
                int y = random.nextInt(cityNum - 1) % (cityNum);
                while (x == y) {
                    y = random.nextInt(cityNum - 1) % (cityNum);
                }

                //x不等於y
                SO so = new SO(x, y);
                list.add(so);
            }
            listV.add(list);
        }
    }

    public void solve() {
        initGroup();

        initListV();

        //挑選最好的個體
        for (int i = 0; i < scale; i++) {
            Pd.put(i, mUnits.get(i));
        }
        Pgd = Pd.get(0);
        for (int i = 0; i < scale; i++) {
            if (Pgd.getFitness() > Pd.get(i).getFitness()) {
                Pgd = Pd.get(i);
            }
        }
        System.out.println("初始化最好結果為:" + Pgd.getFitness());
        Pgd.printPath();

        // 進化
        evolution();

        // 列印
        System.out.println("==================最後粒子群=====================");

        System.out.println("最佳長度出現代數:");
        System.out.println(bestT);
        System.out.println("最佳長度");
        System.out.println(Pgd.getFitness());
        System.out.println("最佳路徑:");
        Pgd.printPath();

    }

    /**
     * 進化
     */
    private void evolution() {
        for (int t = 0; t < MAX_GEN; t++) {
            for (int k = 0; k < scale; k++) {
                ArrayList<SO> vii = new ArrayList<>();
                //更新公式:Vii=wVi+ra(Pid-Xid)+rb(Pgd-Xid)
                //第一部分,自身交換對
                int len = (int) (w * listV.get(k).size());
                for (int i = 0; i < len; i++) {
                    vii.add(listV.get(k).get(i));
                }

                //第二部分,和當前粒子中出現最好的結果比較,得出交換序列
                //ra(Pid-Xid)
                ArrayList<SO> a = minus(mUnits.get(k).getPath(), Pd.get(k).getPath());
                float ra = random.nextFloat();
                len = (int) (ra * a.size());
                for (int i = 0; i < len; i++) {
                    vii.add(a.get(i));
                }

                //第三部分,和全域性最優的結果比較,得出交換序列
                //rb(Pgd-Xid)
                ArrayList<SO> b = minus(mUnits.get(k).getPath(), Pgd.getPath());
                float rb = random.nextFloat();
                len = (int) (rb * b.size());
                for (int i = 0; i < len; i++) {
                    vii.add(b.get(i));
                }

                listV.remove(0);    //移除當前,加入新的
                listV.add(vii);

                //執行交換,生成下一個粒子
                exchange(mUnits.get(k).getPath(), vii);
            }

            //更新適應度的值,並挑選最好的個體
            for (int i = 0; i < scale; i++) {
                mUnits.get(i).upDateFitness();
                if (Pd.get(i).getFitness() > mUnits.get(i).getFitness()) {
                    Pd.put(i, mUnits.get(i));
                }
                if (Pgd.getFitness() > Pd.get(i).getFitness()) {
                    Pgd = Pd.get(i);
                    bestT = t;
                }
            }
            //列印當前代的結果
            if (t % 100 == 0) {
                // 列印
                System.out.println("--------第"+t+"代的最佳結果為-----------");
                System.out.println(Pgd.getFitness());
                System.out.println("最佳路徑:");
                Pgd.printPath();
            }
        }
    }

    /**
     * 執行交換,更新粒子
     *
     * @param path
     * @param vii  儲存的是需要交換的下標對
     */
    private void exchange(int[] path, ArrayList<SO> vii) {
        int tmp;
        for (SO so : vii) {
            tmp = path[so.getX()];
            path[so.getX()] = path[so.getY()];
            path[so.getY()] = tmp;
        }
    }


    /**
     * 生成交換對,把a變成和b一樣,返回需要交換的下標對列表
     *
     * @param a
     * @param b
     * @return
     */
    private ArrayList<SO> minus(int[] a, int[] b) {
        int[] tmp = a.clone();
        ArrayList<SO> list = new ArrayList<>();
        int index = 0;
        for (int i = 0; i < b.length; i++) {
            if (tmp[i] != b[i]) {
                //在tmp中找到和b[i]相等的值,將下標儲存起來
                for (int j = i + 1; j < tmp.length; j++) {
                    if (tmp[j] == b[i]) {
                        index = j;
                        break;
                    }
                }
                SO so = new SO(i, index);
                list.add(so);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        PSO pso = new PSO(30, 5000, 0.5f);
        pso.init();
        pso.solve();

    }
}

註釋寫得很詳細,這裡不再贅述,各位可以直接執行程式碼,通過debug的方式,這樣我覺得很容易理解的。看原始碼是學習程式設計最快的方式

最後感謝大家的觀看,如果有問題請評論區留言,本人將盡力解答。

4. 執行結果展示

這裡寫圖片描述

5. 原始碼下載連結

宣告
在參考下面連結的基礎上,對程式碼進行重構,使用java面向物件的思想,是程式碼可讀性大大增強

相關推薦

粒子演算法求解行商問題TSP JAVA實現

粒子群演算法求解旅行商問題TSP 寫在開頭: 最近師妹的結課作業問我,關於使用粒子群求解TSP問題的思路。我想了想,自己去年的作業用的是遺傳演算法,貌似有些關聯,索性給看了看程式碼。重新學習了一遍粒子群演算法,在這裡記錄一下,算是對知識的總結,鞏固一下。

PSO解決TSP問題粒子演算法解決行商問題--python實現

歡迎私戳關注這位大神! 有任何問題歡迎私戳我->給我寫信 首先來看一下什麼是TSP: The travelling salesman problem (TSP) asks the following question: "Given a list

【機器學習】利用蟻演算法求解行商TSP問題

如果喜歡這裡的內容,你能夠給我最大的幫助就是轉發,告訴你的朋友,鼓勵他們一起來學習。 If you like the content here, you can give me the greatest help is forwarding, tell you

遺傳演算法 求解行商 TSP 問題,matlab程式碼

學習啟發式演算法時,旅行商問題是一個經典的例子。其中,遺傳演算法可以用來求解該問題。遺傳演算法是一種進化演算法,由於其啟發式演算法的屬性,並不能保證得到最優解。求解效果與初始種群選取,編碼方法,選擇方法,交叉變異規則有關。 上課時,老師不知從哪裡找了一個非常粗糙的程式,自己

資料結構-基於鄰接矩陣實現圖的遍歷視覺化及使用Floyd、Dijkstra演算法求解最短路徑JavaScript實現

使用 JavaScript 基於鄰接矩陣實現了圖的深度、廣度遍歷,以及 Floyd、Dijkstra 演算法求解最短路徑。另外使用 SVG 實現圖的遍歷視覺化。一、輸入首先,輸入資料主要有兩個,一個是存放節點名的陣列,另一個是存放邊物件的陣列。例如://存放圖結點的陣列 va

資料結構-基於鄰接表實現圖的遍歷視覺化及使用Floyd、Dijkstra演算法求解最短路徑JavaScript實現

使用 JavaScript 基於鄰接表實現了圖的深度、廣度遍歷,以及 Floyd、Dijkstra 演算法求解最短路徑。另外使用 SVG 實現圖的遍歷視覺化。<!DOCTYPE html> <html lang="en"> <head>

LeetCode演算法題-Merge Sorted ArrayJava實現

這是悅樂書的第161次更新,第163篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第20題(順位題號是88)。給定兩個排序的整數陣列nums1和nums2,將nums2中的元素合併到nums1中,並且作為一個排序的陣列。在nums1和nums2中初始化的元素個數分別為m和

LeetCode演算法題-Balanced Binary TreeJava實現

這是悅樂書的第167次更新,第169篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第26題(順位題號是110)。給定二叉樹,判斷它是否是高度平衡的。對於此問題,高度平衡二叉樹定義為:一個二叉樹,其中每個節點的兩個子樹的深度從不相差超過1。例如:

LeetCode演算法題-Linked List CycleJava實現

這是悅樂書的第176次更新,第178篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第35題(順位題號是141)。給定一個連結串列,確定它是否有一個迴圈。 本次解題使用的開發工具是eclipse,jdk使用的版本是1.8,環境是win7 64位系統,使用Java語言編寫和

LeetCode演算法題-Factorial Trailing ZeroesJava實現

這是悅樂書的第183次更新,第185篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第42題(順位題號是172)。給定一個整數n,返回n!中的尾隨零數。例如: 輸入:3 輸出:0 說明:3! = 6,沒有尾隨零。 輸入:5 輸出:1 說明:5! = 120,一個尾隨零。

LeetCode演算法題-Reverse Linked ListJava實現

這是悅樂書的第192次更新,第195篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第51題(順位題號是206)。反轉單鏈表。例如: 輸入:1-> 2-> 3-> 4-> 5 輸出:5-> 4-> 3-> 2-> 1 本次

LeetCode演算法題-Contains Duplicate IIJava實現

這是悅樂書的第193次更新,第197篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第53題(順位題號是219)。給定整數陣列和整數k,找出陣列中是否存在兩個不同的索引i和j,使得nums [i] = nums [j]並且i和j之間的絕對差值小於等於k。例如: 輸入:nu

LeetCode演算法題-Power Of TwoJava實現

這是悅樂書的第194次更新,第200篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第56題(順位題號是231)。給定一個整數,寫一個函式來確定它是否是2的冪。例如: 輸入:1 輸出:true 說明:2^0 = 1 輸入:16 輸出:true 說明:2^4 = 16

LeetCode演算法題-Palindrome Linked ListJava實現

這是悅樂書的第196次更新,第202篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第58題(順位題號是234)。給出一個單鏈表,確定它是否是迴文。例如: 輸入:1-> 2 輸出:false 輸入:1-> 2-> 2-

資料結構與演算法 -- 二叉搜尋樹java實現

package com.huang.test.datastructure; import java.util.*; /** * 二叉搜尋樹 */ abstract class BstData<T> { BstData<T> left;

【LeetCode-演算法】55. 跳躍遊戲Java實現

題目 給定一個非負整數陣列,你最初位於陣列的第一個位置。陣列中的每個元素代表你在該位置可以跳躍的最大長度。判斷你是否能夠到達最後一個位置。 示例1 輸入: [2,3,1,1,4] 輸出: true 解釋: 從位置 0 到 1 跳 1 步, 然後跳 3 步到達最後

【LeetCode-演算法】59. 螺旋矩陣ⅡJava實現

題目 給定一個正整數 n,生成一個包含 1 到 n2 所有元素,且元素按順時針順序螺旋排列的正方形矩陣。 示例 輸入: 3 輸出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ] 程式碼實現 class Solutio

【LeetCode-演算法】75. 顏色分類Java實現

題目 給定一個包含紅色、白色和藍色,一共 n 個元素的陣列,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。 此題中,我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。 注意: 不能使用程式碼庫中的排序函式來解決這道題。 示例

【LeetCode-演算法】 79.單詞搜尋Java實現

題目 給定一個二維網格和一個單詞,找出該單詞是否存在於網格中。 單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母不允許被重複使用。 示例: board = [ ['A','B','C

演算法經典面試題整理java實現

以下從Java角度解釋面試常見的演算法和資料結構:字串,連結串列,樹,圖,排序,遞迴 vs. 迭代,動態規劃,位操作,概率問題,排列組合,以及一些需要尋找規律的題目。 1. 字串和陣列 字串和陣列是最常見的面試題目型別,應當分配最大的時間。 關於字串