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之间的输入,生成的节点信息如下:

 

动态规划旅行商问题Python并输出路线 动态规划 旅行商_i++

 

 

 

 

 

 

统计的时间如下:


节点数

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


将这些数据绘制成图表如下:

 

动态规划旅行商问题Python并输出路线 动态规划 旅行商_最短路径_02

 

 

由此可见,随着节点数目的增加,处理时间是呈指数增长的。

动态规划算法属于用精确算法求解该问题,常用的精确方法还包括:分枝定界法、线性规划法等。但是,从图表中可以看出,随着问题规模的增大,精确算法将变得无能为力,因此,在后来的研究中,我们可以尝试用遗传算法、模拟退火算法、蚁群算法、禁忌搜索算法、贪婪算法和神经网络方法等解决该问题。

 

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