用动态规划算法解Travelling Salesman Problem(TSP)问题

用动态规划算法解Travelling Salesman Problem(TSP)问题

基础知识

  Travelling Salesman Problem (TSP) 是最基本的路线问题。它寻求的是旅行者由起点出发,通过所有给定的需求点后,再次返回起点所花费的最小路径成本,也叫旅行商问题、旅行推销员问题、担货郎问题等。
  动态规划算法(Dynamic Programming,简称DP)通常用于求解具有某种最优性质的问题,其基本思想是将待求解问题分解成若干个子问题,先求解子问题,然后由这些子问题的解再得到原问题的解。

动态规划的求解过程

  下面来验证一下此方法求解的可行性。
  设 s,s1,s2…s为满足题意的最短回路。假设从s到s1的路径已经确定,则问题转化为从s1到s的最短路径问题。而很显然,s1,s2…s一定可以构成一条最短路径,所以构成最优子结构性质,可以用动态规划求解。

动态规划方程的推导

  用 V’ 表示一个点的集合,假设从顶点 s 出发, d ( i , V’ ) 表示当前到达顶点 i,经过 V’ 集合中所有顶点一次的最小花费。

  1. .当 V’ 为仅包含起点的集合,也就是
    d ( s , { s } ) = 0 d(s,\{ s\} ) = 0 d(s,{s})=0
  2. 其他情况,则对子问题求最优解。需在 V’ 这个城市集合中,尝试每一个城市结点,并求出最优解。
    在这里插入图片描述
  3. 最后的求解方式为:
    在这里插入图片描述

其中 S 为包含所有点的集合。把公式一套,题就解了。

状态压缩

  推到动态规划方程时,我们注意到 V’ 是一个数的集合,而且解决的问题规模比较小,于是可以用一个二进制数来存储这个集合。简单来说就是——如果城市 k 在集合 V’ 中,那么存储集合的变量 i 的第 k 位就为 1,否则为 0。由于有 n 个城市,所有的状态总数我们用 M 来表示,那么很明显:M = 2^n,而 0 到 2^n -1 的所有整数则构成了 V’ 的所有状态。这样,结合位运算,动归方程的状态表示就很容易了。

源码:

#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
// 定义常量
const int INF = 0x3f3f3f3f;
#define sqr(x) ((x)*(x))
// 定义变量
string file_name;
int type; // type == 1 满秩矩阵格式, type == 2 二维坐标式
int s;
int N;// 城市结点数量
int init_point;
double **dp; // 动态规划状态数组dp[i][j],i表示集合V’,j表示当前到达的城市结点
double **dis; // 两个城市结点之间的距离
double ans;
// 定义结构体
struct vertex {
	double x, y; // 城市结点的坐标
	int id; // 城市结点的id
	int input(FILE *fp) {
		return fscanf(fp, "%d %lf %lf", &id, &x, &y);
	}
}*node;

double EUC_2D(const vertex &a, const vertex &b) {
	return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}

void io() { // 数据读入
	printf("input file_name and data type\n");
	cin >> file_name >> type;
	FILE *fp = fopen(file_name.c_str(), "r");
	fscanf(fp, "%d", &N);
	node = new vertex[N + 5];
	dis = new double*[N + 5];
	if (type == 1) {
		for (int i = 0; i < N; i++) {
			dis[i] = new double[N];
			for (int j = 0; j < N; j++)
				fscanf(fp, "%lf", &dis[i][j]);
		}
	}
	else {
		for (int i = 0; i < N; i++)
			node[i].input(fp);
		for (int i = 0; i < N; i++) {
			dis[i] = new double[N];
			for (int j = 0; j < N; j++)
				dis[i][j] = EUC_2D(node[i], node[j]);// 计算城市之间的距离
		}
	}
	fclose(fp);
	return;
}

void init() { // 数据初始化
	dp = new double*[(1 << N) + 5];
	for (int i = 0; i < (1 << N); i++) {
		dp[i] = new double[N + 5];
		for (int j = 0; j < N; j++)
			dp[i][j] = INF;
	} // 初始化,除了dp[1][0],其余值都为INF
	ans = INF;
	return;
}

double slove() {
	int M = (1 << N);
	// M就是第四部分所说的V’状态总数,1<<N表示2^N,总共有2^N种状态
	dp[1][0] = 0;
	// 假设固定出发点为0,从0出发回到0的花费为0。TSP只要求是一个环路,所以出发点可以任选
	for (int i = 1; i < M; i++) {
		// 枚举V’的所有状态
		for (int j = 1; j < N; j++) {
			// 选择下一个加入集合的城市
			if (i & (1 << j)) continue;
			// 城市已经存在于V’之中
			if (!(i & 1)) continue;
			// 出发城市固定为0号城市
			for (int k = 0; k < N; k++) {
				// 在V’这个城市集合中尝试每一个结点,并求出最优解
				if (i & (1 << k)) {
					// 确保k已经在集合之中并且是上一步转移过来的结点
					dp[(1 << j) | i][j] = min(dp[(1 << j) | i][j], dp[i][k] + dis[k][j]); // 转移方程
				} // 将j点加入到i集合中
			}
		}
	}
	for (int i = 0; i < N; i++)
		ans = min(dp[M - 1][i] + dis[i][0], ans);
	// 因为固定了出发点,所以要加上到城市0的距离。另外要从所有的完成整个环路的集合V’中选择,完成最后的转移
	return ans;
}

int main() {
	io();
	init();
	string tmp = file_name + ".sol";
	FILE *fp = fopen(tmp.c_str(), "w");
	fprintf(fp, "%.2lf\n", slove());
	delete[] dp;
	delete[] node;
	delete[] dis;
	fclose(fp);
	return 0;
}

输入数据:

若城市数据文件如下所示:

     16
     1   38.24   20.42
     2   39.57   26.15
     3   40.56   25.32
     4   36.26   23.12
     5   33.48   10.54
     6   37.56   12.19
     7   38.42   13.11
     8   37.52   20.44
     9   41.23   9.10
   10   41.17   13.05
   11   36.08   -5.21
   12   38.47   15.13
   13   38.15   15.35
   14   37.51   15.17
   15   35.49   14.32
   16   39.36   19.56
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值