惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
W
WeLiveSecurity
T
Troy Hunt's Blog
Project Zero
Project Zero
Security Archives - TechRepublic
Security Archives - TechRepublic
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
T
Threat Research - Cisco Blogs
T
Tenable Blog
Jina AI
Jina AI
Recorded Future
Recorded Future
T
The Exploit Database - CXSecurity.com
N
News | PayPal Newsroom
P
Palo Alto Networks Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
G
GRAHAM CLULEY
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Register - Security
The Register - Security
Last Week in AI
Last Week in AI
P
Privacy & Cybersecurity Law Blog
Microsoft Azure Blog
Microsoft Azure Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
SecWiki News
SecWiki News
S
Schneier on Security
有赞技术团队
有赞技术团队
IT之家
IT之家
美团技术团队
Cisco Talos Blog
Cisco Talos Blog
NISL@THU
NISL@THU
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Hacker News: Ask HN
Hacker News: Ask HN
罗磊的独立博客
博客园_首页
Cyberwarzone
Cyberwarzone
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
爱范儿
爱范儿
云风的 BLOG
云风的 BLOG
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Webroot Blog
Webroot Blog
Latest news
Latest news
D
DataBreaches.Net
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
宝玉的分享
宝玉的分享
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
计算与判定:P、NP、NP-hard 和 NP-complete 问题
Ofnoname · 2026-04-27 · via 博客园_首页

学习算法课学习到后期,都会进入一个看起来很理论话题:P、NP、NP-hard、NP-complete。这些概念想回答的核心问题是给各种问题和算法分类:

某个问题到底是“可以高效解决”,还是“很可能没有高效精确算法”?

这个判断非常重要。因为如果一个问题本质上是 NP-complete,那么你继续死磕“完美、快速、精确”的算法,可能方向就错了。更现实的做法往往是近似算法、启发式算法、剪枝搜索。

判定问题和优化问题

算法问题大致可以分成很多类型,其中复杂度理论特别喜欢研究一种问题:判定问题

判定问题

判定问题的答案只有两个:yes 或 no

例如:给定一个整数序列 \(S\),问:是否存在两个元素相等? 这就是一个判定问题。它只关心有没有重复元素,不要求你找出出现最多的元素,也不要求你统计所有频率。

再比如,给定一个图 \(G=(V,E)\) 和一个整数 \(k\),问:图中是否存在一个大小至少为 \(k\) 的 clique? 这也是判定问题。

(在无向图中,一个 clique,中文常译为“团”,指的是一组顶点,其中任意两个顶点之间都有边相连)

优化问题

优化问题关心的是某个最优值,比如最大值或最小值。

例如:给定一个整数序列 \(S\),问哪个元素出现频率最高? 这是优化问题。

再比如,给定一个图 \(G\),问:图中最大 clique 的大小是多少? 这也是优化问题,叫做 MAX-CLIQUE

再比如,给定一张带权图,问:从点 \(s\) 到点 \(t\) 的最短路径长度是多少? 这是最短路径的优化版。

优化问题如何变成判定问题

优化问题可以通过加入一个界限值 \(k\),变成判定问题。

例如:

  • 图中最大 clique 的大小是多少?
    变成:图中是否存在大小至少为 \(k\) 的 clique?

  • \(s\)\(t\) 的最短路径长度是多少?
    变成:是否存在一条从 \(s\)\(t\) 的路径,长度不超过 \(k\)

  • 经过所有城市并回到起点的最短路线是多少?
    变成:是否存在一条经过所有城市并回到起点、总长度不超过 \(k\) 的路线?

这件事很重要,因为复杂度理论经常优先研究判定问题。如果优化问题容易,那么对应的判定问题也容易。比如你已经能快速求出最短路径长度 \(d\),那么要回答“是否存在长度不超过 \(k\) 的路径”,只需要判断:\( d \le k \)

因此,对很多自然优化问题来说,如果我们能快速求出最优值,就能快速回答对应的判定问题。反过来,如果某个判定版已经被证明很难,那么对应的优化版通常也不会更容易,很多时候可以进一步证明为 NP-hard。

P:可以高效解决的问题

P 是一类判定问题。它的定义是:可以用确定性算法在多项式时间内解决的判定问题,属于 P

确定性算法

确定性算法就是每一步都只有一个确定选择的算法。同样的输入,每次运行,执行路径一样,输出也一样。

大部分我们平时写的算法都是确定性算法,比如:二分查找、快速排序、BFS、DFS、Dijkstra、动态规划

当然,快速排序如果随机选 pivot,那它可以是随机算法。但如果 pivot 选择规则固定,它就是确定性的。

多项式时间

如果输入规模是 \(n\),算法运行时间可以表示为:

\[O(n^k) \]

其中 \(k\) 是某个常数,那么这个算法就是多项式时间算法。比如 \(O(n)\)\(O(n \log n)\)\(O(n^2)\)\(O(n^3)\)

在复杂度理论里,通常把多项式时间视为“高效可解”的理论边界。为什么呢?

主要原因:\(O(n)\)\(O(n^2)\)\(O(n^3)\) 虽然内部差别很大,但总体上比指数增长温和得多。相比之下,\(O(2^n)\)\(O(3^n)\) 或者 \(O(n!)\) 增长极快,当 \(n=100\) 时,\(2^{100}\) 已经是天文数字。

其次,如果 A 可以多项式时间规约到 B,而 B 可以多项式时间解决,那么 A 也可以多项式时间解决。

因为:

\[poly(n) + poly(n) = poly(n) \]

并且:

\[poly(poly(n)) = poly(n) \]

多项式的复合仍是多项式。这使得整个 NP-complete 理论非常稳定。

当然,多项式时间不等于现实中一定快。例如 \(O(n^{100})\) 虽然是多项式,但几乎没有实际意义,实际应用可能不如指数算法。但在复杂度理论里,还是使用多项式时间来刻画问题的结构性难度。

典型的 P 问题例子

2-COLORING

给定一个无向图,问:能不能用两种颜色给图中的顶点染色,使得任意相邻顶点颜色不同?

这个问题等价于判断图是否是二分图,可以用 BFS 或 DFS 在线性时间内解决。

SHORTEST PATH 的判定版

给定一张带权图、两个点 \(s,t\) 和一个数 \(k\),问:是否存在一条从 \(s\)\(t\) 的路径,长度不超过 \(k\)

如果边权非负,可以先用 Dijkstra 求出最短路径 \(d\),然后判断 \(d \le k\)

当然,我们已经知道,即使是 SHORTEST PATH 的优化版,也可以在多项式时间轻松解决。

2-SAT

SAT,全称 satisfiability,中文常译为“可满足性问题”。它问的是:

给定一个布尔公式,是否存在一种变量赋值,使整个公式为真?

例如:

\[(x \lor y) \land (\neg x \lor z) \]

这个公式里有变量 \(x,y,z\)。如果存在一组 true/false 赋值让整个公式为 true,那么它就是可满足的。

2-SAT 是 SAT 的一个限制版本:每个子句里至多有 2 个 literal(比如上面的例子)。可以证明 2-SAT 可以在多项式时间内解决,所以它属于 P。

NP:可以高效验证的问题

NP 也是一类判定问题。它的核心思想不是“能不能快速求出答案”,而是:如果有一个候选解,能否在多项式时间内验证它的对错?

更正式地说,一个判定问题属于 NP,如果对于所有 yes 实例,都存在一个多项式长度的证据,并且这个证据可以在多项式时间内被验证。

用 CLIQUE 理解 NP

CLIQUE 问题是:给定一个图 \(G=(V,E)\) 和整数 \(k\),问图中是否存在大小至少为 \(k\) 的 clique?

这个问题属于 NP。虽然要设计一个多项式算法来求解很困难,但 NP 问题的定义只需要我能验证解的对错就可以。如果有人告诉你:

\(k\) 个点就是一个 clique。

你只需要检查这些点是否两两相连。检查 \( \frac{k(k-1)}{2} \) 次,这是多项式时间。

总结:NP 问题答案可能难找,但答案一旦给出,容易检查。

用数独理解 NP

数独更符合我们对这个问题的理解。给你一个空白很多的数独盘面,求解答案可能需要搜索很多可能性和大量运算。但如果有人给你一个填好的数独,你检查它是否合法很容易了。所以这类问题最有 NP 的味道。当然,常见的 \(9\times 9\) 数独规模太小,不适合直接讨论渐近复杂度。复杂度理论里通常讨论的是推广到更大规模的数独变体。不过作为直觉例子,数独很好地体现了 NP 的味道:答案可能难找,但答案一旦给出,容易检查。

所有 P 问题都属于 NP

前面已经提过,如果一个问题能多项式时间直接求解,那么当然也能多项式时间验证。所以:\( P \subseteq NP \),真正不知道的是:\( P = NP? \)

规约:把一个问题翻译成另一个问题

理解 NP-hard 和 NP-complete 之前,必须先理解规约。

规约的意思是:

把问题 A 的实例,在多项式时间内转换成问题 B 的实例,并且保持 yes/no 答案一致。

记作:

\[A \le_{poly} B \]

意思是:A 可以多项式时间规约到 B

也就是说,如果我们有一个能解决 B 的算法,那么就可以这样解决 A:

  1. 把 A 的输入转换成 B 的输入
  2. 调用 B 的算法
  3. 返回 B 的答案

所以:如果 \(A \le_{poly} B\),那么 B 至少和 A 一样难。A 可以借助 B 来解决,所以 B 的能力至少覆盖 A。

例子:INDEPENDENT SET 规约到 CLIQUE

在无向图中,independent set,中文常译为“独立集”,指的是一组顶点,其中任意两个顶点之间都没有边。

CLIQUE 刚好相反:

  • clique 要求任意两个点之间都有边
  • independent set 要求任意两个点之间都没有边

现在有一个问题:给定图 \(G\) 和整数 \(k\),问 \(G\) 中是否存在大小为 \(k\) 的 independent set?

我们可以把它规约到 CLIQUE。做法是构造 \(G\) 的补图 \(\overline{G}\)。补图的意思是:原来有边的地方,补图里没有边、原来没有边的地方,补图里有边

于是:

\[G \text{ 中有大小为 } k \text{ 的 independent set} \]

当且仅当:

\[\overline{G} \text{ 中有大小为 } k \text{ 的 clique} \]

所以:

\[INDEPENDENT\ SET \le_{poly} CLIQUE \]

这个转换显然可以在多项式时间内完成。

例子:CLIQUE 规约到 VERTEX COVER

在无向图中,vertex cover,中文常译为“顶点覆盖”,指的是一组顶点,使得图中每条边至少有一个端点被选中。

VERTEX COVER 的判定版是:

给定图 \(G\) 和整数 \(k\),问 \(G\) 中是否存在大小至少为 \(k\) 的 independent set?

CLIQUE 可以规约到 VERTEX COVER。

核心关系是:

\[G \text{ 有大小为 } k \text{ 的 clique} \]

当且仅当:

\[\overline{G} \text{ 有大小为 } n-k \text{ 的 vertex cover} \]

因为 \(G\) 中的 clique 等价于 \(\overline{G}\) 中的 independent set,一个 independent set 的补集就是 vertex cover。所以给定 CLIQUE 实例 \((G,k)\),我们可以转换成 VERTEX COVER 实例:

\[(\overline{G}, n-k) \]

如果 VERTEX COVER 的答案是 yes,那么原 CLIQUE 的答案也是 yes。

例子:3-SAT 规约到 CLIQUE

刚刚介绍了 2-SAT。3-SAT 是 SAT 的一个限制版本:公式是合取范式,每个子句 clause 中包含 3 个文字 literal。一个 literal 可以是变量本身,比如 \(x\),也可以是变量的否定,比如 \(\neg x\)

例如:

\[(x \lor y \lor z) \land (\neg x \lor y \lor w) \land (x \lor \neg z \lor w) \]

有些教材把 3-SAT 定义为每个子句“恰好 3 个 literal”,有些教材定义为“至多 3 个 literal”。这两个版本都可以证明为 NP-complete,差别不影响本文。

3-SAT 可以规约到 CLIQUE。构造思路是:

  • 每个 clause 里的每个 literal 建一个点;
  • 不同 clause 的两个 literal 如果不冲突,就连边;
  • 冲突的意思是一个是 \(x\),另一个是 \(\neg x\)
  • 如果公式有 \(m\) 个 clause,就问图中是否存在大小至少为 \(m\) 的 clique。

为什么这样有效?

一个大小为 \(m\) 的 clique,意味着我们可以从每个 clause 中选出一个 literal,并且这些 literal 两两不矛盾。这就对应着一组可以让公式为真的赋值。

所以:

\[\text{3SAT} \le_{\operatorname{poly}} \text{CLIQUE} \]

这个例子非常经典。

image

NP-hard:至少和 NP 中最难的问题一样难

一个问题 \(\Pi\) 是 NP-hard,如果:NP 中的所有问题都可以多项式时间规约到 \(\Pi\)

也就是说,对于任意 \(\Pi' \in NP\),都有:

\[\Pi' \le_{poly} \Pi \]

直观理解:NP-hard 问题至少和 NP 里最难的问题一样难。

不过 NP-hard 不要求这个问题本身属于 NP。因此 NP-hard 问题可以是:

  • 判定问题
  • 优化问题
  • 甚至不是可判定问题

例子:MAX-CLIQUE

MAX-CLIQUE 是优化问题:给定图 \(G\),输出图中最大 clique 的大小

如果你能快速解决 MAX-CLIQUE,那么你就能快速解决 CLIQUE 的判定版:图 \(G\) 中是否存在大小至少为 \(k\) 的 clique?

所以,MAX-CLIQUE 至少和 CLIQUE 一样难。而 CLIQUE 是 NP-complete,因此 MAX-CLIQUE 是 NP-hard。

例子:TSP 优化版是 NP-hard

旅行商问题 TSP 的优化版:给定若干城市以及城市之间的距离,求一条经过每个城市一次并回到起点的最短路线

如果优化版 TSP 能快速解决,那么判定版 TSP 也能快速解决。所以优化版 TSP 是 NP-hard。

特殊例子:停机问题

停机问题是:给定一个程序和输入,问这个程序运行后是否会停机?

这和前面的问题区别更大了,是一个不可判定问题。不存在一个算法能够对所有程序和输入都给出正确 yes/no 答案。

停机问题可以是 NP-hard,但它不是 NP-complete,因为 NP-complete 要求问题属于 NP,而停机问题连 NP 都不是。

这个例子说明 NP-hard 的范围比 NP-complete 大得多。

NP-complete:NP 里面最难的一批问题

NP-complete,中文叫 NP 完全。它既可以被多项式时间验证,又至少和 NP 中最难的问题一样难。关系可以写成:

\[NP\text{-complete} = NP \cap NP\text{-hard} \]

经典问题:SAT 是 NP-complete

SAT 是:给定一个布尔公式,是否存在一种变量赋值,使公式为真?它属于 NP。

因为如果有人给出一组变量赋值,我们可以把它代入公式,然后在多项式时间内检查公式是否为 true。

SAT 也是 NP-hard 和 NP-complete。Cook-Levin 定理证明了:任何 NP 问题都可以多项式时间规约到 SAT。

SAT 的重要性在于:它是第一个被证明为 NP-complete 的问题。从 SAT 出发,人们又证明了大量问题是 NP-complete。

其他例子

3-SAT 是 NP-complete。3-SAT 是 SAT 的特殊版本,每个 clause 只有 3 个 literal。它仍然是 NP-complete。3-SAT 是很多 NP-complete 证明的起点。

常见证明链条包括:

\[3SAT \le_{poly} CLIQUE \]

\[3SAT \le_{poly} 3COLORING \]

\[3SAT \le_{poly} HAMILTONIAN\ PATH \]

CLIQUE 是 NP-complete:它属于 NP,因为给定一组点,可以快速检查它们是否两两相连。它是 NP-hard,因为可以从 3-SAT 规约过来。

VERTEX COVER 是 NP-complete

给定图 \(G\) 和整数 \(k\),问是否存在不超过 \(k\) 个顶点,覆盖图中的所有边?

它属于 NP,因为给定一个点集,可以快速检查每条边是否至少有一个端点在点集中。它是 NP-hard,因为 CLIQUE 可以规约到 VERTEX COVER。

3-COLORING 是 NP-complete

给定一个无向图,问能否用 3 种颜色给所有顶点染色,使得任意相邻顶点颜色不同?

它属于 NP,因为给定一种染色方案,可以快速检查所有边两端颜色是否不同。它也是 NP-hard,可以通过 3-SAT 规约证明。

这里有一个很有意思的对比:

  • 2-COLORING 属于 P
  • 3-COLORING 是 NP-complete

颜色数只从 2 增加到 3,问题难度发生了巨大变化。

如何证明一个问题是 NP-complete

证明一个问题 \(\Pi\) 是 NP-complete,通常有一个标准模板。

证明它属于 NP

首先要证明:

\[\Pi \in NP \]

也就是:如果有一个候选解,你可以在多项式时间内验证它。

找一个已知 NP-complete 问题规约到它

然后找一个已经知道是 NP-complete 的问题 \(\Pi'\),证明:

\[\Pi' \le_{poly} \Pi \]

意思是:已知难问题 \(\Pi'\) 可以多项式时间转换成当前问题 \(\Pi\)

这一步说明:

如果 \(\Pi\) 容易,那么 \(\Pi'\) 也容易。

\(\Pi'\) 已经是 NP-complete,所以 \(\Pi\) 至少也同样难。

结合两步骤,就得到:\( \Pi \text{ 是 NP-complete} \)

一定要注意规约方向。要证明当前问题 \(\Pi\) 很难,应该证明:

\[\text{已知难问题} \le_{\operatorname{poly}} \Pi \]

而不是证明:

\[\Pi \le_{\operatorname{poly}} \text{已知难问题} \]

后者只能说明当前问题不比已知难问题更难,不能证明当前问题也很难。

常见的证明路线

很多 NP-complete 证明会形成一条链:

\[SAT \le_{poly} 3SAT \le_{poly} CLIQUE \le_{poly} VERTEX\ COVER \]

也可以有:

\[3SAT \le_{poly} 3COLORING \]

\[3SAT \le_{poly} HAMILTONIAN\ PATH \]

\[SUBSET\ SUM \le_{poly} KNAPSACK \]

规约具有传递性。如果:\( A \le_{poly} B \),并且:\( B \le_{poly} C \),那么:\( A \le_{poly} C \)。只要有几个“源头问题”被证明很难,后面就可以不断扩展出一整张 NP-complete 问题网络。

为什么要区分 P、NP、NP-hard、NP-complete

区分这些概念的意义不只是给问题贴标签,而是帮助我们判断应该投入什么样的算法努力。

如果一个问题属于 P,我们通常会继续寻找更快、更省空间、更适合工程实现的精确算法。

如果一个问题是 NP-complete,那么它很可能不存在多项式时间精确算法。此时更现实的方向包括:

  • 近似算法:不求最优,但保证离最优解不太远;
  • 启发式算法:不保证理论最优,但在实际数据上效果好;
  • 随机算法:通过随机化获得更好的平均表现;
  • 剪枝搜索:仍然搜索,但尽量减少不必要的分支;
  • 参数化算法:当某些参数很小时,问题可以高效求解;
  • 利用特殊结构:实际输入可能不是最坏情况。

如果一个问题是 NP-hard 但不一定属于 NP,比如某些优化问题,那么它至少和 NP-complete 问题一样难。我们不能指望一般情况下总能快速求出精确最优解。

所以,这些分类的作用是帮助我们避免把时间浪费在错误目标上:不是所有问题都适合追求“又快、又精确、又通用”的算法。

进阶补充:伪多项式时间

还有一个很容易被忽略的点:输入中的数字是如何编码的。

比如最短路径问题中,边权是整数还是浮点数,通常不会改变它属于 P 这个事实。算法主要受顶点数和边数影响。在理论分析中,我们通常假定比较边权大小、相加边权这些基本操作可以在合理时间内完成。

但有些问题就不一样,比如 0-1 背包问题。

0-1 背包问题是:

给定若干物品,每个物品有重量和价值,在容量限制内最大化总价值。

经典动态规划的复杂度是:

\[O(nW) \]

其中 \(n\) 是物品数量,\(W\) 是背包容量。

如果 \(W\) 很小,这个算法很好用。但如果 \(W\) 是一个非常大的数字,算法就会很慢。

关键在于:输入里写下数字 \(W\),需要的长度大约是 \(\log W\),而不是 \(W\) 本身。例如,用二进制写下一个很大的容量,只需要几十位或几百位,但动态规划却可能要跑和 \(W\) 成正比的步数。

因此,\(O(nW)\) 不是关于输入长度的多项式时间,而是关于数值大小的多项式时间。这种复杂度叫做伪多项式时间

这解释了为什么有些问题的“数值大小”非常重要。背包问题属于典型的弱 NP-hard 问题:当数值范围较小时,可以用伪多项式 DP;当数值范围很大时,困难就会暴露出来。

P vs NP 世纪难题

最后,我们回到最著名的问题:

所有可以快速验证的问题,是否也都可以快速求解?

显然 \( P \subseteq NP \),因为能快速求解,一定能快速验证。

但是 \( P = NP \) 吗?反过来是否成立,没人知道。

如果:

\[P = NP \]

那意味着所有 NP 问题,包括 SAT、CLIQUE、3-COLORING、TSP 判定版等,都有多项式时间算法。这会对优化、密码学、自动证明、程序分析、AI 规划等领域产生巨大影响。

如果:

\[P \ne NP \]

那就说明确实存在一些问题:答案可以快速验证,但不能快速求出。这符合大多数人的直觉,也是当前更广泛相信的方向。

但到目前为止,没人证明 \(P=NP\),也没人证明 \(P \ne NP\)

在 P vs NP 尚未解决之前,NP-complete 为我们提供了一种判断问题困难性的强有力证据。当你证明一个问题是 NP-complete,你实际上是在说:如果这个问题有多项式时间算法,那么所有 NP 问题都有多项式时间算法。