货郎担问题TSP(dp解法)

119 篇文章 4 订阅
112 篇文章 0 订阅

题目链接

货郎担问题也叫旅行商问题,即TSP问题(Traveling Salesman Problem),是数学领域中著名问题之一。

题目背景

有n个城市,用1,2,…,n表示,城i,j之间的距离为dij,有一个货郎从城1出发到其他城市一次且仅一次,最后回到城市1,怎样选择行走路线使总路程最短?

货郎担问题(TSP问题)是一个组合优化问题。
该问题可以被证明具有NPC计算复杂性。

经典模型

邮路问题

假定有一辆邮车要到n个不同的地点收集邮件,
这种情况可以用n十1个结点的图来表示。
一个结点表示此邮车出发并要返回的那个邮局,
其余的n个结点表示要收集邮件的n个地点。
由地点i到地点j的距离则由边 < i,j > 上所赋予的成本来表示。
邮车所行经的路线是一条周游路线,希望求出具有最小长度的周游路线。

螺帽问题

第二个例子是在一条装配线上用一个机械手去紧固待装配部件上的螺帽问题。
机械手由其初始位置(该位置在第一个要紧固的螺帽的上方)开始,
依次移动到其余的每一个螺帽,最后返回到初始位置。
机械手移动的路线就是以螺帽为结点的一个图中的一条周游路线。
一条最小成本周游路线将使这机械手完成其工作所用的时间取最小值。

生产安排问题

第三个例子是产品的生产安排问题。
假设要在同一组机器上制造n种不同的产品,生产是周期性进行的,
即在每一个生产周期这n种产品都要被制造。
要生产这些产品有两种开销,一种是制造第i种产品时所耗费的资金(1≤i≤n),称为生产成本,
另一种是这些机器由制造第i种产品变到制造第j种产品时所耗费的开支cij称为转换成本。
显然,生产成本与生产顺序无关。
于是,我们希望找到一种制造这些产品的顺序,
使得制造这n种产品的转换成本和为最小。
由于生产是周期进行的,因此在开始下一周期生产时也要开支转换成本,
它等于由最后一种产品变到制造第一种产品的转换成本。
于是,可以把这个问题看成是一个具有n个结点,边成本为cij图的货郎担问题。

动态规划解法

现在使用最广泛的还是动态规划的解法,但也只是适用于规模较小的情况

设计状态
f(i,S),表示从起点到i经过集合S中的所有点的最短路径

f(i,S)=min{f(j,S-{j})+dis(i,j)}

初始状态:f(i,{})=dis(起点,i)
最终答案:f(起点,{1,2,3,4,…,n-1})
时间复杂度:n^2*2^n
所以n只能<=15

代码很简单
用的是递归的思想

num:还没有经过的点的个数
now:当前位置

调用的时候默认start—>now的来路已经处理好了
递归处理now—>终点
枚举和now相连的所有点
如果枚举点i在来路上没有经过,而且不是start(不能在中途就回到起点了)
那么我们就继续递归

终止条件是:num==1,返回当前点到终点的距离

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

int n;
struct node{
    int x,y;
    bool operator < (const node &a)const
    {
        return (x<a.x||(x==a.x&&y<a.y));
    }
};
node po[1005];
bool p[1005];

double dis(node x,node y)
{
    return sqrt((double)(x.x-y.x)*(x.x-y.x)+(double)(x.y-y.y)*(x.y-y.y));
}

double doit(int num,int now)
{
    if (num==1) return dis(po[1],po[now]);

    double ans=1e9;
    for (int i=2;i<=n;i++)                //不能中途回到起点 
        if (i!=now&&p[i])                 //除了起点,每个点只经过一次 
        {
            p[i]=0;
            ans=min(ans,dis(po[now],po[i])+doit(num-1,i));
            p[i]=1;
        }        
    return ans;
}

int main()
{
    while (scanf("%d",&n)!=EOF)
    {
        for (int i=1;i<=n;i++) scanf("%d%d",&po[i].x,&po[i].y);
        sort(po+1,po+1+n);
        memset(p,1,sizeof(p));
        printf("%0.2lf\n",doit(n,1));
    }
    return 0;
}

然而上述方法虽然简单易懂,但是没有办法记忆化搜索
非常容易就T了,难道我们就这样GG了吗
实际上我们还有一种dp方法:

我们想象有两个人同时从起点向终点走,
设计状态:f[i][j]表示第1个人走到第i个点,第2个人走到了第j个点
那我们要怎么转移呢?
换句话说,现在的状态能不能转移到f[i+1][j]?
不好说,因为上面的状态不能表示哪些点还没有经过
所以这不是一个好的状态定义

那我们修改一下:
f[i][j]表示第1个人走到第i个点,第2个人走到了第j个点,
且1~max(i,j)我们都走过了,这种情况下还有多少路要走
不难发现f[i][j]=f[j][i]
为了方便,我们规定i>j
这样我们就可以列出转移方程了:

f[i][j]=min{f[i+1][j]+dis(i,i+1),f[i+1][i]+dis(j,i+1)}

边界条件是:f[n-1][j]=dis(n-1,n)+dis(j,n)

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

int n;
struct node{
    int x,y;
    bool operator < (const node &a)const
    {
        return (x<a.x||(x==a.x&&y<a.y));
    }
};
node po[1005];
double f[1005][1005];
bool vis[1005][1005];

double dis(node x,node y)
{
    return sqrt((double)(x.x-y.x)*(x.x-y.x)+(double)(x.y-y.y)*(x.y-y.y));
}

double doit(int w1,int w2)
{
    if (w1==n-1)
    {
        return dis(po[n-1],po[n])+dis(po[w2],po[n]);
    }

    if (vis[w1][w2]) return f[w1][w2];

    vis[w1][w2]=1;
    double &ans=f[w1][w2];
    ans=1e9;
    ans=min(doit(w1+1,w2)+dis(po[w1+1],po[w1]),doit(w1+1,w1)+dis(po[w2],po[w1+1]));
    return ans;
}

int main()
{
    while (scanf("%d",&n)!=EOF)
    {
        for (int i=1;i<=n;i++) scanf("%d%d",&po[i].x,&po[i].y);
        sort(po+1,po+1+n);
        memset(vis,0,sizeof(vis));
        memset(f,0,sizeof(f));
        printf("%0.2lf\n",doit(1,1));
    }
    return 0;
}
  • 11
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
动态规划货郎担问题是指在给定的n个城市之间,求解一条经过每个城市恰好一次的最短路径。这个问题可以使用动态规划算法来解决。具体来说,可以使用一个二维数组g[i][S]来表示从城市i开始,经过集合S中的所有城市,最终回到城市1的最短路径长度。其中,集合S是除了城市1之外的所有城市的集合。根据动态规划的思想,可以先求解子问题,再从子问题的解得到原问题的解。因此,可以按照以下步骤来求解动态规划货郎担问题: . 初始化g[i][S],当S中只有一个城市j时,g[i][{j}]的值为城市i到城市j的距离。 2. 对于集合S中的每个城市j,计算g[i][S]的值。具体来说,可以枚举S中除了j之外的所有城市k,计算g[k][S-{j}]的值,然后加上城市i到城市j的距离,取最小值即可。 3. 最终的答案是g[{2,3,...,n}],即从城市1开始,经过除了城市1之外的所有城市,最终回到城市1的最短路径长度。 下面是动态规划货郎担问题的C++代码实现: ``` #include <iostream> #include <cstring> using namespace std; const int MAXN = 20; const int INF = 0x3f3f3f3f; int n, cost[MAXN][MAXN], g[MAXN][1 << MAXN]; int tsp(int start, int state) { if (state == (1 << n) - 1) { return cost[start][0]; } if (g[start][state] != -1) { return g[start][state]; } int ans = INF; for (int i = 0; i < n; i++) { if ((state & (1 << i)) == 0) { ans = min(ans, cost[start][i] + tsp(i, state | (1 << i))); } } return g[start][state] = ans; } int main() { memset(g, -1, sizeof(g)); cout << "请输入n的值" << endl; cin >> n; int cc[MAXN][MAXN]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cout << "请输入各成本值" << endl; cin >> cc[i][j]; cost[i][j] = cc[i][j]; } } cout << tsp(0, 1) << endl; return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值