1. 程式人生 > >動態規劃思想求旅行商問題

動態規劃思想求旅行商問題

1.      旅行商問題

1.1 旅行商問題描述

旅行商問題(TSP問題)是指旅行家要旅行n個城市然後回到出發城市,要求各個城市經歷且僅經歷一次,並要求所走的路程最短。該問題又稱為貨郎擔問題、郵遞員問題、售貨員問題,是圖問題中最廣為人知的問題。解決旅行商問題有很多的求解方法,如蠻力法、動態規劃法、貪心法和分支限界法等。主要研究用動態規劃演算法求解TSP問題,並對演算法的效能進行了分析。

1.2 數學模型

給定一個完全無向帶權圖G=(V,E),其每條邊(u,v)∈E有一非負整數權值w(u,v)。要求找出G的一條經過每個頂點一次且僅經過一次的迴路,使得該回路上所有邊的權值之和儘可能地小。

1.3 演算法分析

旅行商問題的各個城市間的距離可以用代價矩陣來表示,就是鄰接矩陣表示法。如果,則Cij = 。

先說明旅行商問題具有最優解結構。設S1,S2,…,Sp, s是從s出發的一條路徑長度最短的簡單迴路,假設從s到下一個城市S1已經求出,則問題轉化為求S1到S的最短路徑,顯然S1,S2,…,Sp,,s一定構成一條從S1到S的最短路徑,如果不然,設S1,S2,…,Sp,s是一條從S1到S的最短路徑且經過n-1個城市,則S1,S2,…,Sp,將是從S出發的路徑長度最短的簡單迴路且比S1,S2,…,Sp,s要短,從而導致矛盾。所以,旅行商問題一定滿足最優性原理。

2.      動態規劃演算法

2.1 動態規劃法的設計思想

動態規劃法將待求解問題分解成若干個相互重疊的子問題,每個子問題對應決策過程的一個階段,一般來說,子問題的重疊關係表現在對給定問題求解的遞推關係(也就是動態規劃函式)中,將子問題的解求解一次並填入表中,當需要再次求解此子問題時,可以通過查表獲得該子問題的解而不用再次求解,從而避免了大量重複計算。

2.2 動態規劃思想的函式

假設從頂點i出發,令d(i, V’)表示從頂點i出發經過V’中各個頂點一次且僅一次,最後回到出發點i的最短路徑長度,開始時,V’ = V – {i}, 於是,TSP問題的動態規劃函式為:

2.3 基於動態規劃思想的演算法分析

for (i=1; i<N; i++) 

 d[i][0]=c[i][0];  

for (j=1; j<2n-1; j++)   

for (i=1; i<n; i++)   

if (子集V[j]中不包含i)  

           對V[j]中的每個元素k,

計算V[m] == V[j]-k; 

d[i][j]=min(c[i][k]+d[k][m]);   

對V[2n-1 -1]中的每一個元素k,計算V[m] == V[2n-1-1]-k; 

d[0][2n-1 -1]=min(c[0][k]+d[k][m]); 

輸出最短路徑長度d[0][2n-1 -1];

2.4 時間複雜性

T(n) = O(N *2n)

和蠻力法相比,動態規劃法求解TSP問題,把原來的時間複雜性是O(n!)的排列問題,轉化為組合問題,從而降低了演算法的時間複雜性,但它仍需要指數時間。

3.      時間統計和結果對比分析

軟體環境:Win7 , Microsoft Visual Studio 2008

硬體環境:PC機,1.8GHZ主頻,2G記憶體 

隨機生成100次規模在15至20之間的輸入,生成的節點資訊如下:

 

統計的時間如下:

節點數

100個隨機輸入的該節點數的個數

平均時間(ms)

15

19

1620.84

16

15

4764.20

17

19

10430.42

18

17

18674.24

19

15

36537.87

20

15

66489.53

將這些資料繪製成圖表如下:

 

由此可見,隨著節點數目的增加,處理時間是呈指數增長的。

動態規劃演算法屬於用精確演算法求解該問題,常用的精確方法還包括:分枝定界法、線性規劃法等。但是,從圖表中可以看出,隨著問題規模的增大,精確演算法將變得無能為力,因此,在後來的研究中,我們可以嘗試用遺傳演算法、模擬退火演算法、蟻群演算法、禁忌搜尋演算法、貪婪演算法和神經網路方法等解決該問題。

4.      演算法原始碼

private voidTSP(object  pa)
        {
            int[,] num=((StrPara)pa).num;  //隨機資料陣列
            int index =((StrPara)pa).index;//list列表的index
            int n = ((StrPara)pa).n;         //節點數目
            int i,j,k,min,temp;
            int b=(int)Math.Pow(2,n-1);
            int[,] F=new int[n,b];  //生成的表
           int[,] M=new int[n,b];   //儲存路徑
           //for (i = 0; i < b; i++)  //初始化F[][]和M[][]  
           //{
           //   for (j = 0; j < n; j++)
           //   {
           //        F[j, i] = -1;
           //        M[j, i] = -1;
           //   }
          
           //}
  
    //給F的第0列賦初值  
    for(i=0;i<n;i++) 
       F[i,0] =num[i,0];
 
    DateTime timestart;
    DateTime timeend;
           timestart = DateTime.Now;   //計時開始
    //遍歷並填表  
    int m=0;
    for (i = 1; i < b - 1; i++)//最後一列不在迴圈裡計算  
    {
        for (j = 1; j < n; j++)
        {
            if (((int)Math.Pow(2, j - 1) &i) == 0)//結點j不在i表示的集合中  
            {
                m++;
                min = 65535;
                for (k = 1; k < n; k++)
                {
                    if (((int)Math.Pow(2, k -1) & i) != 0)//非零表示結點k在集合中  
                    {
                        temp = num[j, k] + F[k,i - (int)Math.Pow(2, k - 1)];
                        if (temp < min)
                        {
                            min = temp;
                            F[j, i] = min;//儲存階段最優值  
                            M[j, i] = k;//儲存最優決策  
                        }
                    }
                }
            }
        }
    } 
 
     timeend = DateTime.Now;
    TimeSpan ts = timeend - timestart;
   //最後一列,即總最優值的計算 
    min=65535; 
    for(k=1;k<n;k++) 
    { 
    temp = num[0, k] + F[k, b - 1 - (int)Math.Pow(2,k - 1)]; 
        if(temp < min) 
        { 
            min = temp; 
            F[0,b-1] = min; //總最短路徑
            M[0,b-1] = k; 
        } 
    } 
     //生成路徑
    string strtem = "0->";
    for(i=b-1,j=0; i>0; )//i的二進位制是5個1,表示集合{1,2,3,4,5}  
   { 
        j = M[j,i];//下一步去往哪個結點  
        i = i - (int)Math.Pow(2, j - 1);//從i中去掉j結點  
        strtem += j.ToString() +"->";
    }
    strtem += "0";
    StrNode node = (StrNode)list[index];
    node.count = F[0, b - 1];
    node.strLine = strtem;
    node.time = (int)(ts.TotalMilliseconds);
    list[index] = node;
    this.Invoke(newfnShowPrograss(fnShowPrograss1));
     }