C++动态规划解决TSP(旅行商)问题

题目描述:

某旅行商希望从某城市出发经过一系列的城市最后再回到出发的城市。这些城市之间均可直航,他希望只经过这些城市一次且旅行的总线路最短。设有n个城市,城市的编号从1到n。

输入第一行为整数n,表示城市的数量。其后n行,每行有n个整数,用空格隔开,表示城市之间的距离。其中的第1个数表示城市1和城市2之间的距离,第2个数表示城市1和城市3之间的距离,…,第 n-1个数表示城市1和城市n之间的距离,依次类推。

解题思路:

给出一个样例,5个城市

二维表格表示城市之间的距离

04865
403210
83087
62803
510730

显然我们可以想到有很多种路线,

1 ——>  2 ——> 3 ——> 4 ——> 5 ——>  1;

1 ——>  2 ——> 3 ——> 5 ——> 4 ——>  1;

如果把所有情况都列出来,显然是可以解决这个问题的(回溯法),利用动态规划去解决这个问题则需要去将这个问题分割成子问题,下面给出dp数组的含义以及分析过程:

dp[i][j] :从i 出发经过集合 j里面所有节点并回到起点的最小距离

乍一看可能觉得很蒙圈,下面给出几个dp[][]的值帮助理解

dp[1][0] = ?  1 代表第一个城市,也就是起点,0是点的集合,那么意思就是没有点,那么从1回到1的距离是0,dp[1][0] = 0;

dp[2][0] =? 2代表第二个城市,0的意思也是不需要再经过任何点,从2回到1的距离即dp[2][0]的值dp[2][0]  = 4

以此类推,我们可以得到所有的dp[n][0],这也是dp的初始化的一部分,因为我们要计算的是经过所有城市的花费,那么dp[1][0]、dp[1][1]、dp[1][2]都是无意义的,我们只需要去计算dp[1][除了起点之外的所有城市]。

dp[2][1] = ?从2出发,经过1个城市回到1的值?

这里的1并不代表要经过多少个城市,因为这个问题本质上是组合问题,我们要去准确的知道经过的是哪些城市,而不是数量,因此 j 也就是这里的 1 是一个二进制数,利用二进制来表示要经过的点 1  ==  0001 (5个城市除去起点还剩4个),注意这里并不表示第一个城市,回看我们的dp定义——从i 出发经过集合 j里面所有节点并回到起点,已经包含了回到起点,那么这里表示的其实是第二个城市。回到dp[2][1],从2出发经过  0001(就是第2个城市)那么显然这个是无意义的。它与dp[2][0]的意义相同。

到这应该能知道dp[3][1]代表的意思了,从3出发经过 0001(第2个城市)回到1的最小距离,那么怎么计算呢?

动态规划最重要的便是状态转移当前求的问题要能够从之前的问题求得。

3 ——> 2 ——> 1   可以看不可以看成

3 ——> 2 ——> 1   加粗的部分我们是不是已经计算过了?是的就是dp[2][0],

那么dp[3][1] = dp[2][0] + value[3][2](value[i][j]代表从i到j的距离)

dp[4][3] 的意思呢?

从4出发经过 0011(第2和3城市)回到1的最小距离,

有两种方案:4 ——> 3 ——> 2——> 1                4 ——> 2 ——> 3——> 1

第一种的状态转移:4 ——> 3 ——> 2——> 1   dp[4][3] = dp[3][1] + value[4][3];

第二种的状态转移:4 ——> 2 ——> 3——> 1   dp[4][3] = dp[2][2] + value[4][2];这里为什么是dp[2][2]呢?2 = 0010(也就是第三个城市)

显然将两者取min即使答案,至此基本的思路就解释完了,下面给出完整的dp表格以及代码,具体的细节就给读者自行体会了。

#include<iostream>
#include<vector>
#include<algorithm>
#include<time.h>
#include<sys/timeb.h>
using namespace std;

int main() {
	int n;
	cin >> n;
	vector<vector<int>>value(n+1,vector<int>(n+1,0));
	int dp_Size=(1 << (n - 1));
	int ans = 10000;
	vector<vector<int>>dp(n, vector<int>(dp_Size,10000));
	vector<int>dp_ans(n - 1);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> value[i][j];
		}
	}

	for (int i = 1; i < n; i++) {
		dp[i][0] = value[i + 1][1];
	}
	for (int i = 1; i < dp_Size-1; i++) {
		for (int j = 1; j < n; j++) {
			if ((1 << (j - 1) & i) != 0) {
				continue;
			}
			for (int k = 1; k < n;k++) {
				if (((1 << (k - 1)) & i)) { //按位与
					dp[j][i] = min(dp[j][i], dp[k][i-(1<<(k-1))] + value[j+1][k + 1]);
					dp_ans[j - 1] = dp[j][i];
				}
			}
		}
	}	
	for (int i = 1; i < n; i++) {
		ans = min(ans, value[1][i + 1] + dp_ans[i - 1]);
	}
	/*for (int j = 0; j < n; j++) {
		for (int i = 0; i < dp_Size; i++) {
			cout << dp[j][i]<<"  ";
		}
		cout << endl;
	}*/
    //打印dp数组
	cout << endl;
	/*for (int i = 0; i < n - 1; i++) {
		cout << dp_ans[i]<<"\t";
	}*/
	cout << ans;

	return 0;
}

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
旅行商问题TSP)是一个著名的组合优化问题,它要求找到一条路径,使得从一个起点出发,经过所有给定的点恰好一次,并最终回到起点,使得路径的总长度最小。 动态规划算法解决TSP问题的一种有效方法,以下是用c++实现动态规划算法解决TSP问题的基本步骤: 1. 定义状态:定义dp[i][j]表示从起点出发,经过集合S中的所有点,最终到达点j的最短路径长度(其中集合S表示除起点和终点以外的所有点的集合)。 2. 初始化状态:对于所有的i和j,初始化dp[i][j]为无穷大。 3. 状态转移方程:对于每个i和j,枚举集合S中的所有点k,更新dp[i][j] = min(dp[i][j], dp[i-{k}][k] + dis[k][j]),其中dis[k][j]表示点k到点j的距离。 4. 最终结果:最终的结果为dp[起点][终点]。 以下是c++代码实现: ```c++ const int N = 20; const int INF = 1e9; int n; // n为点的个数 int dis[N][N]; // dis[i][j]表示点i到点j的距离 int dp[1 << N][N]; // dp[i][j]表示从起点出发,经过集合S中的所有点,最终到达点j的最短路径长度 int tsp() { memset(dp, INF, sizeof(dp)); dp[1][0] = 0; for(int s = 1; s < (1 << n); s++) { for(int i = 0; i < n; i++) { if(!(s & (1 << i))) continue; for(int j = 0; j < n; j++) { if(s & (1 << j)) continue; dp[s | (1 << j)][j] = min(dp[s | (1 << j)][j], dp[s][i] + dis[i][j]); } } } return dp[(1 << n) - 1][0]; // 返回从起点出发,经过所有点,最终回到起点的最短路径长度 } int main() { cin >> n; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { cin >> dis[i][j]; } } cout << tsp() << endl; return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亏贼的baby

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值