推销员问题(简化版)
售货员的难题
货郎担问题(TSP)
题目大意
货郎担问题要求找到一条最短路径,让旅行者遍历所有城市后回到起点。针对不同的问题规模,分别采用不同的算法。
解法描述
朴素分支限界法(BFS)
算法思想
借助队列实现广度优先搜索(BFS),每个节点存储当前路径和已访问的城市集合信息。在搜索过程中,维护一个bestl变量记录当前找到的最短路径长度,利用限界函数剪枝,即若当前节点路径长度加上到达下一个城市的成本大于bestl,则停止探索该节点,以此提高搜索效率。
实现方式
定义结构体node,包含当前路径长度cl、当前处理的城市编号id以及记录已访问城市的bitset。从起点城市
1
开始,创建初始节点并入队。在队列非空时,取出队首节点。若当前路径长度大于等于bestl,则进行剪枝;若已访问城市数量达到总城市数量(本题不要求形成环路),则更新bestl;否则,遍历所有城市,若城市未被访问、与当前城市可达且新路径长度小于bestl,则创建新节点并入队。
复杂度分析
时间复杂度 :理论时间复杂度为\(O(n!)\) ,源于 BFS
遍历排列树的特性,但实际执行中,由于限界条件的作用,时间复杂度会有所降低
空间复杂度 :空间复杂度为\(O(n!)\)
,因为在最坏情况下,队列需要存储整个解空间的排列树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <bits/stdc++.h> using namespace std;const int INF = 0x3f3f3f3f ;int m[12 ][12 ];int bestl, n;struct node { int cl; int id; bitset<11> visited; node () {} node (int cl_, int id_) : cl (cl_), id (id_) {} }; void bfs () { queue<node> q; node temp (0 , 1 ) ; temp.visited.set (1 ); q.push (temp); while (!q.empty ()) { auto cur = q.front (); q.pop (); int t = cur.id; if (cur.cl >= bestl) continue ; if ((int )cur.visited.count () == n) { bestl = min (bestl, cur.cl); continue ; } for (int j = 1 ; j <= n; ++j) { if (!cur.visited.test (j) && m[t][j] != INF && cur.cl + m[t][j] < bestl) { temp = node (cur.cl + m[t][j], j); temp.visited = cur.visited; temp.visited.set (j); q.push (temp); } } } } int main () { cin >> n; bestl = INF; for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= n; j++) { int temp; cin >> temp; if (temp == -1 )m[i][j] = INF; else m[i][j] = temp; } } bfs (); cout << bestl; return 0 ; }
Bellman-Held-Karp(动态规划)
算法思想
利用状态压缩动态规划策略,将问题状态表示为当前访问的城市集合。其核心在于通过逐步添加新城市到当前集合,依据状态转移方程更新到达新城市的最小成本,最终从所有可能的回路中找出成本最小的路径。
实现方式
使用二维数组dp[idx][t]记录状态,idx是用二进制表示的城市集合,t表示当前到达的城市,dp的值代表在该状态下抵达城市t的消耗。先将dp数组初始化为极大值,设置dp[1][0] = 0,表示从起点
0 出发到达自身成本为
0。通过三层循环进行状态转移,外层循环遍历所有可能的城市集合(从 1 到
\((1<<n)-1\)
),中层循环遍历集合内的城市u,内层循环遍历不在集合中的城市v,执行dp[i|(1<<v)][v]=min(dp[i|(1<<v)][v], dp[i][u]+g[u][v])更新状态。最后,通过ans = min(ans, dp[(1<<n)-1][i]+g[i][0])
寻找最优边以构成最终回路,得出最小成本路径。
复杂度分析
时间复杂度 :时间复杂度为 \(O(n^2\times2^n)\)
,这是因为存在三层循环,其中两层与城市集合相关(复杂度为\(O(2^n)\)
),一层与城市数量相关(复杂度为\(O(n)\) )
空间复杂度 :空间复杂度是\(O(n\times2^n)\)
,主要用于存储动态规划表dp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <bits/stdc++.h> using namespace std;const int N = 20 ;int g[N][N], n, dp[1 << N][N], ans = 0x3f3f3f3f ;void Bellman_Held_Karp () { for (int i = 1 ; i < (1 << n); i++) { for (int u = 0 ; u < n; u++) { if (i & (1 << u)) { for (int v = 0 ; v < n; v++) { if (!(i & (1 << v))) { dp[i | (1 << v)][v] = min (dp[i | (1 << v)][v], dp[i][u] + g[u][v]); } } } } } int t = (1 << n) - 1 ; for (int i = 0 ; i < n; i++) { ans = min (ans, dp[t][i] + g[i][0 ]); } } int main () { cin >> n; memset (dp, 0x3f , sizeof dp); dp[1 ][0 ] = 0 ; for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < n; j++) { cin >> g[i][j]; } } Bellman_Held_Karp (); cout << ans; return 0 ; }