基於貪心算法求解TSP問題(JAVA)


概述

前段時間在搞貪心算法,為了舉例,故拿TSP來開刀,寫了段求解算法代碼以便有需之人,注意代碼考慮可讀性從最容易理解角度寫,沒有優化,有需要可以自行優化!

詳細

前段時間在搞貪心算法,為了舉例,故拿TSP來開刀,寫了段求解算法代碼以便有需之人,注意代碼考慮可讀性從最容易理解角度寫,沒有優化,有需要可以自行優化!

一、TPS問題

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

TSP問題是一個組合優化問題。該問題可以被證明具有NPC計算復雜性。TSP問題可以分為兩類,一類是對稱TSP問題(Symmetric TSP),另一類是非對稱問題(Asymmetric TSP)。所有的TSP問題都可以用一個圖(Graph)來描述:

V={c1, c2, …, ci, …, cn},i = 1,2, …, n,是所有城市的集合.ci表示第i個城市,n為城市的數目;

E={(r, s): r,s∈ V}是所有城市之間連接的集合;

C = {crs: r,s∈ V}是所有城市之間連接的成本度量(一般為城市之間的距離);

如果crs = csr, 那么該TSP問題為對稱的,否則為非對稱的。

 

一個TSP問題可以表達為:

求解遍歷圖G = (V, E, C),所有的節點一次並且回到起始節點,使得連接這些節點的路徑成本最低。

二、貪心算法

貪心算法,又名貪婪算法(學校里老教授都喜歡叫貪婪算法),是一種常用的求解最優化問題的簡單、迅速的算法。貪心算法總是做出在當前看來最好的選擇,它所做的每一個在當前狀態下某種意義上是最好的選擇即貪心選擇,並希望通過每次所作的貪心選擇導致最終得到問題最優解。必須注意的是,貪心算法不是對所有問題都能得到整體最優解,選擇的貪心策略必須具備無后效性,即某個狀態以后的過程不會影響以前的狀態,只與當前狀態有關。

1、貪心算法的基本思路

從問題的某一個初始解觸發逐步逼近給定的目標,以盡可能快地求得更好的解。當達到某算法中的某一步不能再繼續前進時,算法停止。大致步驟如下:

1)建立數學模型來描述問題;

2)把求解的問題分成若干個子問題

3)對每一個子問題求解,得到子問題的局部最優解

4)把子問題的局部最優解合成原問題的一個解

2、貪心算法的實現框架

貪心算法沒有固定的算法框架,算法設計的關鍵是貪心策略的選擇,而貪心策略適用的前提是:局部最優策略能導致產生全局最優解。

從問題的某一初始解出發;

while (能朝給定總目標前進一步)

{

利用可行的決策,求出可行解的一個解元素;

}

由所有解元素組合成問題的一個可行解;

3、貪心算法存在的問題

1)不能保證求得的最后解是最佳的;

2)不能用來求最大最小解問題;

3)只能在某些特定條件約束的情況下使用,例如貪心策略必須具備無后效性等。

4、典型的貪心算法使用領域

馬踏棋盤、背包、裝箱等

三、貪心算法求解TSP問題

貪心策略:在當前節點下遍歷所有能到達的下一節點,選擇距離最近的節點作為下一節點。基本思路是,從一節點出發遍歷所有能到達的下一節點,選擇距離最近的節點作為下一節點,然后把當前節點標記已走過,下一節點作為當前節點,重復貪心策略,以此類推直至所有節點都標記為已走節點結束。

我們使用TSP問題依然來自於tsplib上的att48,這是一個對稱TSP問題,城市規模為48,其最優值為10628.其距離計算方法下圖所示:

好,下面是具體代碼:

package noah;  
  
import java.io.BufferedReader;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStreamReader;  
  
public class TxTsp {  
  
    private int cityNum; // 城市數量  
    private int[][] distance; // 距離矩陣  
  
    private int[] colable;//代表列,也表示是否走過,走過置0  
    private int[] row;//代表行,選過置0  
  
    public TxTsp(int n) {  
        cityNum = n;  
    }  
  
    private void init(String filename) throws IOException {  
        // 讀取數據  
        int[] x;  
        int[] y;  
        String strbuff;  
        BufferedReader data = new BufferedReader(new InputStreamReader(  
                new FileInputStream(filename)));  
        distance = new int[cityNum][cityNum];  
        x = new int[cityNum];  
        y = new int[cityNum];  
        for (int i = 0; i < cityNum; i++) {  
            // 讀取一行數據,數據格式1 6734 1453  
            strbuff = data.readLine();  
            // 字符分割  
            String[] strcol = strbuff.split(" ");  
            x[i] = Integer.valueOf(strcol[1]);// x坐標  
            y[i] = Integer.valueOf(strcol[2]);// y坐標  
        }  
        data.close();  
  
        // 計算距離矩陣  
        // ,針對具體問題,距離計算方法也不一樣,此處用的是att48作為案例,它有48個城市,距離計算方法為偽歐氏距離,最優值為10628  
        for (int i = 0; i < cityNum - 1; i++) {  
            distance[i][i] = 0; // 對角線為0  
            for (int j = i + 1; j < cityNum; j++) {  
                double rij = Math  
                        .sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j])  
                                * (y[i] - y[j])) / 10.0);  
                // 四舍五入,取整  
                int tij = (int) Math.round(rij);  
                if (tij < rij) {  
                    distance[i][j] = tij + 1;  
                    distance[j][i] = distance[i][j];  
                } else {  
                    distance[i][j] = tij;  
                    distance[j][i] = distance[i][j];  
                }  
            }  
        }  
  
        distance[cityNum - 1][cityNum - 1] = 0;  
  
        colable = new int[cityNum];  
        colable[0] = 0;  
        for (int i = 1; i < cityNum; i++) {  
            colable[i] = 1;  
        }  
  
        row = new int[cityNum];  
        for (int i = 0; i < cityNum; i++) {  
            row[i] = 1;  
        }  
  
    }  
      
    public void solve(){  
          
        int[] temp = new int[cityNum];  
        String path="0";  
          
        int s=0;//計算距離  
        int i=0;//當前節點  
        int j=0;//下一個節點  
        //默認從0開始  
        while(row[i]==1){  
            //復制一行  
            for (int k = 0; k < cityNum; k++) {  
                temp[k] = distance[i][k];  
                //System.out.print(temp[k]+" ");  
            }  
            //System.out.println();  
            //選擇下一個節點,要求不是已經走過,並且與i不同  
            j = selectmin(temp);  
            //找出下一節點  
            row[i] = 0;//行置0,表示已經選過  
            colable[j] = 0;//列0,表示已經走過  
              
            path+="-->" + j;  
            //System.out.println(i + "-->" + j);  
            //System.out.println(distance[i][j]);  
            s = s + distance[i][j];  
            i = j;//當前節點指向下一節點  
        }  
        System.out.println("路徑:" + path);  
        System.out.println("總距離為:" + s);  
          
    }  
      
    public int selectmin(int[] p){  
        int j = 0, m = p[0], k = 0;  
        //尋找第一個可用節點,注意最后一次尋找,沒有可用節點  
        while (colable[j] == 0) {  
            j++;  
            //System.out.print(j+" ");  
            if(j>=cityNum){  
                //沒有可用節點,說明已結束,最后一次為 *-->0  
                m = p[0];  
                break;  
                //或者直接return 0;  
            }  
            else{  
                m = p[j];  
            }  
        }  
        //從可用節點J開始往后掃描,找出距離最小節點  
        for (; j < cityNum; j++) {  
            if (colable[j] == 1) {  
                if (m >= p[j]) {  
                    m = p[j];  
                    k = j;  
                }  
            }  
        }  
        return k;  
    }  
  
  
    public void printinit() {  
        System.out.println("print begin....");  
        for (int i = 0; i < cityNum; i++) {  
            for (int j = 0; j < cityNum; j++) {  
                System.out.print(distance[i][j] + " ");  
            }  
            System.out.println();  
        }  
        System.out.println("print end....");  
    }  
  
    public static void main(String[] args) throws IOException {  
        System.out.println("Start....");  
        TxTsp ts = new TxTsp(48);  
        ts.init("c://data.txt");  
        //ts.printinit();  
        ts.solve();  
    }  
}

四、項目運行介紹

下載項目后,導入eclipse,項目截圖如下:

image.png

求解結果截圖:

五、總結

單從求解結果來看,我個人其實還是能接受這個解,但仔細想想,實際上這個求解結果有太多運氣成分在里面,貪心算法畢竟是貪心算法,只能緩一時,而不是長久之計,問題的模型、參數對貪心算法求解結果具有決定性作用,這在某種程度上是不能接受的,於是聰明的人類就發明了各種智能算法(也叫啟發式算法),但在我看來所謂的智能算法本質上就是貪心算法和隨機化算法結合,例如傳統遺傳算法用的選擇策略就是典型的貪心選擇,正是這些貪心算法和隨機算法的結合,我們才看到今天各種各樣的智能算法。

注:本文著作權歸作者,由demo大師發表,拒絕轉載,轉載需要作者授權

 


免責聲明!

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



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