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));
}