云计算百科
云计算领域专业知识百科平台

动态规划进阶:状态压缩 DP(State Compression DP)与旅行商问题(TSP)位运算优化

动态规划进阶:状态压缩 DP(State Compression DP)与旅行商问题(TSP)位运算优化

封面信息图

在动态规划(Dynamic Programming)的高阶题型与运筹优化算法中,有一类极其特殊的 NP-Hard 组合优化问题:

  • 旅行商问题(Traveling Salesperson Problem,TSP):遍历 $N$ 个城市每个城市恰好一次并回到起点的最短总距离;
  • LeetCode 847:访问所有节点的最短路径;
  • 棋盘摆放问题(如经典的蒙德里安的梦想 / 互不攻击的国王与炮车)。

在这些问题中,数据规模通常极小(如 $N \\le 16 \\sim 20$),但每个元素拥有“被选中/未被选中”、“被访问/未被访问”等离散二元状态。如果用普通的数组来记录“当前哪些城市已经被访问过了”,状态维度将达到多维数组无法承受的复杂度。

状态压缩动态规划(State Compression DP,简称状压 DP) 凭借计算机底层的二进制位运算(Bitmasking):将一个大小为 $N$ 的布尔状态集合,精确压缩进一个 32 位整型整数 int state 的各个二进制位中!通过极致的位移与位运算,将原本指数级暴搜的时间复杂度大幅优化,在毫秒级时间内征服原本不可解的组合难题。

今天我们把状压 DP 的二进制位运算心法、状态转移方程推导与 TSP 经典模板彻底讲透。


一、状态压缩核心位运算操作字典

设状态集合用整型变量 mask 表示(第 $i$ 位为 1 代表第 $i$ 个城市已被访问,为 0 代表未访问):

graph TD
subgraph 二进制位运算核心操作 (0-indexed)
Op1["1. 检查第 i 个元素是否已被访问: (mask >> i) & 1 == 1"]
Op2["2. 将第 i 个元素标记为已访问: mask | (1 << i)"]
Op3["3. 将第 i 个元素从集合中移除: mask & ~(1 << i)"]
Op4["4. 将第 i 个元素的状态取反: mask ^ (1 << i)"]
Op5["5. 遍历全集状态空间: for mask = 0 to (1 << N) – 1"]
end

业务逻辑二进制位运算表达式物理说明
判断第 $i$ 位是否为 1 (mask & (1 << i)) != 0 或 ((mask >> i) & 1) == 1 检查第 $i$ 个元素是否存在于当前集合中
将第 $i$ 位设为 1 `mask (1 << i)`
将第 $i$ 位清零(设为 0) mask & ~(1 << i) 从集合中删除第 $i$ 个元素
将第 $i$ 位翻转 mask ^ (1 << i) 状态取反
获取集合中元素的总个数 Integer.bitCount(mask) 统计二进制中 1 的个数(即已访问城市数)
提取最低位的 1(Lowbit) mask & (-mask) 快速枚举集合中的元素

二、旅行商问题(TSP)状压 DP 状态定义与转移方程

给定 $N$ 个城市以及任意两个城市 $i$ 与 $j$ 之间的距离矩阵 dist[i][j]。求解从起点 0 出发,访问完所有城市恰好一次的最短路径总长度。

1. 状态定义:

$$\\mathbf{dp[\\text{mask}][u]}$$

  • 物理含义:当前已经访问过的城市集合为 mask(二进制编码),且当前停留在城市 $u$ 时的【最小累计路径开销】!
2. 状态转移方程(自底向上向外递推):

当前状态为 $(\\text{mask}, u)$,我们尝试从城市 $u$ 走向下一个尚未访问过的城市 $v$(即满足 (mask & (1 << v)) == 0):

$$\\mathbf{dp[\\text{mask} \\mid (1 \\ll v)][v] = \\min \\left( dp[\\text{mask} \\mid (1 \\ll v)][v], \\ dp[\\text{mask}][u] + \\text{dist}[u][v] \\right)}$$

graph LR
S1["dp[mask][u]: 已经访问集合 mask, 停留在城市 u"] –>|走向未访问城市 v| S2["dp[mask | (1 << v)][v]: 访问集合扩充, 停留在城市 v"]
Note["转移开销: + dist[u][v]"]

3. 初始状态与最终答案:
  • 初始化:所有 $dp$ 数组设为正无穷(INF);
  • 起点:$dp[1 \\ll 0][0] = dp[1][0] = 0$(集合中仅包含起点 0,停留在城市 0,耗时为 0);
  • 最终目标:$dp[(1 \\ll N) – 1][\\text{last_city}]$(所有二进制位全部为 1,代表全集覆盖)。

工业级 TSP 状压 DP Java 实现模板

import java.util.Arrays;

public class TspStateCompressionDp {

private static final int INF = 0x3f3f3f3f;

public int solveTsp(int n, int[][] dist) {
int totalStates = 1 << n; // 2^N 种状态集合
int[][] dp = new int[totalStates][n];

// 1. 初始化 dp 数组为无穷大
for (int i = 0; i < totalStates; i++) {
Arrays.fill(dp[i], INF);
}

// 2. 起点初始状态:从城市 0 出发
dp[1][0] = 0; // 1 的二进制为 00…001 (仅访问了城市 0)

// 3. 状态从小到大递推遍历 (保证子状态一定先于父状态计算完成!)
for (int mask = 1; mask < totalStates; mask++) {
for (int u = 0; u < n; u++) {
// 若当前集合 mask 中根本不包含城市 u,则该状态不可能存在,直接跳过
if ((mask & (1 << u)) == 0 || dp[mask][u] == INF) {
continue;
}

// 尝试从当前城市 u 走向下一个未访问城市 v
for (int v = 0; v < n; v++) {
// 若城市 v 尚未被访问
if ((mask & (1 << v)) == 0) {
int nextMask = mask | (1 << v);
dp[nextMask][v] = Math.min(dp[nextMask][v], dp[mask][u] + dist[u][v]);
}
}
}
}

// 4. 统计访问完全部城市(mask = (1 << n) – 1)并返回起点 0 的最短回路
int allVisitedMask = (1 << n) – 1;
int minTotalCost = INF;
for (int u = 0; u < n; u++) {
if (dp[allVisitedMask][u] != INF) {
// 加上从最后停留的城市 u 回到起点 0 的距离
minTotalCost = Math.min(minTotalCost, dp[allVisitedMask][u] + dist[u][0]);
}
}

return minTotalCost;
}
}


复杂度对比与时空分析

算法方案时间复杂度空间复杂度$N = 16$ 时的计算量级
朴素全排列暴力搜索(DFS / Brute-Force) $\\mathcal{O}(N!)$ $\\mathcal{O}(N)$ $16! \\approx 2.09 \\times 10^{13}$(直接超时暴毙!)
状压动态规划(Held-Karp 算法) $\\mathcal{O}(2^N \\cdot N^2)$ $\\mathcal{O}(2^N \\cdot N)$ $2^{16} \\times 16^2 \\approx 1.67 \\times 10^7$(毫秒级秒杀!)

实习生的算法进阶总结

状压 DP 是位运算(Bitwise Manipulation)与动态规划(DP)最精妙的结合体:它利用二进制底层的位表示,将原本离散高维的集合状态优雅压缩进单个整型寄存器中,在保证状态无后效性的同时,将阶乘级的暴搜开销压缩为指数级多项式。牢记 “判断包含用 &、加入集合用 |、遍历状态从小到大保证拓扑有序” 三大心法,面对各类网格覆盖与旅行商难题,你都能写出闪电般迅捷的状压解法。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 动态规划进阶:状态压缩 DP(State Compression DP)与旅行商问题(TSP)位运算优化
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!