问题描述
旅行商问题(Traveling Salesman Problem, TSP):给定 n 个城市以及城市之间的距离,求从某个城市出发,经过每个城市恰好一次,最终回到起点的最短路径长度。
TSP 是组合优化中最著名的 NP 难问题之一,在物流路径规划、电路板钻孔、基因测序等领域有广泛实际应用。
解法一:状态压缩动态规划(精确解)
设 dp[mask][i] 表示已经访问过的城市集合为 mask,且当前位于城市 i 时的最短路径长度。
public class TSPDynamicProgramming {
// 状态压缩DP,适用于 n <= 20 的情况
public static int tsp(int[][] dist) {
int n = dist.length;
int fullMask = (1 << n) - 1;
// dp[mask][i]: 已访问mask中的城市,当前在城市i的最小距离
int[][] dp = new int[1 << n][n];
for (int[] row : dp) java.util.Arrays.fill(row, Integer.MAX_VALUE / 2);
// 从城市0出发
dp[1][0] = 0;
for (int mask = 1; mask <= fullMask; mask++) {
for (int last = 0; last < n; last++) {
if ((mask & (1 << last)) == 0) continue; // last不在mask中
if (dp[mask][last] >= Integer.MAX_VALUE / 2) continue;
for (int next = 0; next < n; next++) {
if ((mask & (1 << next)) != 0) continue; // next已访问
int newMask = mask | (1 << next);
dp[newMask][next] = Math.min(dp[newMask][next],
dp[mask][last] + dist[last][next]);
}
}
}
// 回到起点城市0
int minCost = Integer.MAX_VALUE;
for (int last = 1; last < n; last++) {
minCost = Math.min(minCost, dp[fullMask][last] + dist[last][0]);
}
return minCost;
}
// 路径还原版本
public static int tspWithPath(int[][] dist, int[] bestPath) {
int n = dist.length;
int fullMask = (1 << n) - 1;
int[][] dp = new int[1 << n][n];
int[][] parent = new int[1 << n][n];
for (int[] row : dp) java.util.Arrays.fill(row, Integer.MAX_VALUE / 2);
dp[1][0] = 0;
for (int mask = 1; mask <= fullMask; mask++) {
for (int last = 0; last < n; last++) {
if ((mask & (1 << last)) == 0) continue;
if (dp[mask][last] >= Integer.MAX_VALUE / 2) continue;
for (int next = 0; next < n; next++) {
if ((mask & (1 << next)) != 0) continue;
int newMask = mask | (1 << next);
int newCost = dp[mask][last] + dist[last][next];
if (newCost < dp[newMask][next]) {
dp[newMask][next] = newCost;
parent[newMask][next] = last;
}
}
}
}
int minCost = Integer.MAX_VALUE;
int lastCity = -1;
for (int last = 1; last < n; last++) {
int cost = dp[fullMask][last] + dist[last][0];
if (cost < minCost) {
minCost = cost;
lastCity = last;
}
}
// 回溯路径
int mask = fullMask;
for (int i = n - 1; i >= 0; i--) {
bestPath[i] = lastCity;
int prev = parent[mask][lastCity];
mask ^= (1 << lastCity);
lastCity = prev;
}
return minCost;
}
public static void main(String[] args) {
int[][] dist = {
{0, 10, 15, 20},
{10, 0, 35, 25},
{15, 35, 0, 30},
{20, 25, 30, 0}
};
System.out.println("最短TSP路径长度: " + tsp(dist)); // 80
int[] path = new int[dist.length];
System.out.println("带路径的最短长度: " + tspWithPath(dist, path));
System.out.println("路径: " + java.util.Arrays.toString(path));
}
}
时间复杂度 O(n² × 2ⁿ),空间复杂度 O(n × 2ⁿ)。当 n = 20 时,2²⁰ ≈ 100万,尚可接受。
解法二:最近邻贪心近似
public class TSPNearestNeighbor {
// O(n²) 贪心近似,不保证最优,但速度快
public static int tspNearestNeighbor(int[][] dist) {
int n = dist.length;
boolean[] visited = new boolean[n];
int current = 0;
visited[0] = true;
int totalCost = 0;
int count = 1;
while (count < n) {
int nearest = -1;
int minDist = Integer.MAX_VALUE;
for (int i = 0; i < n; i++) {
if (!visited[i] && dist[current][i] < minDist) {
minDist = dist[current][i];
nearest = i;
}
}
totalCost += minDist;
visited[nearest] = true;
current = nearest;
count++;
}
// 返回起点
totalCost += dist[current][0];
return totalCost;
}
}
解法三:最小生成树 2-近似
利用 MST 的性质:最优 TSP 回路去掉一条边后得到生成树,因此 MST 权重是 TSP 最优解的下界。对 MST 做前序遍历可得到 2-近似解。
import java.util.*;
public class TSPMSTApproximation {
// Prim算法求MST
public static int mstPrim(int[][] dist) {
int n = dist.length;
boolean[] inMST = new boolean[n];
int[] minEdge = new int[n];
Arrays.fill(minEdge, Integer.MAX_VALUE);
minEdge[0] = 0;
int totalWeight = 0;
for (int i = 0; i < n; i++) {
int u = -1;
for (int j = 0; j < n; j++) {
if (!inMST[j] && (u == -1 || minEdge[j] < minEdge[u])) {
u = j;
}
}
inMST[u] = true;
totalWeight += minEdge[u];
for (int v = 0; v < n; v++) {
if (!inMST[v] && dist[u][v] < minEdge[v]) {
minEdge[v] = dist[u][v];
}
}
}
return totalWeight;
}
// 2-近似:TSP最优解 <= 2 * MST权重
public static int tspApproximation(int[][] dist) {
return 2 * mstPrim(dist);
}
}
算法边界分析
| 方法 | 时间复杂度 | 近似比 | 适用规模 |
|---|---|---|---|
| 状态压缩DP | O(n² × 2ⁿ) | 精确解 | n ≤ 20 |
| 最近邻贪心 | O(n²) | 无保证 | n 很大,快速近似 |
| MST 2-近似 | O(n²) | ≤ 2 | n 很大,需要理论保证 |
实际应用
在真实物流场景中,城市数往往成千上万,精确 DP 不可行。此时通常采用:
– 遗传算法、模拟退火等元启发式算法
– 约束求解器(如 OR-Tools)
– 结合地理信息的启发式构造
总结
TSP 展示了计算复杂性理论对算法设计的深刻影响:当问题规模增大时,精确算法让位于近似算法与启发式方法。理解不同方法的适用边界,是算法工程师的核心能力之一。