Solve TSP with dynamic programming——动态规划解决旅行商(邮递员)问题

本文介绍了一种解决旅行商问题(TSP)的动态规划算法,特别适用于无向简单图。算法通过计算最小成本路径,找到从任意顶点出发并返回原点的最短回路,适用于小规模问题。核心思想是利用动态规划递归关系,求解子问题以获得全局最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

无向简单图的TSP算法

小规模精确解:

算法思想:

小规模精确解的算法中心思想是动态规划思想。

假设给定顶点集合V为{0,1,2,3,4,... .n}。由于图为无向完全图,我们可以很自然地将0视为输出的起点和终点。对于每个其他顶点i(除0之外),我们找到以0为起点,i为终点,且所有顶点恰好出现一次的最小成本路径。假设定义这条最小成本路径的成本为Cost(i),则相应TSP回路的Cost将是Cost(i)+ dist(i,0)其中dist(i,0)是从i到0的距离。最后,我们返回所有[cost(i)+ dist(i,0)]值,再从中选择最小值,此时该最小值也就是TSP的最优路径。而如何获得Cost(i)呢?那就是用动态规划算法来完成了:

定义CSi):

从集合S中访问每个顶点一次,且从0开始到i结束的最小成本路径的Cost

定义动态规划递归关系:

If size of S is 2, then S must be {0, i},
	C(S, i) = dist(1, i) 
Else if size of S is greater than 2.
	C(S, i) = min { C(S-{i}, j) + dis(j, i)} where j belongs to S, j != i and j != 0.

       所以,根据以上定义,求解TSP最优路径,等价于求解:

              min {C(S, i) + dist(i, 0)} for any i in |V – 0|

 

伪代码构造:

TSP(V, G)
// input: V is the vertex set, exactly i indicates ith vertex
// input: G is a matrix, which G[i][j] indicates the distance from ith node to jth one 
// output: the best optimal path of TSP
// output: the total cost of the path
	// define C a matrix
	// C[i][S]: C(S, i) be the cost of the minimum cost path visiting each vertex 
	// 			in set S exactly once, starting at 1 and ending at i
	C(|V|, (2^|V|, -1))
	// define path a matrix which the value records the last node of the current node
	paths(|V|, 2^|V|)
	min = INT_MAX
	for e in V-{0} do
		cost = dp_TSP(G, V, C, S, e, paths) + G[e][0]
		if cost < min do
			min = cost
			index = e
		endif
	endfor
	
	path = []
	S = [all node in V]
	do
		push index in path
		temp = index
		index = path[index][S]
		pop temp from S
	while index != -1
	push 0 in path
	
	return path.reverse, min
	
dp_TSP(G, V, C, S, i, paths)
// input: G is a matrix, which G[i][j] indicates the distance from ith node to jth one
// input: V is verties set
// input: C means C(S, i)
// input: S includes all nodes needed to visited once and only once 
// input: i, which indicate the end vertex in S
// input: records the last node of the current node in C(S, i)
// output: the value of C(S, i)
	if |S| == 2
		return G[0][i]
	endif
	if C(S, i) != -1
		return C(S, i)
	endif
	
	min = INT_MAX
	for j in S
		if j != {0} and j != e then
			value = C(S-{i}, j) + G[j][i]
			if value <= min then
				min = value
				index = j
			endif
		endif
	endfor
	
	C(S, i) = min
	path(S, i) = index
	return min

 

函数设计:

邻接矩阵读取函数设计:

      

TSP函数设计:

dp_TSP函数设计:

测试结果:

 

算法分析:

由于我们使用动态编程解决了这个问题,且动态编程方法包含子问题,子问题如下图显示:

如果我们求解递归方程,我们将得到总 个子问题,也就是。每个子问题将花费O(n)时间(找到剩余(n-1)个节点的路径)。因此总时间复杂度为。空间复杂度也是子问题的数量,即

虽然时间复杂度远小于On!),但仍然呈指数级。所需空间也是指数级的。因此,即使对于稍高数量的顶点,这种方法也是不可行的。由于在算法实现当中,用了long long int来作为S集合,所以超过64个节点的图就无法适用了。只能通过大规模近似求解。

 

完整代码:

#include <vector>
#include <iostream>

using namespace std;
typedef vector<vector<int> > G;
typedef vector<int> V;
G load_graph(int n);
void TSP(G& g, int n);
int dp_TSP(G& g, G& C, long long int S, int i, G& path);
V get_set(long long int S, int n);
int main(){
	int n;
	cin>> n;
	G g = load_graph(n);
	TSP(g, n);
	return 0;
}

G load_graph(int n){
	G g(n, V(n));
	for(int a=0; a<n; a++)
		for(int b=0; b<n; b++)
			scanf("%d", &g[a][b]);
			
	return g;
}
void printG(G& g){
	int n = g.size();
	int m = g[0].size();
	for(int a=0; a<n; a++){
		for(int b=0; b<m; b++)
			cout<< g[a][b]<< ' ';
		cout<< endl;
	}
}
void printV(V g){
	int n = g.size();
	for(int a=0; a<n; a++)
		cout<< g[a]<< ' ';
	cout<< endl;
}

void TSP(G& g, int n){
	// there are (2^n - 1) probable S for each i in C(S, i)
	int m = (1<<n);
	// define a term C(S, i) be the cost of the minimum cost path visiting each vertex 
	// in set S exactly once, starting at 1 and ending at i.
	G C(n, V(m, -1));
	// record the path
	G path(n, V(m, -1));
	// run the dynamic programming TSP algorithm
	// start at 0th node, and end at 0th node
	// find the minimum cost path with 1 as the starting point, i as the ending point
	int min = INT_MAX;
	int target = -1;
	for(int i=1; i<n; i++){
		// define S as a long long int variable, each ith bit record the existance of the ith node
		// initial S = 1<<n - 1, which means that S includes all node at first
		long long int S = (1<<n) - 1;
		// the cost of corresponding Cycle would be cost(i) + dist(i, 0)
		int cost = dp_TSP(g, C, S, i, path) + g[i][0];
		// the minimum of all [cost(i) + dist(i, 0)] values
		if(cost < min){
			min = cost;
			target = i;
		}
	}
	V output;
	long long int S = (1<<n) - 1;
	do{
		output.push_back(target);
		int temp = target;
		target = path[target][S];
		S = S^(1<<temp);
	}while(target != -1);
	//printG(C);
	cout<< min<< endl;
	for(int a=n-1; a>=0; a--)
		cout<< output[a]<< ' ';
	cout<< 0<< endl;
}

int dp_TSP(G& g, G& C, long long int S, int i, G& path){
	V set = get_set(S, g.size());
	/*
	If size of S is 2, then S must be {0, i},
		C(S, i) = dist(1, i) 
	Else if size of S is greater than 2.
		C(S, i) = min { C(S-{i}, j) + dis(j, i)} where j belongs to S, j != i and j != 0.
	*/
	if(set.size()==2){
		path[i][S] = 0;
		return g[0][i];
	}
	if(C[i][S] != -1)
		return C[i][S];
	int min = INT_MAX, index = -1;
	long long int new_S = S^(1<<i);
	for(int a=0; a<set.size(); a++){
		int j = set[a];
		if(j!=i && j!=0){
			int result = dp_TSP(g, C, new_S, j, path) + g[j][i];
			//cout<< i<< ' '<< result<< ' '<< j<< endl;
			if(result < min){
				min = result;
				index = j;
			}
		}
	}
	C[i][S] = min;
	path[i][S] = index;
	//cout<< i<< ' '<< index<< "dad"<< endl;
	return min;
}

V get_set(long long int S, int n){
	V set;
	for(int a=0; a<n; a++)
		if(S & (1<<a))
			set.push_back(a);
			
	return set;
}

 

动态规划是一种通过将复杂问题分解为更小的子问题,并存储每个子问题的结果以避免冗余计算的技术。旅行问题TSP,Traveling Salesman Problem)是一个经典的组合优化问题,目标是在给定一组城市及它们之间的距离的情况下,找到访问所有城市的最短路径并回到起点。 ### 动态规划解决TSP的核心思想 1. **状态表示** 定义 `dp[S][i]` 表示当前已经经过的城市集合为 \( S \),并且最后到达的是第 \( i \) 个城市时的最小代价。其中: - \( S \) 是一个二进制数,用于标记哪些城市已经被访问过。 - \( i \) 是最后一个访问的城市编号。 2. **递推公式** 状态转移方程可以写成: $$ dp[S][i] = \min_{j \in S, j \neq i} (dp[S-\{i\}][j] + dist[j][i]) $$ 其中 \( S-\{i\} \) 表示从集合 \( S \) 中移除城市 \( i \),\( dist[j][i] \) 表示从城市 \( j \) 到城市 \( i \) 的直接距离。 3. **初始条件** 设定初始状态为只包含起点的情况,即 \( dp[\{0\}][0] = 0 \),其他情况初始化为无穷大。 4. **最终解** 最终结果可以从所有的 \( dp[(S_{全集})][i] + dist[i][0] \) 计算得到,选择其中的最小值作为最优解。这里加上 \( dist[i][0] \) 是为了返回到起始点形成回路。 --- ### 时间复杂度分析 由于有 \( O(2^n) \) 种城市集合以及每种状态下最多需要检查 \( n \) 个城市的状态,因此总的时间复杂度为 \( O(n^2 \cdot 2^n) \)。 尽管如此,相比暴力搜索方法 (\( O(n!) \)),这种基于动态规划的方式极大地提高了效率,尤其适合处理较小规模的问题实例(如 \( n < 20 \))。 --- #### 示例伪代码 ```python def tsp_dp(dist): n = len(dist) all_set = (1 << n) - 1 # 所有城市的掩码 INF = float('inf') # 初始化 DP 数组 dp = [[INF for _ in range(n)] for __ in range(1 << n)] dp[1][0] = 0 # 起点设为第一个城市 # 填充 DP 表格 for s in range(1 << n): # 遍历所有可能的城市子集 for i in range(n): # 当前考虑的城市是否在子集中 if not ((s >> i) & 1): continue # 如果不在子集中,则跳过 prev_s = s ^ (1 << i) # 排除掉 city i 后剩余的城市子集 if prev_s == 0: continue # 子集为空则无法进一步扩展 for j in range(n): # 尝试添加另一个未选城市 j -> i if (prev_s >> j) & 1: dp[s][i] = min(dp[s][i], dp[prev_s][j] + dist[j][i]) result = INF for i in range(n): # 回到原点,枚举结束点的所有可能性 if dp[all_set][i] != INF: result = min(result, dp[all_set][i] + dist[i][0]) return result ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值