動態規劃思想求旅行商問題
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));
}