跳到内容

元数据卡

  • 前置知识:ch14-greedy(复杂度直觉)
  • 预计时间:25 分钟
  • 核心难度:进阶
  • 完成标志:能解释 P/NP/NP-complete/NP-hard 的区别,知道面对 NP-hard 怎么活

你的进度

贪心和 DP 你都会了。但你遇到了一些奇怪的问题:旅行商要走遍所有驿站,你试了所有招数,n=30 就卡死。你不是代码写得烂——你是遇到了计算复杂度的暗区。

你的任务 理解 P vs NP 的核心直觉,学会归约思维,认识几个经典 NP-complete 问题,知道遇到它们时怎么活。


P、NP、NP-complete、NP-hard

P: 能在多项式时间内求解。排序、查找、最短路、DP——多数属于 P。

NP: 给个"候选答案"能在多项式时间内验证。验证容易,求解不一定。比如数独——填满的棋盘几秒验证完,但从头解可能花几小时。

NP-complete: NP 里最难的问题。任何一个 NP 问题都可以归约到它。找到一个 NP-complete 的多项式解法,所有 NP 问题都解决了(P = NP)。

NP-hard: 至少和 NP-complete 一样难。可能不在 NP 里。

NP-hard
+-------------------------+
| NP-complete             |
|  +-------------------+  |
|  | NP               |  |
|  | +------+        |  |
|  | | P    |        |  |
|  | | 排序 |        |  |
|  | +------+        |  |
|  | 3-SAT 顶点覆盖  |  |
|  +-------------------+  |
| 停机问题(不可判定)   |
+-------------------------+

归约——把难题翻译成另一道

把问题 A 的实例在多项式时间内转换成问题 B 的实例,且 B 的解就是 A 的解。记作 A <=_p B。

顶点覆盖 -> 独立集:补集翻译。

python
def vertex_cover_to_independent_set(graph, k):
    return graph, len(graph) - k

经典 NPC 问题

旅行商问题(TSP): n 个城市,找最短回路。暴力 O(n!)。n=30 时 30! 约 2.65e32,算不完。

python
def tsp_nearest_neighbor(dist, start=0):
    n = len(dist)
    visited, route = {start}, [start]
    cur = start
    while len(visited) < n:
        nxt = min((c for c in range(n) if c not in visited),
                   key=lambda c: dist[cur][c])
        visited.add(nxt); route.append(nxt); cur = nxt
    route.append(start)
    total = sum(dist[route[i]][route[i+1]] for i in range(n))
    return route, total

dist = [[0,10,15,20],[10,0,35,25],[15,35,0,30],[20,25,30,0]]
route, total = tsp_nearest_neighbor(dist)
print(f"路线: {route}, 距离: {total}")
# 预期: 路线: [0,1,3,2,0], 距离: 80

顶点覆盖 2-近似:

python
def approx_vertex_cover(graph):
    edges = {(u,v) for u in graph for v in graph[u] if u < v}
    selected = set()
    while edges:
        u,v = edges.pop()
        selected.update([u,v])
        edges -= {(a,b) for (a,b) in edges if a in (u,v) or b in (u,v)}
    return selected

g = {0:[1,2],1:[0,2,3],2:[0,1,3],3:[1,2]}
print(approx_vertex_cover(g))
# 保证覆盖大小 <= 2 * 最优

应对 NP-hard 的策略

策略适用场景代价
精确搜索 + 剪枝n <= 30指数时间
近似算法能接受有界误差损失最优性
启发式(贪心/模拟退火)不需要保证无界误差但可用
参数化算法(FPT)某参数小指数只依赖参数
直接用 solver不想自己写依赖第三方

心态: 知道问题是 NP-hard 不等于放弃。大部分现实问题有特殊结构——图不大、数据有规律、能接受近似。


常见陷阱

卡点真相
"NP-complete 就是所有难题"不对,停机问题更难(不可判定)
"归约必须写完整代码"不需要,核心是转换逻辑
"找不到多项式解 -> NP-hard"不一定,可能是你没找到正确结构

旅人笔记

NP-complete 的价值不是"这问题难",而是"别怪自己没写对"。知道这个事实能省掉你几周瞎折腾。近似算法在工业界比精确算法值钱 10 倍。Google Maps 不保证绝对最优——但它 200ms 内给你足够好的路线。


下一步:摊还分析

NP 讲的是"有的问题本来就难"。下一站讲另一种智慧——有些操作偶尔很贵,但长期平均很便宜。

看摊还分析 ->

Built with VitePress | Software Systems Atlas