TSP问题(推销员问题)

推销员问题

内容

有一推销员,欲到 n n n( n ≤ 10 n\le10 n10)个城市推销产品。为了节省旅行费用,在出发前他查清了任意两个城市间的旅行费用,想找到一条旅行路线,仅经过每个城市一次,且使旅行费用最少。本问题已知城市 n n n,和 n × n n\times n n×n 的表达任意两个城市间费用的矩阵。试求最短路径及其费用。


一、问题分析(模型、算法设计和正确性证明等)

1、遍历方法
模型:
采用矩阵表示的图结构进行存储
算法设计:
遍历算法,计算出所有的可能行进路线,将其对应权值相加取最小。其中全排列的给出使用递归方法。
正确性证明:
因为考虑到所有的可能情况,该方法正确。
2、递归方法
模型:
采用矩阵表示的图结构进行存储
算法设计:
递归算法,从一点遍历另一集合所有点再回到自身的最小花费等同于先到达该集合任意点再从该点遍历剩余的点构成的子集的最小花费。
正确性证明:
设定出发点$x$,目标遍历集合$y$,则$TSP(x,y) = \min_{i\in y}(TSP(i,y-i)+edge[x][i])$,其中,$edge[x][i]$表示从$x$到$i$的距离。则按照这种方法进行向下递归求解,最终集合会变为$\phi$,此时即为递归出口,返回值为$TSP(x,\phi) = edge[x][0]$(假定0是是初始点)。由于每一个子问题均为最优解,因此主要问题即为最优解。

二、复杂度分析

1、遍历法

假设共要去 n n n个地方。

时间复杂度:

​ 第一步是计算 n n n的全排列问题,使用递归的方法为 p r e m ( R ) = ∑ i = 1 n r i p r e m ( r i ) prem(R) = \sum_{i=1}^{n}r_i prem(r_i) prem(R)=i=1nriprem(ri),而当 n = 1 n = 1 n=1时, p r e m ( R ) = ( r ) prem(R) = (r) prem(R)=(r)。因此,算法复杂度为 ∏ i = 1 n i = n ! \prod_{i=1}^{n}i=n! i=1ni=n!

​ 第二步是计算给出的序列路径权重,即对算出的 n ! n! n!条路径进行长度计算,每条路径计算时相加 n n n次,即算法复杂度为 n ∗ n ! n*n! nn!

​ 第三步是对计算出的 n ! n! n!个路径权重查找最小值, n ! n! n!

因此复杂度为 O ( ( n + 1 ) ! ) O((n+1)!) O((n+1)!)

空间复杂度:

​ 采用 n ∗ n n*n nn的矩阵记录图中边的权重。采用内含 p a t h [ n ] path[n] path[n]数组的结构体表示一个解决方案。共有 n ! n! n!个解决方案(即全排列个数),故空间复杂度为 O ( ( n + 1 ) ! ) O((n+1)!) O((n+1)!)

2、递归法
时间复杂度:

​ 为了更加方便地表示点集合,采用离散数学中特征函数表示方法表示,即第 i i i个数字如果存在即为 1 1 1,否则为 0 0 0。所以每一个二进制数可以表示一个集合,二进制数用十进制数进行表示。在这种表示方法下,每一个规模为 i i i的递归程序调用 i − 1 i-1 i1次比较取最小值,因此时间复杂度为 ∑ i = 1 n i ( i − 1 ) C n i = O ( 2 n n 2 ) \sum_{i=1}^{n}i(i-1)C_{n}^{i} = O(2^n n^2) i=1ni(i1)Cni=O(2nn2)

空间复杂度:

​ 为了避免重复递归,使用空间代替时间的方法,将每一次的递归求解结果使用一个数组进行存储再次需要时直接返回此结果,故空间复杂度为 O ( ( n + 1 ) ! ) O((n+1)!) O((n+1)!)

三、程序实现和测试过程和结果(主要描述出现的问题和解决方法)

程序实现:
1、遍历法
(1)空间爆炸问题:

​ 程序内申请 i n t   a [ 10 ! ] int ~a[10!] int a[10!]会导致程序停止。解决方法为将数组声明为全局变量,因为局部变量存在于动态数据区中,而全局变量存在于静态存储区中。

(2)全排列的给出:

​ 全排列利用 p r e m ( R ) = ∑ i = 1 n r i p r e m ( r i ) prem(R) = \sum_{i=1}^{n}r_i prem(r_i) prem(R)=i=1nriprem(ri),进行递归程序的编写,递归方法为将每一个需要求的序列的除第一个外所有元素与第一个元素换位置之后求以该元素开头的序列全排列,递归出口为要求的全排列序列长度为 1 1 1。代码如下:

void PREM(int lst[], int low, int high, solve T[],int N)
{
    if(low == high)
    {
        for(int i=0; i<N; i++) T[flag].path[i] = lst[i];
        flag++;
    }
    else
    {
        for(int i=low; i<=high; i++)
        {
            swap(lst[low],lst[i]);
            PREM(lst, low+1, high, T, N);
            swap(lst[low],lst[i]);
        }
    }
}
2、递归法
(1)空间爆炸问题:

​ 同上。

(2)重复调用问题:

​ 递归函数在调用的时候会多次调用到相同的子问题,会导致不必要的时间浪费,因此将每一次的递归求解结果使用一个数组进行存储再次需要时直接返回此结果。代码如下:

int TSP(int x, int y)
{
    int re = inf;
    if(cost[x][y]) return cost[x][y];				//代替数组
    if(y==0) return edge[x][0];						//递归出口
    for(int i=0; i<n; i++)
    {
        if(!((y>>i)&1)) continue;					//判断是否访问
        int temp = TSP(i,y-pow(2,i))+edge[x][i];	//更新条件
        if(re > temp) re = temp;
    }
    cost[x][y] = re;
    return re;
}
(3)打印路径问题

​ 打印路径时同样使用递归函数,但是在已经计算完成的 c o s t cost cost数组下进行计算。找到每一个路径对应的 n e x t next next再进行对于 n e x t next next的搜索。代码如下:

void print_path(int x, int y)
{
    if(y==0) return;
    int re = inf, next;
    for(int i=0; i<n; i++)
    {
        if(!((y>>i)&1)) continue;
        if(re > cost[i][y-(int)pow(2,i)]+edge[x][i])
        {
            next=i; re=cost[i][y-(int)pow(2,i)]+edge[x][i];
        }
    }
    cout<<next+1<<" ";
    print_path(next, y-pow(2,next));
}

四、总结(经验和反思等)

1、大空间的存储:较大空间的申请最好放在全局变量进行申请。

2、可以使用空间简化时间,在递归时使用数组进行存储可以简化递归的调用次数。

3、相对于暴力求解方法,递归方法的空间复杂度更低,可以求解的问题规模也更大。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值