2讲义 11A:复杂性回顾
William Pires
这份讲义包含了我们在课堂上有时未加证明就提出的主张的证明。你只负责课堂上所教的内容,但将这些证明(或者更好的是,自己想出它们)作为练习可能会很有用。此外,这里的一些后期材料将在下一次讲座中讲解。
📖 [逐步解释]
这部分是讲义的引言。作者 William Pires 首先明确了这份讲义的目的和内容。
- 目的:这份讲义旨在提供一些在课堂上可能被提及但没有详细证明的理论主张的完整证明。这在理论课程中很常见,因为课堂时间有限,无法深入每一个细节。讲义作为补充材料,让学生可以深入研究。
- 内容:讲义包含了一些关键主张的证明。
- 学习建议:作者建议学生将阅读和理解这些证明,甚至尝试自己独立推导它们,作为一种学习练习。这有助于加深对概念的理解,而不仅仅是记忆结论。
- 范围说明:作者强调,学生的考试或课程要求范围仅限于课堂上讲授的内容。这份讲义是可选的补充学习材料,但对深入理解非常有益。
- 后续内容预告:引言最后提到,讲义的后半部分内容可能会在未来的讲座中进行讲解,这暗示了讲义内容的重要性以及与课程进度的衔接。
💡 [数值示例]
这个引言部分没有技术性内容,因此不涉及具体数值示例。我们可以举一个学习方法上的例子:假设课堂上老师说“两个多项式的和仍然是多项式”,但没有证明。这份讲义就会详细写出证明过程,比如设 $f(n)=O(n^a)$,$g(n)=O(n^b)$,那么 $f(n)+g(n) \le c_1 n^a + c_2 n^b \le (c_1+c_2) n^{\max(a,b)}$,所以 $f(n)+g(n) = O(n^{\max(a,b)})$,因此它也是多项式。学生通过自己推导这个过程,能更好地理解多项式的封闭性。
⚠️ [易错点]
- 误解学习范围:一个常见的易错点是学生可能会误以为这份补充讲义里的所有内容都是必考的。作者明确指出“你只负责课堂上所教的内容”,所以学生应以课堂笔记和要求为准,将此讲义视为拓展和加深理解的工具。
- 忽视练习价值:学生可能因为内容不强制要求而完全跳过。但作者建议将其作为练习,这对于培养理论推导能力至关重要。
📝 [总结]
本段是讲义的开篇引言,阐明了讲义的核心价值——为课堂上省略的证明提供详细推导。它定位了讲义的角色(补充学习材料),并给出了学习建议(主动练习证明),同时预告了内容与后续课程的关联。
🎯 [存在目的]
本段的目的是设定读者的预期,明确讲义的性质、范围和学习方法。它帮助学生理解为什么需要这份材料,以及如何最有效地利用它来辅助学习,避免学生因内容看似超出课堂范围而感到困惑或直接忽略。
🧠 [直觉心智模型]
可以将这份讲义看作是电影的“导演剪辑版”或附带的“幕后制作花絮”。课堂教学是公映版,为了流畅性和时间控制,会剪掉一些推导细节。而这份讲义则把所有被剪掉的、解释“为什么”的精彩细节都完整地呈现了出来,让真正感兴趣的观众能看到全貌。
💭 [直观想象]
想象一下你在学习一本武功秘籍。课堂上师傅只教了你一些招式(比如“飞龙在天”),告诉你它们很厉害。而这份讲义就像是秘籍中不轻易示人的内功心法部分,它详细解释了每一招每一式背后的运气法门、经络走向(即数学证明)。虽然只学招式也能用,但理解了内功心法,你才能真正融会贯通,甚至创造出新的招式。
32 时间复杂性
📜 [原文2]
1 时间复杂性
假设我们有一个图灵机 $M$ 判决一个语言 $L$。给定某个输入 $x$,我们想了解 $M$ 接受或拒绝 $x$ 需要多长时间。为此,我们考虑 $M$ 的运行时间作为 $x$ 长度的函数(将 $x$ 作为输入需要多少个字母表符号)。
📖 [逐步解释]
这一段引入了计算复杂性理论的核心概念之一:时间复杂性。
- 基本设定:我们从一个理论计算模型——图灵机 $M$ 开始。这个图灵机被设计用来“判决”一个语言 $L$。“判决”(decide)一个语言意味着对于任何给定的字符串,图灵机总能在有限的时间内停机,并明确地回答“是”(接受)或“否”(拒绝)。“是”表示该字符串属于语言 $L$,“否”则表示不属于。
- 核心问题:我们关心的不仅仅是图灵机 能否 解决问题,更关心它解决问题需要 多长时间。这个“多长时间”就是时间复杂性要研究的对象。
- 衡量标准:时间不是用秒或分钟来衡量的,因为那取决于具体的计算机硬件。在理论计算机科学中,时间是用图灵机执行的“步骤”数来衡量的。一个步骤通常指一次状态转换。
- 输入的角色:解决一个问题的耗时通常不固定,它依赖于问题的“规模”。对于图灵机来说,问题的规模就是输入字符串 $x$ 的长度,记为 $|x|$。输入越长,通常处理时间也越长。
- 函数关系:因此,我们不讨论某个具体输入的运行时间,而是将运行时间看作是输入长度 $n$ 的一个函数。这个函数描述了运行时间随着输入长度增长的变化趋势。
💡 [数值示例]
- 语言 L:所有由偶数个‘1’组成的字符串的语言。例如,"11", "1111", "" (空字符串) 属于 $L$;"1", "111" 不属于 $L$。
- 图灵机 M:一个简单的图灵机,从左到右扫描磁带上的输入 $x$。它用一个状态来记录目前为止读到的‘1’是奇数个还是偶数个。
- 输入 x:假设输入是 "1111"。它的长度 $|x| = 4$。
- 运行时间:$M$ 会从左到右读取这4个'1',每读取一个就改变一次状态,读完后停机。可能需要4步读取,加上起始和结束的一些步骤,比如总共6步。如果输入是 "111111",长度为6,可能需要8步。我们可以看到,运行时间大致和输入长度成正比。
⚠️ [易错点]
- 时间单位的混淆:初学者容易将理论上的“步骤数”与物理世界的“秒”混淆。时间复杂性是独立于硬件的抽象度量。
- 忽略对最坏情况的考虑:对于同一个长度的输入,算法的运行时间也可能不同。例如,搜索算法在数组的第一个位置就找到目标,和在最后一个位置才找到,耗时完全不同。时间复杂性通常(如下文定义)关心的是“最坏情况”下的运行时间。
📝 [总结]
本段为时间复杂性的讨论设定了舞台。它明确指出,我们将使用图灵机作为计算模型,通过计算其在处理不同长度的输入时所需执行的步骤数,来量化算法的效率。这建立了一个从输入长度到运行时间的函数关系,是后续所有复杂性分析的基础。
🎯 [存在目的]
本段的目的是从最基本的计算模型(图灵机)和问题(判决语言)出发,引出衡量计算资源消耗(特别是时间)的必要性。它将一个模糊的“快慢”问题,转化为一个精确的数学问题——分析一个函数,即运行时间函数。
[直觉心-智模型]
可以把图灵机想象成一个非常笨拙但很守规则的工人,他有一张长长的纸带(磁带)和一本操作手册(转换函数)。输入是写在纸带上的初始任务。时间复杂性就像是在问:对于一个长度为 $n$ 的任务清单,这个工人最多需要翻阅多少次操作手册、涂改多少次纸带才能完成任务?
💭 [直观想象]
想象你在做一道很长的数学题,比如计算 100 个数字的连加。题目的长度就是这 100 个数字。你每做一次加法,就算是一个“步骤”。完成这道题总共需要 99 次加法。如果题目变成 200 个数字,就需要 199 次加法。你的“运行时间”大约是 $n-1$ 步,其中 $n$ 是数字的个数(输入长度)。时间复杂性就是研究这种“步骤数”和“题目长度”之间的关系。
2.1 定义 1:运行时间
📜 [原文3]
定义 1. 设 $M$ 是一个在每个输入上都停机的图灵机。$M$ 的运行时间记作 $t(n)$,是 $M$ 在任何长度为 $n$ 的输入上所需的最大步骤数。也就是说:
$$
t(n):=\max _{x \in \Sigma^{*},|x|=n} \text { number of steps } M \text { takes before it halts on } x
$$
在上面,输入 $x$ 上 $M$ 的步骤数是 $M$ 停机前所经历的转换数。
📖 [逐步解释]
这个定义精确地形式化了上一段引入的“运行时间函数”的概念。
- 前提条件:这个定义只适用于“在每个输入上都停机”的图灵机。这种图灵机也叫判决器 (decider)。如果一个图灵机在某些输入上会无限循环,那么它的运行时间就是无限,这个定义就没意义了。
- 运行时间的记号:运行时间函数通常用 $t(n)$ 表示,其中 $n$ 是输入长度。
- 核心思想:最坏情况分析 (Worst-Case Analysis):定义中的 max 是关键。对于所有长度为 $n$ 的输入字符串,它们的实际运行步数可能各不相同。$t(n)$ 取的是这些步数中的“最大值”。这意味着我们总是在为最坏的情况做准备。无论你给我哪个长度为 $n$ 的输入,我都能保证在 $t(n)$ 步之内解决它。
- 形式化定义:
- $t(n) :=$ 这是定义 $t(n)$ 的符号。
- $\max_{x \in \Sigma^{*},|x|=n}$:这部分是说,我们要考虑所有可能的、长度为 $n$ 的输入字符串 $x$。$\Sigma^{*}$ 表示由字母表 $\Sigma$ 中所有符号组成的所有有限长度字符串的集合。
- number of steps M takes before it halts on x:这是对单个输入 $x$ 的运行步数。
- 步骤的定义:一段补充说明,明确了“步骤数”就是图灵机从开始到停机所执行的状态转换的总次数。
💡 [数值示例]
- 问题:在一个包含 $n$ 个整数的无序数组中,查找是否存在数字 5。
- 算法(图灵机 M):从数组的第一个元素开始,逐个检查是否为 5。如果找到,立即停机并回答“是”。如果检查完所有元素都没有找到,停机并回答“否”。
- 输入长度 n = 4:数组有 4 个元素。
- 情况 1 (最好情况):输入是 {5, 8, 2, 10}。算法第一步就找到了 5,运行 1 步。
- 情况 2:输入是 {8, 5, 2, 10}。算法第二步找到 5,运行 2 步。
- 情况 3 (最坏情况):输入是 {8, 2, 10, 5} 或者 {8, 2, 10, 9}。
- 在 {8, 2, 10, 5} 中,算法需要检查 4 次才找到。
- 在 {8, 2, 10, 9} 中,算法需要检查全部 4 个元素才确定 5 不存在。
这两种情况都代表了最坏的情况,运行 4 步。
- 计算 t(4):在所有长度为 4 的输入中,最多的运行步数是 4。所以 $t(4) = 4$。
- 推广:对于长度为 $n$ 的输入,最坏情况下需要检查所有 $n$ 个元素。所以 $t(n) = n$。
⚠️ [易错点]
- 平均情况 vs. 最坏情况:这个定义只关心最坏情况。在实际应用中,有时平均情况复杂性也很有用,但理论分析通常从最坏情况开始,因为它提供了一个性能的上限保证。
- 对所有输入的误解:$t(n)$ 不是所有长度为 $n$ 的输入的运行时间之和或平均值,而是在这些输入中挑选出的那个最长的运行时间。
📝 [总结]
定义1给出了时间复杂性分析的基石——最坏情况运行时间 $t(n)$。它是一个关于输入长度 $n$ 的函数,其值为在所有长度为 $n$ 的输入中,算法所需的最大执行步骤数。这个定义为我们提供了一个衡量算法效率的、有保证的、与具体机器无关的标尺。
🎯 [存在目的]
这个定义的目的是将算法效率的分析从对具体、单个输入的讨论,提升到对一类(相同长度)输入的一般性、系统性讨论。通过关注“最坏情况”,它为算法提供了一个可靠的性能保证,这在设计关键系统时至关重要。
🧠 [直觉心智模型]
$t(n)$ 就像是一个“承诺”。一个算法向你承诺:“无论你给我一个多难的、规模为 $n$ 的任务,我向你保证,我完成它所需的时间绝不会超过 $t(n)$。” 这个承诺让你能预估最糟糕的情况下需要等待多久。
💭 [直观想象]
想象你在快递公司分拣包裹。$n$ 是今天需要分拣的包裹总数。
- 最好情况:所有包裹都去同一个地址,你把它们放进一个箱子就行了,很快。
- 最坏情况:$n$ 个包裹,每个都去一个不同的、偏远的地址,你需要一个一个地查找地址、贴标签、分区。这是最费时的。
$t(n)$ 就是这个最坏情况所花费的时间。公司老板用 $t(n)$ 来安排人手和规划下班时间,因为他必须保证即使在最忙乱的一天,工作也能完成。
2.2 大 O 符号
📜 [原文4]
精确计算图灵机在输入上所需的步骤数相当麻烦,因此我们使用大 O 符号来隐藏常数。回想一下,对于两个函数 $f, g: \mathbb{N} \rightarrow \mathbb{R}^{+}$,我们说 $f(n)=O(g(n))$,如果存在常数 $c>0$ 和 $n_{0} \in \mathbb{N}$,使得对于所有 $n \geq n_{0}$:
$$
f(n) \leq c \cdot g(n)
$$
📖 [逐步解释]
这一段解释了为什么我们需要以及如何使用大O符号 (Big O Notation)。
- 动机:精确计算 $t(n)$,比如 $t(n) = 5n^2 + 10n + 3$,是非常繁琐且不必要的。我们更关心的是当输入规模 $n$ 变得非常大时,运行时间的“增长趋势”或“数量级”。是像 $n$ (线性),还是像 $n^2$ (平方),或是像 $2^n$ (指数)?
- 大 O 的作用:大O符号提供了一种标准的方式来忽略那些在 $n$ 很大时变得不重要的部分(低阶项和常数系数),而只关注起主导作用的部分。它描述了函数增长的“上界”。
- 形式化定义:$f(n) = O(g(n))$ 这句话的含义是“函数 $f(n)$ 的增长速度不会比函数 $g(n)$ 快太多”。
- 前提:有两个从自然数($\mathbb{N}$)映射到正实数($\mathbb{R}^{+}$)的函数 $f$ 和 $g$。在我们的场景中,$f(n)$ 就是运行时间 $t(n)$。
- 条件:要满足 $f(n) = O(g(n))$,必须能找到两个常数:一个正的比例因子 $c$ 和一个起始点 $n_0$。
- 核心不等式:对于所有大于等于这个起始点 $n_0$ 的 $n$,不等式 $f(n) \leq c \cdot g(n)$ 必须恒成立。这意味着,从 $n_0$ 开始,$f(n)$ 的曲线总是在 $c \cdot g(n)$ 曲线的下方(或与之重合)。
💡 [数值示例]
示例 1: 证明 $f(n) = 3n^2 + 10n + 4$ 是 $O(n^2)$。
- 目标: 我们需要找到 $c$ 和 $n_0$ 使得对于所有 $n \geq n_0$,$3n^2 + 10n + 4 \leq c \cdot n^2$。
- 推导:
- 我们希望右边只有 $n^2$ 项,所以尝试把左边的所有项都用 $n^2$ 来表示。
- 当 $n \geq 1$ 时,$10n \leq 10n^2$。
- 当 $n \geq 1$ 时,$4 \leq 4n^2$。
- 所以,$3n^2 + 10n + 4 \leq 3n^2 + 10n^2 + 4n^2 = 17n^2$。
- 选择 c 和 n0: 在这个推导中,我们让 $c=17$。这个不等式成立的前提是 $n \geq 1$。所以我们可以选择 $n_0 = 1$。
- 结论: 我们找到了 $c=17$ 和 $n_0=1$,使得对于所有 $n \geq 1$,$f(n) \leq 17n^2$。因此,根据定义,$f(n) = O(n^2)$。
示例 2: 证明 $f(n) = 100n + 50$ 是 $O(n)$。
- 目标: 找到 $c, n_0$ 使得 $100n + 50 \leq c \cdot n$ for all $n \geq n_0$。
- 推导:
- 当 $n \geq 1$ 时,$50 \leq 50n$。
- 所以,$100n + 50 \leq 100n + 50n = 150n$。
- 选择 c 和 n0: 我们选择 $c=150$ 和 $n_0=1$。
- 结论: $f(n) = O(n)$。
⚠️ [易错点]
- “等于”的误解:$f(n) = O(g(n))$ 中的等号不是真正的等于,更准确的理解是 $f(n) \in O(g(n))$(f 属于 O(g) 这个集合)。它描述的是一种单向的“小于等于”关系。例如,$n = O(n^2)$ 是正确的,但 $n^2 = O(n)$ 是错误的。
- 追求最紧的上界:虽然 $3n^2 + 10n + 4 = O(n^3)$ 甚至 $O(2^n)$ 在技术上都是正确的,但这么说没有意义。我们总是希望能找到一个尽可能“紧”的上界,即 $O(n^2)$。
- c 和 n0 不唯一:能使定义成立的 $c$ 和 $n_0$ 有无数组。例如在示例1中,我们也可以选择 $c=4$ 和 $n_0=10$,因为当 $n \ge 10$ 时,$3n^2+10n+4 \le 3n^2+n^2+n^2 = 5n^2$ 是错的,应该是 $3n^2+10n+4 \le 3n^2+n \cdot n + n^2/25 \cdot 4$ 也不对。换个方法:$3n^2+10n+4 \le 3n^2+n^2$ (当 $10n+4 \le n^2$ 时成立,解得 $n \ge 11$ 左右)。所以我们可以选择 $c=4$ 和 $n_0 = 11$。只要能找到一对即可。
📝 [总结]
大O符号是分析算法效率的通用语言。它通过忽略低阶项和常数因子,让我们聚焦于算法运行时间随输入规模增长的核心趋势。$f(n) = O(g(n))$ 意味着当 $n$ 足够大时,$f(n)$ 的增长速度由一个更简单的函数 $g(n)$ 给出了一个上界。
🎯 [存在目的]
引入大O符号的目的是为了简化和标准化算法复杂性的讨论。如果没有它,我们将陷入对 $5n^2+3$ 和 $6n^2-10n$ 孰优孰劣的无尽争论中。大O符号让我们能够清晰地断言:它们都是 $O(n^2)$,属于同一个复杂性级别。这使得比较不同算法的根本效率成为可能。
🧠 [直觉心智模型]
大O符号就像是给函数贴上“增长速度”的标签。$O(n)$ 是“线性增长”标签,$O(n^2)$ 是“平方增长”标签,$O(\log n)$ 是“对数增长”标签。我们给一个算法的运行时间函数贴上最合适的、最简单的标签,以便快速了解其性能特征。
💭 [直观想象]
想象有两匹马赛跑。马A的速度函数是 $f(n)=n^2$ 米/秒,马B的速度函数是 $g(n)=100n$ 米/秒。在开始的几秒($n<100$),马B更快。但是,一旦比赛距离足够长($n \ge 100$),马A就会远远超过马B,并且它们之间的差距会越来越大。大O分析就像是在说:“从长远来看,马A的潜力(增长率)是‘平方级’的,而马B只是‘线性级’的。所以对于长跑比赛,马A最终会是赢家。” 它关注的是最终的、起决定性作用的增长趋势。
2.3 定义 2:TIME(t(n)) 复杂性类
📜 [原文5]
定义 2 ($\operatorname{Time}(t(n))$). 设 $t: \mathbb{N} \rightarrow \mathbb{N}$,我们定义复杂性类 $\operatorname{Time}(t(n))$ 为以下语言集合:
$$
\operatorname{Time}(t(n)):=\{L \mid L \text { is a language decided by an } O(t(n)) \text { time Turing machine }\}
$$
展开定义,这意味着存在某个常数 $c$ 和 $n_{0}$,使得对于所有 $n \geq n_{0}$,$M$ 在所有长度为 $n$ 的输入上在 $\leq c * t(n)$ 的时间内停机,并且 $M$ 判决 $L$。
我们注意到 $\operatorname{Time}(t(n))$ 的定义对我们所考虑的计算模型敏感:单带图灵机与多带图灵机、算法(如 Python)等。但是我们将关注的 P 的定义不依赖于这些。因此你可以安全地忽略这些问题,并使用(确定性)伪代码来描述算法。
📖 [逐步解释]
这个定义利用大O符号将具有相似运行时间的语言(或问题)归为一类,这个“类”就是复杂性类。
- 什么是复杂性类:复杂性类是一个“装语言的篮子”。所有能被某种特定资源限制(比如时间或空间)的计算模型解决的语言,都被放进同一个篮子里。
- TIME(t(n)) 的定义:这个特定的篮子叫做 $\operatorname{Time}(t(n))$。它装的是所有那些“可以被一个运行时间为 $O(t(n))$ 的图灵机判决”的语言。
- 展开定义:一个语言 $L$ 属于 $\operatorname{Time}(t(n))$,当且仅当:
- 存在一个图灵机 $M$ 能够判决 $L$。
- 这个图灵机 $M$ 的运行时间是 $O(t(n))$。根据大O定义,这意味着存在常数 $c$ 和 $n_0$,使得对于所有长度 $n \geq n_0$ 的输入,$M$ 的最坏情况运行步数不超过 $c \cdot t(n)$。
- 对计算模型的敏感性:这是一个非常重要的理论细节。$\operatorname{Time}(t(n))$ 的确切内容取决于你用的是哪种图灵机。例如,一个问题用“多带图灵机”可能在 $O(n)$ 时间解决,但用“单带图灵机”可能需要 $O(n^2)$ 时间。这意味着同一个语言 $L$ 可能属于多带图灵机的 $\operatorname{Time}(n)$,但属于单带图灵机的 $\operatorname{Time}(n^2)$。
- P 的优越性:作者提前剧透,后续将要定义的复杂性类 P(多项式时间)有一个非常好的特性,就是它对这些常见的确定性计算模型不敏感。一个问题在多带图灵机上是多项式时间可解的,那么在单带图灵机上也是(尽管多项式的次数可能会增加)。这使得我们可以忽略底层模型的细节,直接用更方便的伪代码来讨论算法,只要能证明它是多项式时间的就行。
💡 [数值示例]
- t(n) = n:$\operatorname{Time}(n)$ 就是所有能被线性时间图灵机判决的语言的集合。例如,在数组中找最大值的问题对应的语言就属于 $\operatorname{Time}(n)$。
- t(n) = n^2:$\operatorname{Time}(n^2)$ 就是所有能被平方时间图灵机判决的语言的集合。例如,一个朴素的比较排序算法(如冒泡排序)所解决的排序问题,就属于这个类别(在特定模型下)。
- 关系: 如果一个语言属于 $\operatorname{Time}(n)$,那么它肯定也属于 $\operatorname{Time}(n^2)$,因为一个 $O(n)$ 的算法也满足 $O(n^2)$ 的定义。所以 $\operatorname{Time}(n) \subseteq \operatorname{Time}(n^2)$。
⚠️ [易错点]
- 类与问题的混淆:$\operatorname{Time}(t(n))$ 是一个集合(一个类),里面装着很多很多语言(问题)。而 $O(t(n))$ 是描述单个算法运行时间的一个函数上界。
- 忽略模型依赖性:在进行精细的复杂性分析时(例如,区分 $\operatorname{Time}(n)$ 和 $\operatorname{Time}(n \log n)$),计算模型至关重要。只有在讨论像 P 这样“健壮”的复杂性类时,我们才能在一定程度上忽略模型差异。
📝 [总结]
$\operatorname{Time}(t(n))$ 是一个复杂性类,它将所有能被运行时间为 $O(t(n))$ 的图灵机解决的问题(语言)收集在一起。这个定义为我们提供了一种根据计算效率对问题进行分类的方法。同时,本段也指出了这种分类方式可能对底层计算模型敏感,并预示了 P 类的重要性,因为它克服了这个问题。
🎯 [存在目的]
这个定义的目的是从对单个问题效率的分析,过渡到对一整类问题效率的分析。通过创建复杂性类,理论计算机科学家可以研究不同类别问题之间的关系(例如,$\operatorname{Time}(n)$ 和 $\operatorname{Time}(n^2)$ 是否真的不同?),从而构建起整个计算复杂性的大厦。
🧠 [直觉心智模型]
复杂性类就像是图书馆里的书架。
- $\operatorname{Time}(n)$ 书架上放的是“读一遍就能懂”的书(线性时间问题)。
- $\operatorname{Time}(n^2)$ 书架上放的是“需要把每一页和其他所有页都比较一遍才能懂”的书(平方时间问题)。
- $\operatorname{Time}(2^n)$ 书架上放的是“需要尝试所有可能的解读组合才能懂”的书(指数时间问题)。
一个语言属于哪个复杂性类,就好像一本书应该被放在哪个书架上。
💭 [直观想象]
想象一个城市里的交通工具系统。
- $\operatorname{Time}(n)$ 类比于“步行可达”的问题。问题的规模 $n$ 就像距离,时间花费和距离成正比。
- $\operatorname{Time}(\log n)$ 类比于“坐地铁可达”的问题。即使距离 $n$ 增加很多,花费的时间(站数)增长得很慢。
- $\operatorname{Time}(n^k)$ 类比于“开车可达”的问题。在城市里,这通常是可接受的。
- $\operatorname{Time}(2^n)$ 类比于“只能靠运气找到一条不堵的小路”才能到达的问题。随着距离 $n$ 的增加,找到这条路的时间呈爆炸性增长,非常不靠谱。
复杂性类就是给城市里的每个地点(问题)贴上一个标签:“步行区”、“地铁区”、“驾车区”或“禁区”。
2.4 多项式
📜 [原文6]
定义 3. 我们说 $t: \mathbb{N} \rightarrow \mathbb{N}$ 是多项式的,如果存在某个常数 $k$ 使得 $f(n)=O\left(n^{k}\right)$。
示例:$n^{5}+1000, \log (n)$ 都是多项式的。$2^{n}+n^{3}$ 不是多项式的。
好消息:如果 $f: \mathbb{N} \rightarrow \mathbb{N}$ 和 $g: \mathbb{N} \rightarrow \mathbb{N}$ 都是多项式的,那么以下也是多项式的:
- $f+g$,其中 $(f+g)(x)=f(x)+g(x)$。
- $f * g$,其中 $(f * g)(x)=f(x)*g(x)$。
- $f \circ g$,其中 $(f \circ g)(x)=f(g(x))$。
你可以不加证明地使用这一点,尽管证明起来并不难,我们将在定理 6 的证明中给出最后一个的详细证明。
📖 [逐步解释]
这段内容定义了什么是“多项式”时间,并列举了它的关键性质。这是区分“高效”算法和“低效”算法的核心分界线。
- 多项式的定义:一个函数 $f(n)$ 被认为是多项式的,如果它的增长速度被某个 $n^k$ 函数所限制。换句话SHUO,它的大O表示是 $O(n^k)$,其中 $k$ 是一个固定的常数,与 $n$ 无关。
- 示例分析:
- $n^5 + 1000$:是 $O(n^5)$,因为当 $n$ 很大时,$n^5$ 项占主导。这里的 $k=5$,所以它是多项式的。
- $\log(n)$:对数函数的增长比任何 $n^k$(只要 $k>0$)都要慢。例如,$\log(n) = O(n^1)$。所以它也是多项式的。这是一个需要注意的点,比多项式增长慢的函数也算在多项式时间的范畴里。
- $2^n + n^3$:这是一个指数函数。无论你选择多大的常数 $k$,当 $n$ 足够大时,$2^n$ 的增长最终都会超过 $n^k$。因此,$2^n$ 不是 $O(n^k)$,这个函数不是多项式的。
- 多项式的封闭性 (Closure Properties):这部分是“好消息”,因为它说明了多项式时间算法的良好组合特性。
- 和 (Addition):两个多项式时间算法顺序执行,总时间是两者之和,结果仍然是多项式时间。
- 积 (Multiplication):一个多项式时间的循环,其内部再执行一个多项式时间的操作,总时间是两者之积,结果仍然是多项式时间。
- 复合 (Composition):一个多项式时间的算法,其输入是另一个多项式时间算法的输出,总时间是函数的复合,结果仍然是多项式时间。这在归约(后面会讲)中非常重要。
- 使用许可:作者允许我们直接使用这些封闭性质,无需每次都重新证明,这大大简化了后续的复杂性分析。
💡 [数值示例]
- 多项式定义:
- $f(n) = 10n^3 + 5n^2 + 100$ 是 $O(n^3)$,所以是多项式的 ($k=3$)。
- $f(n) = n \log n$ 是 $O(n^2)$,所以是多项式的 ($k=2$)。
- 非多项式定义:
- $f(n) = n!$ (阶乘),增长比任何指数函数都快,不是多项式的。
- 封闭性:
- 和: 设 $f(n) = n^2$ (算法A),$g(n) = n^3$ (算法B)。顺序执行两个算法,总时间是 $n^2+n^3$。这是 $O(n^3)$,仍然是多项式。
- 积: 算法A循环 $n^2$ 次,每次循环内部执行算法B ($n^3$ 步)。总时间是 $n^2 \cdot n^3 = n^5$。这是 $O(n^5)$,仍然是多项式。
- 复合: 算法B的输入大小是算法A的输出。假设算法A输出大小为 $m=n^2$。算法B在输入 $m$ 上的时间是 $m^3 = (n^2)^3 = n^6$。这是 $O(n^6)$,仍然是多项式。
⚠️ [易错点]
- k 必须是常数:在 $O(n^k)$ 中,$k$ 必须是一个不依赖于 $n$ 的常数。像 $O(n^{\log n})$ 这样的函数,虽然看起来有点像多项式,但它的指数不是常数,所以它不是多项式的。它被称为“准多项式时间”。
- 对数函数是多项式时间:这是一个常见的混淆点。因为 $\log n$ 增长得非常慢,它被任何正次方的多项式(如 $n^{0.001}$)作为上界,所以它属于多项式时间的范畴。更准确地说,多项式时间意味着“时间复杂度不差于多项式”。
📝 [总结]
本段定义了“多项式增长”,这是衡量算法是否“高效”或“可行”(tractable)的理论标准。任何运行时间可以被 $n^k$($k$为常数)限制的算法都被认为是多项式时间算法。此外,本段还强调了多项式时间算法在组合(顺序、嵌套、复合)下的稳定性,即多项式时间的“积木”搭建起来的仍然是多项式时间的。
🎯 [存在目的]
本段的目的是建立一条明确的、具有良好数学性质的界线,来区分“快”算法和“慢”算法。这条线就是“多项式时间”。这个定义对于后续引入复杂性类 P 至关重要,并且“多项式的封闭性”是多项式时间归约理论的基石。
🧠 [直觉心智模型]
多项式时间就像是“可预测的、可控的”增长。如果你告诉一个工程师,他的算法是多项式时间的,他会感到安心,因为他知道即使问题规模加倍,运行时间也只是增加一个固定的倍数(比如 $2^k$ 倍),而不是失控地爆炸。而非多项式时间(如指数时间)则是“失控的、不可用的”增长,问题规模稍微增大一点点,就可能需要宇宙毁灭那么长的时间来计算。
💭 [直观想象]
想象你在盖房子。
- 多项式时间 $O(n^2)$:盖一个 $n \times n$ 平米的地基。如果 $n$ 翻倍,面积变成原来的 4 倍,工作量是可预期的。这是可行的。
- 指数时间 $O(2^n)$:假设要盖一座塔,每加盖一层($n \to n+1$),之前的所有楼层都要重新加固一遍,导致总工作量翻倍。当塔盖到一定高度时,再加盖一层就会变得几乎不可能。这就是不可行的。
“多项式”意味着任务的复杂度增长是“有理性的”,而“指数”则意味着增长是“疯狂的”。
43 P 和 NP
📜 [原文7]
2 P 和 NP
📖 [逐步解释]
这是讲义的核心章节标题,预示着本章将介绍计算机科学领域最核心、最著名的两个复杂性类:P 和 NP。这个标题将整个讨论从基础定义(时间复杂性,大O)提升到了对重大理论概念的探讨。P vs NP 问题是计算机科学中最重要、悬而未决的百万美元大奖问题。本章将分别定义它们,并为后续讨论它们之间的关系打下基础。
📝 [总结]
这是一个章节标题,标志着讲义进入了中心议题:定义和解释复杂性类 P 和 NP。
🎯 [存在目的]
作为章节标题,它的作用是组织文章结构,并告知读者即将进入一个全新的、更高级别的主题。
3.1 P
📜 [原文8]
21 P
📖 [逐步解释]
这是“P 和 NP”章节下的一个小节标题,表示将首先开始详细定义和讨论复杂性类 P。
🎯 [存在目的]
结构化内容,让读者清晰地知道,下面的内容将专门围绕 P 展开。
31.1 定义 4: P
📜 [原文9]
定义 4 (P).
$\mathrm{P}=\{L \mid L \text { is a language and there exists a decider } M \text { for } L \text { running in polynomial time } \}$ 等价地,
$$
\mathrm{P}:=\bigcup_{k \in N} \operatorname{Time}\left(n^{k}\right)
$$
📖 [逐步解释]
这个定义正式引入了复杂性类 P。
- 第一种定义(描述性):
- $\mathrm{P}$ 是一个语言的集合(一个复杂性类)。
- 一个语言 $L$ 属于 P,条件是:存在一个确定性图灵机(decider) $M$ 能够判决 $L$,并且这个 $M$ 的运行时间是多项式时间。
- “多项式时间”的含义是,存在某个常数 $k$,使得 $M$ 的运行时间是 $O(n^k)$。
- 简而言之,P 包含了所有可以被确定性算法在多项式时间内“高效”解决的问题。
- 第二种定义(数学化):
- $\mathrm{P}$ 是所有 $\operatorname{Time}(n^k)$ 复杂性类的并集。
- $k$ 取遍所有自然数 $N=\{0, 1, 2, \dots\}$。
- $\operatorname{Time}(n^0)$, $\operatorname{Time}(n^1)$, $\operatorname{Time}(n^2)$, $\operatorname{Time}(n^3)$, ... 把所有这些复杂性类里的语言都倒进一个大篮子里,这个大篮子就是 P。
- 这个定义更精确地表达了“存在某个常数 $k$”的含义。只要你能找到任何一个 $k$ 使得问题能在 $O(n^k)$ 时间解决,那它就属于 P。
💡 [数值示例]
- 线性搜索:在一个无序数组中查找一个元素。最坏情况需要 $O(n)$ 时间。因为 $n=n^1$,所以 $k=1$。这个问题属于 $\operatorname{Time}(n^1)$,因此它属于 P。
- 冒泡排序:对一个数组进行排序。最坏情况需要 $O(n^2)$ 时间。$k=2$。这个问题属于 $\operatorname{Time}(n^2)$,因此它属于 P。
- 矩阵乘法:两个 $n \times n$ 矩阵相乘的标准算法需要 $O(n^3)$ 时间。$k=3$。这个问题属于 $\operatorname{Time}(n^3)$,因此它属于 P。
- 一个不被认为在 P 中的例子(目前):旅行商问题(TSP)。找到访问 $n$ 个城市并返回起点的最短路径。已知的最优确定性算法都需要指数时间,如 $O(n^2 2^n)$。目前没有找到多项式时间算法,所以 TSP 通常被认为不属于 P。
⚠️ [易错点]
- P 不等于“实用”:虽然 P 被理论上定义为“高效”的,但一个 $O(n^{100})$ 的算法在实际中是完全不可用的。然而,根据定义,它仍然属于 P。幸运的是,大多数现实中遇到的 P 类问题的多项式次数都很低(如 2, 3)。这被称为“多项式时间的经验性假设”。
- 确定性是关键:P 的定义严格要求是确定性图灵机 (decider)。每一步的选择都是唯一确定的。这与后面 NP 的非确定性形成对比。
📝 [总结]
复杂性类 P 是所有能够被一个确定性算法在多项式时间内解决的判定问题的集合。它被广泛认为是理论上“高效可解”问题的边界。其数学定义是所有 $\operatorname{Time}(n^k)$ 类的并集。
🎯 [存在目的]
定义 P 的目的是为了给“简单问题”或“易解问题”一个形式化的、健壮的分类。这个类构成了我们计算能力的一个基准,也是复杂性理论中进行比较和归类的出发点。
🧠 [直觉心智模型]
P 类问题就是那些“我们知道如何按部就班、在合理时间内解决”的问题。就像是有一本详细的菜谱,只要你严格按照步骤做,即使食材再多(输入规模 $n$ 变大),你花的时间也只是按一个可控的方式(多项式)增加,最终总能做出菜来。
💭 [直观想象]
想象你在一个巨大的图书馆里找一本书。
- P 类查找方式:图书馆的书是按字母顺序排列的。你可以用二分查找法。每一步都排除掉一半的书。即使书的总数 $N$ 非常大,你需要的步数也只是 $\log N$。这是一个非常高效的、在 P 类中的方法。
- 另一个 P 类方式:假设你要检查所有书的标题是否包含“龙”字。你必须一本一本地翻阅,需要 $O(N)$ 时间。虽然慢,但也是多项式时间,所以也属于 P。
- 非 P 类方式:假设图书馆的书是完全混乱的,你要找出一个包含 10 本书的子集,它们的厚度加起来正好是 1 米。除了把所有可能的 10 本书的组合都试一遍之外,没有更好的办法。组合的数量是指数级的,这就不属于 P。
31.2 证明模板 1:展示 L 属于 P
📜 [原文10]
证明模板 1 (展示 $L \in \mathrm{P}$ ).
- 给出 $L$ 的确定性判决器的伪代码。
- 展示如果 $x \in L$,你的算法接受。
- 展示如果 $x \notin L$,你的算法拒绝。
- 展示 $M$ 在多项式时间内运行,即在 $O\left(n^{k}\right)$ 时间内,其中 $k$ 为某个常数。(无需具体证明 $k$ 是什么)。
我们将在第 4 节中使用此模板进行证明。
📖 [逐步解释]
这段提供了一个标准的、结构化的方法来证明一个问题(语言 $L$)是属于复杂性类 P 的。
- 步骤 1: 描述算法。首先,你必须设计一个具体的、一步一步的确定性算法(用伪代码描述就足够了)。这个算法的输入是问题的一个实例 $x$。
- 步骤 2: 证明完备性 (Completeness)。你必须证明,对于任何一个“是”的实例(即 $x \in L$),你的算法总能正确地输出“接受”。这保证了你的算法不会漏掉任何正确的解。
- 步骤 3: 证明可靠性 (Soundness)。你必须证明,对于任何一个“否”的实例(即 $x \notin L$),你的算法总能正确地输出“拒绝”。这保证了你的算法不会把不是解的误判为解。步骤2和3合起来证明了你的算法是“正确”的。
- 步骤 4: 证明效率。最后,也是最关键的一步,你必须分析你的算法的运行时间,并证明它是多项式时间的。也就是说,它的运行时间是 $O(n^k)$,其中 $n$ 是输入 $x$ 的长度,$k$ 是某个常数。这一步证明了你的算法是“高效”的。
同时满足这四点,就严格地证明了 $L \in \mathrm{P}$。
💡 [数值示例]
问题 L: PATH = $\{\langle G, s, t \rangle \mid G \text{ 是一个有向图,且存在从节点 } s \text{ 到节点 } t \text{ 的路径}\}$。证明 PATH $\in \mathrm{P}$。
- 伪代码: 使用广度优先搜索 (BFS)。
```
Algorithm BFS_PATH(G, s, t):
- 创建一个队列 Q,并将 s 放入 Q。
- 创建一个集合 visited,并将 s 放入 visited。
- while Q is not empty:
- u = Q.pop()
- if u == t: return "Accept"
- for each neighbor v of u:
- if v is not in visited:
- add v to visited
- add v to Q
- return "Reject"
```
- 完备性: 如果存在从 $s$ 到 $t$ 的路径,那么 BFS 保证能够找到它。因为 BFS 会层层探索所有从 $s$ 可达的节点,所以最终一定会到达 $t$ 并接受。
- 可靠性: 如果不存在从 $s$ 到 $t$ 的路径,那么 $t$ 永远不会被加入队列。算法最终会因为队列变空而终止,并返回“拒绝”。
- 效率: 假设图有 $V$ 个节点和 $E$ 条边,输入大小 $n$ 大致是 $V+E$ 的量级。
- 每个节点最多被入队和出队一次 ($O(V)$)。
- 每条边最多被检查一次 ($O(E)$)。
- BFS 的总运行时间是 $O(V+E)$。这是一个关于输入规模 $n$ 的线性时间算法,是多项式时间的($k=1$)。
- 结论: 满足所有四个条件,因此 PATH $\in \mathrm{P}$。
⚠️ [易错点]
- 忘记证明正确性: 很多人在证明一个问题属于 P 时,只给出了一个多项式时间算法,但忘记了严格证明这个算法对于所有情况都是正确的(完备性和可靠性)。
- 时间复杂度分析错误: 对算法的时间复杂度分析可能出错,比如错误地估计了循环次数。例如,对于图算法,需要清楚地说明复杂度是关于节点数 $V$ 还是边数 $E$ 的函数。
- 忽略输入表示: 算法的时间必须是输入规模 $n$ 的多项式。需要清楚输入的表示方式,例如,一个图是用邻接矩阵($n=V^2$)还是邻接表($n=V+E$)表示,这会影响复杂度的具体形式,但通常不影响它是否为多项式。
📝 [总结]
该模板为证明一个语言 $L$ 属于 P 类提供了一个清晰的四步路线图:1. 提出一个确定性算法,2. 证明它能解决所有“是”实例,3. 证明它能解决所有“否”实例,4. 证明它在多项式时间内完成。
🎯 [存在目的]
这个模板的目的是将一个抽象的证明任务具体化、流程化。它为学生提供了一个可以遵循的、不会遗漏关键点的框架,使得证明过程更加严谨和规范。
🧠 [直觉心智模型]
这个模板就像是申请一项专利的流程。
- 提交设计图纸(伪代码)。
- 证明你的发明能工作(完备性)。
- 证明你的发明不会产生副作用(可靠性)。
- 证明你的发明制造成本低廉且高效(多项式时间)。
只有全部满足,你的“高效解决问题”的专利($L \in \mathrm{P}$)才会被批准。
💭 [直观想象]
想象你要向法官证明被告“有能力在一天内从纽约走到波士顿”($L \in P$)。
- 提供路线方案(伪代码):先坐火车到纽黑文,再转巴士...
- 证明路线可行性(完备性):如果被告真的这样走,时刻表显示他确实能在一天内到达。
- 排除其他可能性(可靠性):如果他不按这个路线走,或者中途出现意外,我们算法会“拒绝”(这里类比不太恰当,但核心是算法的确定性)。
- 计算时间成本(多项式时间):整个行程的时间是坐火车时间+巴士时间,这是一个“合理”的时间,不是需要几百年。
法官审核了你的全部证据,最终裁定:被告确实具备这个能力。证明成功。
3.2 NP
📜 [原文11]
22 NP
非确定性图灵机 (NTM) 是一种允许进行非确定性转换的图灵机。也就是说,给定当前状态 $q$ 和图灵机磁头在不同磁带上看到的符号,可能存在多个可能的转换。例如,对于具有状态 $Q$ 和字母表 $\Sigma$ 的单带图灵机,转换函数现在是 $\delta: Q \times \Sigma \rightarrow \mathcal{P}(Q \times \Sigma \times\{L, R\})$ 的形式。
📖 [逐步解释]
这一段开始介绍复杂性类 NP,首先从它的计算模型——非确定性图灵机 (Nondeterministic Turing Machine, NTM)——开始。
- 与确定性的区别:确定性图灵机 (DTM) 在任何时刻,根据当前状态和读到的符号,下一步的动作是唯一确定的。而 NTM 在同样的情况下,可能有多种选择。
- “非确定性”的含义:它不是指随机,而是指“多分叉的可能性”。你可以把 NTM 的计算过程想象成一棵树,每个节点代表一个机器状态,每条树枝代表一个可能的转换。DTM 的计算是一条单一的路径,而 NTM 的计算是探索一整棵树。
- 转换函数的形式化:
- DTM 的转换函数 $\delta(q, a) = (q', a', d)$ 返回一个元组,表示下一个状态、要写入的符号和移动方向。
- NTM 的转换函数 $\delta(q, a)$ 返回一个集合,这个集合里包含多个可能的元组。例如 $\delta(q_1, 'a') = \{ (q_2, 'b', R), (q_3, 'a', L) \}$,意味着在 $q_1$ 状态读到 'a' 时,机器可以变成 $q_2$ 状态、写入 'b' 并右移,或者可以变成 $q_3$ 状态、写入 'a' 并左移。
- $\mathcal{P}(\dots)$ 表示幂集 (Power Set),即所有子集的集合。返回一个转换元组的集合,就意味着可以选择其中任何一个来执行。
💡 [数值示例]
问题:在一个图中寻找是否存在一个长度为 2 的从节点 A 到节点 D 的路径。
假设图中有边 A->B, A->C, B->D, C->E。
- 从 A 出发,选择第一条边 A->B。
- 从 B 出发,选择第一条边 B->D。
- 路径是 A->B->D,长度为 2,终点是 D。找到路径,停机并接受。
- 在节点 A,非确定性地猜测下一步要去哪里。它有两个选择:B 或 C。
- 计算路径 1: 猜测走向 B。现在在 B。
- 从 B 出发,下一步只能去 D。路径 A->B->D。终点是 D,长度为 2。这条计算路径接受。
- 计算路径 2: (回到第1步) 猜测走向 C。现在在 C。
- 从 C 出发,下一步只能去 E。路径 A->C->E。终点不是 D。这条计算路径拒绝。
NTM 的计算产生了两个分支,因为其中一个分支(路径1)接受了,所以整个 NTM 就接受了这个输入。
⚠️ [易错点]
- 非确定性不等于随机:NTM 不是随机选择一个分支走下去。它被认为是“并行”地探索所有分支,或者说它具有“神奇的猜测能力”,总能做出“正确”的猜测(如果存在正确路径的话)。
- 非确定性不是量子计算:虽然都有并行性的味道,但它们的数学模型和计算能力有本质区别。
📝 [总结]
本段引入了非确定性图灵机 (NTM) 作为定义 NP 的基础。与确定性图灵机每一步动作唯一不同,NTM 在每一步可能有多个选择,其计算过程可以看作是同时探索所有可能的计算路径。
🎯 [存在目的]
为了定义复杂性类 NP,必须首先定义其底层的计算模型 NTM。NTM 的“多选择”特性是理解 NP“猜测并验证”本质的关键。
🧠 [直觉心智模型]
NTM 就像一个拥有无数个“分身”的侦探。在案发现场的每个岔路口,他不是只选择一条路走,而是派出分身去探索每一条路。只要有任何一个分身最终找到了凶手(找到了一个接受路径),整个侦探团队(NTM)就立刻宣布破案。
💭 [直观想象]
你正在走一个迷宫。
- 确定性图灵机:你只有一个人,遵循“右手扶墙”之类的固定策略。你可能会走很多冤枉路,但最终能走遍整个迷宫。
- 非确定性图灵机:在每个岔路口,你都可以分裂成多个你,每个你去探索一条路。只要其中任何一个你找到了出口,你就瞬间知道了出口在哪里。这种“分裂”的能力就是非确定性。
32.1 NTM 的判决和运行时间
📜 [原文12]
回想一下,我们说非确定性图灵机 $M$ 判决语言 $L$,如果:
- $M$ 总是对输入 $x$ 停机,无论它做了什么转换选择。
- 如果 $x \in L$,存在一些转换选择使得 $M$ 接受 $x$。
- 如果 $x \notin L$,对于所有转换选择,$M$ 拒绝 $x$。
我们通常认为这样的图灵机能够“猜测”,然后检查这个猜测是否是解。如果存在任何幸运的猜测,则该字符串属于该语言。
定义 5. 设 $M$ 是一个在每个输入上都停机的非确定性图灵机。我们说 $M$ 的运行时间为 $t(n)$,如果对于任何长度为 $n$ 的输入 $x$, $M$ 在 $x$ 上的所有可能计算在最多 $t(n)$ 步骤内终止。
特别是,对于 $M$ 在时间 $t(n)$ 内运行,在最多 $t(n)$ 次转换后,$M$ 必须在任何长度为 $n$ 的输入上停机。这与我们在每一步非确定性选择的转换无关。因此,NTM 的运行时间概念是所有可能的计算路径中的最坏情况概念。接受概念是最好的情况:如果存在某个猜测(计算路径)接受,则输入在语言中。
📖 [逐步解释]
这部分定义了 NTM 如何“判决”一个语言以及如何衡量其运行时间,这两点都与 DTM 有着微妙但关键的区别。
NTM 的判决规则:
- 停机要求:首先,NTM 必须是一个判决器,意味着在任何输入上,沿着任何计算分支,都必须在有限步内停机。不能有无限循环的分支。
- 接受条件 (Asymmetry):
- 对于属于 L 的输入 ($x \in L$):只需要至少存在一个计算分支(一条“幸运”的路径)最终到达“接受”状态。
- 对于不属于 L 的输入 ($x \notin L$):所有的计算分支都必须最终到达“拒绝”状态。
- “猜测与验证”模型:这个不对称的接受规则完美地诠释了“猜测并验证”的模式。NTM 首先“猜测”一个可能的解(通过一系列非确定性选择),然后沿着这个分支的剩余部分“验证”这个猜测是否正确。如果 $x \in L$,那么至少存在一个正确的“解”可以被猜到并验证成功。如果 $x \notin L$,那么任何“猜测”都无法通过验证。
NTM 的运行时间定义 (定义 5):
- 最长路径决定:NTM 的计算是一棵树,运行时间 $t(n)$ 不是由接受路径的长度决定的,而是由这棵树的深度决定的。
- 最坏情况中的最坏情况:和 DTM 一样,NTM 的运行时间也是一个最坏情况的度量。它是指,在所有长度为 $n$ 的输入中,那个能产生最深的计算树的输入的树深。
- 接受与时间的对比:
- 接受: 是“最好情况”逻辑——只要有一个分支接受就算接受。
- 时间: 是“最坏情况”逻辑——必须等到所有分支(即使是那些最终拒绝的)都停机,取其中最长的时间。
💡 [数值示例]
问题:判断一个数 $N$ 是否为合数(非素数)。设 $N=9$。
- 猜测: 非确定性地猜测一个整数 $i$,范围在 $2$ 到 $N-1$ 之间。
- 验证: 检查 $N$ 是否能被 $i$ 整除。如果能,接受;否则,拒绝。
- 输入 N=9:
- 分支 1: 猜测 $i=2$。$9 \pmod 2 \neq 0$。拒绝。
- 分支 2: 猜测 $i=3$。$9 \pmod 3 = 0$。接受。
- 分支 3: 猜测 $i=4$。$9 \pmod 4 \neq 0$。拒绝。
- ...
- 分支 7: 猜测 $i=8$。$9 \pmod 8 \neq 0$。拒绝。
- 判决: 因为存在一个分支($i=3$)接受了,所以 NTM 最终判决 9 是合数。这是正确的。
- 输入 N=7 (素数):
- 无论猜测 $i=2, 3, 4, 5, 6$,都不能整除 7。所以所有计算分支都会拒绝。因此 NTM 判决 7 不是合数。这也是正确的。
- 运行时间: 猜测一步,验证(做一次除法)假设需要 $\log^2 N$ 步。那么每个分支的长度大约是 $1+\log^2 N$。这棵树的深度(即运行时间)就是 $O(\log^2 N)$。
⚠️ [易错点]
- 接受/拒绝条件混淆:最常见的错误是混淆接受和拒绝的条件。记住:接受是“存在主义的”(存在一个就行),拒绝是“普遍主义的”(所有都必须是)。
- 运行时间的计算:NTM 的运行时间不是接受分支的长度,而是所有分支中最长那条的长度。即使接受分支很短,你也必须等待最长的那个拒绝分支运行完。
📝 [总结]
本段阐明了 NTM 的两个核心运作原则:非对称的接受/拒绝逻辑(存在一个接受路径即接受 vs. 所有路径都拒绝才拒绝),以及由所有计算路径中最长路径决定的运行时间。这个模型完美地形式化了“猜测一个解并验证它”的计算过程。
🎯 [存在目的]
这两个定义是构建复杂性类 NP 的最后一块基石。没有对 NTM 判决和运行时间的清晰定义,NP 的定义将是无源之水。特别是这个不对称的接受规则,是 NP 区别于 P 和其他复杂性类的本质所在。
🧠 [直觉心智模型]
NTM 的判决就像一个庞大的法律团队为被告辩护。
- 如果被告是无辜的 ($x \in L$): 只要团队中有一个律师能找到一个证据(一个计算分支)证明被告无罪,法官就判被告无罪(接受)。
- 如果被告是有罪的 ($x \notin L$): 只有当控方检查了所有律师提出的所有辩护理由(所有计算分支),发现没有一个能证明其无罪时,法官才判被告有罪(拒绝)。
- 运行时间:整个审判结束的时间,不取决于那个聪明的律师多快找到证据,而取决于最笨的那个律师花了多长时间陈述完他那毫无道理的辩护(最长的计算路径)。
💭 [直观想象]
你在一场寻宝游戏中,地图上标有多个可能的藏宝点。
- NTM 的判决:你派出无数个机器人,每个去一个点。
- 宝藏确实存在 ($x \in L$): 只要有一个机器人报告“我找到了!”,游戏就成功结束。
- 宝藏不存在 ($x \notin L$): 你必须等到所有机器人都报告“这里没有”,你才能确定地说这个地方没有宝藏。
- NTM 的运行时间:是你派出机器人到所有机器人都返回报告这个过程所花费的总时间,这取决于去最远的那个藏宝点的机器人花了多长时间。
32.2 定义 6 和 7: NTIME 和 NP
📜 [原文13]
与确定性情况一样,我们有以下两个定义:
定义 6 (NTime($t(n)$)). 设 $t: \mathbb{N} \rightarrow \mathbb{N}$,我们定义复杂性类 NTime$(t(n))$ 为以下语言集合:
NTime$(t(n)):=\{L \mid L$ is a language decided by an $O(t(n))$ time non-deterministic Turing machine $\}$
复杂性类 NP 定义如下:
定义 7 (NP).
$\mathrm{NP}=\{L \mid L \text { is a language decided by a polynomial time non-deterministic Turing machine } \}$ 等价地,
$$
\mathrm{NP}:=\bigcup_{k \in N} \mathrm{NTime}\left(n^{k}\right)
$$
📖 [逐步解释]
这两个定义完全仿照 P 的定义方式,但把计算模型从确定性图灵机 (DTM) 换成了非确定性图灵机 (NTM)。
定义 6 (NTIME(t(n))):
- 这是一个非确定性时间复杂性类。
- 它是一个语言的集合。
- 一个语言 $L$ 属于 $\operatorname{NTIME}(t(n))$,当且仅当存在一个非确定性图灵机 (NTM) 能够在 $O(t(n))$ 的时间内判决它。
- 这个定义与 $\operatorname{Time}(t(n))$ 的唯一区别就是把 "Turing machine" 换成了 "non-deterministic Turing machine"。
定义 7 (NP):
- NP 的全称是 Nondeterministic Polynomial time(非确定性多项式时间)。
- 描述性定义: NP 是所有可以被一个非确定性图灵机 (NTM) 在多项式时间内判决的语言的集合。
- 数学化定义: NP 是所有非确定性多项式时间复杂性类的并集。即 $\mathrm{NP} = \mathrm{NTIME}(n^0) \cup \mathrm{NTIME}(n^1) \cup \mathrm{NTIME}(n^2) \cup \dots$。
- 这个定义与 P 的定义在结构上是平行的,唯一的区别就是把 $\operatorname{Time}$ 换成了 $\operatorname{NTIME}$。
💡 [数值示例]
- 合数问题 (Composite):前面例子里的判断一个数 $N$ 是否为合数的问题。我们设计的 NTM 算法运行时间是 $O(\log^2 N)$。输入是 $N$ 的二进制表示,长度为 $n = \lceil \log_2 N \rceil$。所以运行时间是 $O(n^2)$,这是多-项式时间。因此,Composite $\in \mathrm{NP}$。
- 子集和问题 (Subset Sum):给定一个整数集合 $S$,是否存在一个非空子集,其元素之和为 0?
- NTM 算法:
- 猜测: 对于 $S$ 中的每个元素,非确定性地猜测“选”或“不选”它,从而构成一个子集。
- 验证: 计算这个子集中所有元素的和。如果和为 0,接受;否则拒绝。
- 分析:
- 猜测阶段:需要 $|S|$ 步。
- 验证阶段:计算和需要 $|S|$ 次加法。
- 总时间是多项式的。如果存在这样的子集,那么必然有一条猜测路径能选中它并接受。如果不存在,所有猜测路径都会拒绝。
- 结论: Subset Sum $\in \mathrm{NP}$。
⚠️ [易错点]
- NP 的常见误解:NP 不是 "Non-Polynomial"(非多项式)的缩写!这是一个极其常见的误解。NP 指的是“非确定性多项式时间”。很多 NP 问题目前没有找到多项式时间解法,但这不代表 NP 的定义是“难解问题”。
- NP 问题不一定难:所有 P 类问题都在 NP 中(后面会讲)。所以 NP 里面既有简单问题,也可能包含难问题。
📝 [总结]
复杂性类 NP 被定义为所有能被一个非确定性图灵机在多项式时间内解决的判定问题的集合。这等价于说,NP 问题就是那些解的正确性可以在多项式时间内被确定性地验证的问题(“猜测并验证”模式)。
🎯 [存在目的]
定义 NP 的目的是为了形式化一大类在计算机科学、运筹学、人工智能等领域非常重要的“搜索问题”和“优化问题”的判定版本。这些问题的共同特点是:找到一个解可能很难,但验证一个给定的解是否正确却相对容易。NP 的定义精确地捕捉了这一特性。
🧠 [直觉心智模型]
NP 类问题就像是那些“答案写出来很容易检查对错,但要你想出答案却很难”的数学题。比如,一个复杂的数独谜题。
- 验证解(容易):给你一个填好的数独,你只需要花几分钟检查一下每行、每列、每宫是否都满足1-9不重复的规则。这个验证过程是多项式时间的。
- 找出解(困难):让你从一个空白的数独开始解题,你可能需要大量的尝试和回溯,可能会花上几个小时。找到解的过程似乎是指数时间的。
NP 就是所有这类“验证容易,求解难(可能)”的问题的集合。
💭 [直观想象]
想象你是一个艺术评论家,要判断一幅画是不是伦勃朗的真迹。
- 确定性求解(P类问题):如果有一个鉴定机器,你把画放进去,它几分钟后就输出“真”或“假”,这就是 P 类问题。
- 非确定性求解(NP类问题):没有这样的机器。但是,如果有人拿来一幅画,同时提供了一大堆证据(签名细节、颜料年代分析报告、流传历史记录...),你可以通过多项式时间(比如一天)的分析来验证这些证据是否可靠,从而判断画的真伪。NP 就是所有这类“给定证据可以高效验证”的问题。那个证据就是“证书”。
32.3 证明模板 2:使用 NTM 展示 L 属于 NP
📜 [原文14]
证明模板 2 (使用非确定性图灵机展示 $L \in \mathrm{NP}$ ).
- 给出 $L$ 的非确定性判决器 $M$ 的伪代码。这里 $M$ 只以 $x$ 作为输入。
- 展示如果 $x \in L$,存在一些非确定性选择使得 $M$ 接受。
- 展示如果 $x \notin L$,无论非确定性步骤如何,$M$ 总是拒绝 $x$。
- 展示 $M$ 在多项式时间内运行,即在 $O\left(n^{k}\right)$ 时间内,其中 $k$ 为某个常数。(无需具体证明 $k$ 是什么)。
📖 [逐步解释]
这个模板提供了使用 NTM 定义来证明一个语言 $L$ 属于 NP 的标准流程。它和证明 $L \in \mathrm{P}$ 的模板非常相似,但核心区别在于算法的性质(非确定性)和正确性证明的逻辑(存在性 vs. 普遍性)。
- 步骤 1: 设计 NTM 算法。你需要描述一个非确定性算法。这个算法的核心通常是“猜测”一个潜在的解。伪代码中需要明确指出哪些步骤是非确定性的。
- 步骤 2: 证明完备性 (“是”的情况)。你必须证明,如果输入 $x$ 是一个“是”实例($x \in L$),那么至少存在一条“幸运”的非确定性选择路径,能引导算法最终接受 $x$。这通常对应于“猜中”了那个能证明 $x \in L$ 的解。
- 步骤 3: 证明可靠性 (“否”的情况)。你必须证明,如果输入 $x$ 是一个“否”实例($x \notin L$),那么无论非确定性步骤做出何种选择,算法的每一条可能路径都必须最终拒绝 $x$。这保证了算法不会接受一个不该接受的输入。
- 步骤 4: 证明效率。你必须分析 NTM 的运行时间。根据定义,这指的是其最长计算路径的长度(即计算树的深度)。你必须证明这个最长路径的长度是输入长度 $n$ 的多项式,即 $O(n^k)$。
💡 [数值示例]
问题 L: CLIQUE = $\{\langle G, k \rangle \mid G \text{ 是一个图,且 G 中包含一个大小为 k 的团 (clique)}\}$。一个团是指一个顶点子集,其中任意两个不同的顶点之间都有一条边。证明 CLIQUE $\in \mathrm{NP}$。
- NTM 伪代码:
```
Algorithm NTM_CLIQUE(G, k):
- // 猜测阶段
- 创建一个空集合 S。
- 非确定性地从图 G 的 V 个顶点中选择 k 个顶点,并将它们加入 S。
4.
- // 验证阶段
- for each pair of distinct vertices {u, v} in S:
- if there is no edge between u and v in G:
- Reject.
- Accept.
```
- 完备性: 如果 $G$ 中确实存在一个大小为 $k$ 的团 $C$,那么在步骤3中,存在一个非确定性选择路径,恰好选中了 $C$ 中的所有顶点。对于这个选择,验证阶段的 for 循环将检查 $C$ 中的每一对顶点,因为 $C$ 是一个团,它们之间都有边,所以 if 条件永远不会触发,算法最终会接受。
- 可靠性: 如果 $G$ 中不存在大小为 $k$ 的团,那么无论步骤3中非确定性地选择了哪 $k$ 个顶点的子集 $S$,这个 $S$ 必然不是一个团。这意味着,在 $S$ 中至少存在一对顶点 ${u,v}$ 之间没有边。因此,验证阶段的 for 循环必然会找到这对 ${u,v}$,触发 if 条件,并拒绝。所以,所有计算路径都会拒绝。
- 效率:
- 步骤 3: 猜测 $k$ 个顶点。这可以看作是 $k$ 次猜测,每次从剩余顶点中猜一个。这可以在多项式时间内完成。
- 步骤 6-8: 验证阶段。集合 $S$ 中有 $k$ 个顶点,需要检查 $\binom{k}{2} = \frac{k(k-1)}{2}$ 对顶点。$k$ 小于等于总顶点数 $V$,所以这是 $O(V^2)$。检查一对顶点间是否有边,在邻接矩阵表示下是 $O(1)$。总验证时间是 $O(V^2)$。
- 整个算法的最长路径长度是多项式的(关于输入大小 $V^2 + \log k$)。因此 CLIQUE $\in \mathrm{NP}$。
⚠️ [易错点]
- 猜测步骤不清晰: 必须明确指出哪一步是“非确定性”的。
- 混淆 NTM 和 DTM 的时间: NTM 的多项式时间指的是“猜测+验证”的总时间是多项式的。不要把它和用确定性算法暴力搜索所有可能性的指数时间混淆。
- 忘记分析验证所需的时间: 光猜测是不够的,验证步骤本身也必须是多项式时间的。
📝 [总结]
该模板为使用 NTM 模型证明一个问题属于 NP 类提供了清晰的四步流程:1. 设计一个“猜测并验证”的非确定性算法;2. 证明如果解存在,算法能“猜”到并接受;3. 证明如果解不存在,所有“猜测”都会失败并拒绝;4. 证明整个“猜测+验证”过程在多项式时间内完成。
🎯 [存在目的]
这个模板将 NP 的抽象定义与具体的算法设计和证明实践联系起来。它让证明过程标准化,帮助学生理解如何将一个问题的“搜索”特性转化为一个形式化的非确定性算法,并对其进行正确的复杂性分析。
🧠 [直觉心智模型]
这个模板就像是在指导你怎么写一篇“寻宝可行性报告”。
- 提出寻宝策略(NTM 算法):在地图上所有可能的位置(猜测)进行挖掘。
- 证明能找到宝藏(完备性):如果宝藏真的在某个位置,我的策略包含了对该位置的挖掘,所以一定能找到。
- 证明不会误报(可靠性):如果地图上根本没有宝藏,我挖遍了所有位置,自然一个也找不到,报告也就都是“没找到”。
- 估算寻宝时间(多项式时间):挖掘并确认每个位置是否是宝藏的时间是合理的(多项式的)。(这里的时间是单个机器人的时间,而不是所有机器人加起来的时间)。
💭 [直观想象]
想象你正在参加一个猜谜游戏,谜底是一个名人。
- NTM算法:你拥有特异功能,可以瞬间在脑中“闪现”(非确定性猜测)出世界上所有名人的名字。
- 验证:对于每一个闪现出的名字,你问主持人:“是这个人吗?” 主持人回答“是”或“否”。
- 完备性:如果谜底是“爱因斯坦”,那么你脑中闪现的无数个名字里必然包括“爱因斯坦”,当你问到他时,主持人会说“是”,你就成功了。
- 可靠性:如果谜底其实是一个不在你名人录里的人,那你问遍了所有名字,主持人都会说“否”。
- 时间:你“闪现”名字是瞬间的(1步),问一个名字并得到答案也是很快的(多项式时间)。所以整个过程是多项式时间的。因此,“猜名人”问题在 NP 中。
32.4 示例 1: 3-着色问题
📜 [原文15]
示例 1. 考虑 3-着色问题。在这个问题中,你得到一个图 $G$ 作为输入。$G$ 由 $n$ 个节点的列表 $V$ 和 $m$ 条边的列表 $E$ 给出。目标是知道是否存在 $G$ 的 3-着色。也就是说,对于每个节点,我们是否可以分配颜色 $\{r, b, g\}$ 之一,并且如果 $(u, v)$ 是 $G$ 中的一条边,则 $u$ 和 $v$ 必须具有不同的颜色。
我们想证明 3-着色:$=\{\langle V, E\rangle \mid\langle V, E\rangle$ 代表一个可 3-着色图 $\}$ 属于 NP。
这可以通过非确定性图灵机解决,如下所示。
```
Algorithm 1 3-Coloring NTM
Input: $\langle V, E\rangle$
▷ Here $V$ a list of $n$ nodes. $E$ is a list of edges between the nodes in $V$.
for each vertex $v$ in $V$ do $\quad \triangleright$ Non-deterministically pick a coloring of the graph.
Non-deterministically assign a color from $\{\mathrm{r}, \mathrm{b}, \mathrm{g}\}$ to $v$.
end for
for Every edge $(u, v) \in E$ do $\quad \triangleright$ Verify this is a good coloring.
If $u, v$ have the same color : Reject $\langle V, E\rangle$.
end for
Accept $\langle V, E\rangle$.
```
上述算法在多项式时间内运行,即:给每个顶点分配颜色需要 $O(|V|)$ 时间,因为它只涉及遍历 $V$ 中的所有顶点并为每个顶点选择一种颜色。然后检查每条边 $(u, v)$ 以确保 $(u, v)$ 具有不同的颜色,这需要 $|V| *|E|$ 的多项式时间,因为我们遍历 $E$ 中的每个顶点并查找两个顶点的颜色。
如果图存在 3-着色 $C$,那么存在某个非确定性分支,图灵机将此着色分配给顶点并因此接受。
[^0]如果输入是一个图,但不存在 3-着色,那么图灵机永远不会接受。事实上,对于第一个 for 循环中选择的每种可能的着色,由于不存在 3-着色,我们将找到一条边,其两个顶点具有相同的颜色并拒绝。
📖 [逐步解释]
这个例子应用了“模板2”来证明一个著名的问题——3-着色问题 (3-COLORING)——属于 NP。
- 问题定义:
- 输入: 一个图 $G=(V, E)$,其中 $V$ 是顶点集,E 是边集。
- 问题: 是否能用三种颜色(比如红、绿、蓝)给图中的每个顶点着色,使得任意一条边的两个端点颜色都不同?
- 应用模板2:
- 步骤 1 (NTM 伪代码): 算法分为两阶段:
- 猜测阶段: 遍历图中的每一个顶点 $v$。对于每个 $v$,非确定性地从 {红, 绿, 蓝} 中选择一个颜色赋给它。当所有顶点都赋完颜色后,我们就“猜测”出了一种完整的着色方案。
- 验证阶段: 遍历图中的每一条边 $(u,v)$。检查 $u$ 和 $v$ 的颜色。如果发现它们的颜色相同,则说明这个着色方案是无效的,立即拒绝。如果检查完所有边都没有发现冲突,说明这是一个有效的 3-着色,接受。
- 步骤 2 (完备性证明): 文中解释:“如果图存在 3-着色 $C$,那么存在某个非确定性分支,图灵机将此着色分配给顶点并因此接受。” 这句话的意思是,在猜测阶段,那条“幸运”的、恰好猜出正确着色方案 $C$ 的计算路径是存在的。当算法沿着这条路径执行时,验证阶段不会发现任何冲突,因此会接受。
- 步骤 3 (可靠性证明): 文中解释:“如果输入是一个图,但不存在 3-着色,那么图灵机永远不会接受。” 这是因为,对于猜测阶段产生的所有可能的着色方案(每一个方案都是一条计算路径),它们没有一个是有效的 3-着色。因此,对于任何一个着色方案,验证阶段都必然能至少找到一条边 $(u,v)$,其两端颜色相同,从而导致该路径被拒绝。既然所有路径都被拒绝,NTM 作为一个整体就拒绝该输入。
- 步骤 4 (效率分析):
- 猜测阶段: 有 $|V|$ 个顶点,对每个顶点做一次 3 选 1 的非确定性选择。这个过程的路径长度是 $O(|V|)$。
- 验证阶段: 有 $|E|$ 条边。对于每条边,需要查找其两个端点的颜色并比较。查找颜色可能需要 $O(|V|)$(如果颜色存储在列表中)或者 $O(1)$(如果用哈希表或数组)。假设是 $O(|V|)$,则总验证时间是 $O(|V| \cdot |E|)$。这是一个关于输入规模的多项式。(原文中分析 “$|V|*|E|$” 有些夸大,更精确的分析是 $O(|E|)$,假定颜色可以快速访问)。无论如何,这显然是多项式时间。
- 结论: 既然 3-着色问题满足了模板2的所有条件,因此它属于 NP。
💡 [数值示例]
- 输入图 G: 一个三角形,顶点为 {1, 2, 3},边为 {(1,2), (2,3), (3,1)}。
- NTM 运行:
- 猜测阶段: NTM 会产生 $3^3 = 27$ 条不同的计算路径,对应所有可能的着色方案。
- 路径 1 (幸运的猜测): 猜 V1=红, V2=绿, V3=蓝。
- 路径 2 (不幸的猜测): 猜 V1=红, V2=红, V3=绿。
- ...
- 验证阶段:
- 对于路径 1:
- 检查边 (1,2): V1(红) ≠ V2(绿)。OK。
- 检查边 (2,3): V2(绿) ≠ V3(蓝)。OK。
- 检查边 (3,1): V3(蓝) ≠ V1(红)。OK。
- 所有边都 OK,接受。
- 对于路径 2:
- 检查边 (1,2): V1(红) = V2(红)。冲突!拒绝。
- 最终判决: 因为至少存在一条路径(路径1)接受了,所以 NTM 最终接受输入 $\langle G \rangle$,判断该图可 3-着色。
⚠️ [易错点]
- 时间复杂度分析的细节: 原文中的 $|V|*|E|$ 分析不够精确。一个更好的分析是:猜测 $O(|V|)$,验证时检查 $|E|$ 条边,每次检查花的时间取决于数据结构,如果是邻接表并且颜色用数组存,每次检查是 $O(1)$,总验证时间是 $O(|E|)$。总时间是 $O(|V|+|E|)$,是多项式时间。关键在于结论(多项式时间)是正确的。
- NTM 的幻觉: NTM 并没有真的同时尝试所有 $3^{|V|}$ 种可能性。它是一个数学抽象。这个抽象告诉我们,问题的解空间虽然巨大,但其结构(“证书”)很简单,容易验证。
📝 [总结]
这个例子完美地展示了如何使用“猜测并验证”模型来证明一个问题属于 NP。3-着色问题的“解”(即一个具体的着色方案)可能很难找到,但是一旦有人给出了一个方案,验证它是否正确却非常快(只需检查每条边)。NTM 模型恰好形式化了这一过程:非确定性步骤“猜”出一个方案,后续的确定性步骤在多项式时间内“验证”它。
🎯 [存在目的]
通过 3-着色这个具体、经典且直观的例子,本段旨在巩固读者对 NP 和 NTM 工作方式的理解。它将抽象的证明模板应用到实践中,让读者看到理论是如何与具体问题相结合的。
🧠 [直觉心智模型]
3-着色的 NTM 就像一个拥有特异功能的小孩在做填色游戏。
- 猜测: 小孩看一眼图,瞬间(非确定性)就在脑中想象出了一个完整的填色方案。
- 验证: 然后他一眼扫过去(多项式时间),检查是不是每条线的两端颜色都不一样。
如果这个图真的可以被三种颜色填好,那么他脑中想象出的无数方案里,至少有一个是正确的,他扫一眼后就会高兴地宣布“可以!”。如果这个图根本没法用三种颜色填,那他脑中想象出的所有方案都会被他自己扫一眼后发现有错误,他就会沮...
32.5 NP 的另一种定义:验证器
📜 [原文16]
对于 NP 中的问题,除非你使用检查所有计算的指数时间暴力算法,否则通常很难确定性地判断 $x \in L$,但如果 $x \in L$,我可以给你一个“证明” $y$,通过查看 $y$ 你可以确信 $x \in L$(在多项式时间内)。
这激发了基于验证器的 NP 的另一种定义:
定义 8 (验证器). 语言 $L$ 的验证器 $V$ 是一个确定性算法,使得 $V$ 将 $x$ 和一些字符串 $c$ 作为输入,并且
$$
x \in L \leftrightarrow \exists c \text { such that } V(x, c) \text { accepts. }
$$
如果 $V$ 在 $O\left(|x|^{k}\right)$ 时间内运行(对于某个常数 $k$),则 $V$ 被称为多项式时间验证器。
在上面,我们可以将 $c$ 视为 $x \in L$ 的证明(有时称为“证书”或“证据”)。如果 $x \in L$,则某个证明必须使验证器接受;如果 $x \notin L$,则任何证明都不能使验证器接受。
如果 $V$ 是一个多项式时间验证器,我们必须始终有 $|c|=O\left(|x|^{k}\right)$,也就是说证明的长度必须是 $|x|$ 的多项式长度(否则,图灵机甚至无法在 $O\left(|x|^{k}\right)$ 时间内读取 $c$)。
📖 [逐步解释]
这一段从一个更直观、更实用的角度重新定义了 NP,即通过“验证器” (Verifier) 的概念。
- 核心思想的转换:我们不再谈论神奇的“非确定性图灵机”,而是回到了我们更熟悉的“确定性算法”。但这个算法的任务不是从头解决问题,而是“验证一个解”。
- 验证器的角色:
- 一个验证器 $V$ 是一个确定性的算法。
- 它接受两个输入:原始问题实例 $x$ 和一个所谓的“证书” (certificate) $c$。
- 这个证书 $c$ 就像是宣称“$x$ 属于 $L$”的一个“证据”或“证明”。
- 验证器的逻辑 (定义 8):
- $x \in L \leftrightarrow \exists c \text { such that } V(x, c) \text { accepts.}$
- 这句话是双向的:
- ($\rightarrow$) 如果 $x \in L$: 那么必须存在至少一个证书 $c$,当你把 $(x, c)$ 一起喂给验证器 $V$ 时,$V$ 会输出“接受”。这个 $c$ 就是 $x \in L$ 的“证据”。
- ($\leftarrow$) 如果存在一个证书 $c$ 使得 $V(x,c)$ 接受: 那么我们就可以断定 $x \in L$。
- 推论: 如果 $x \notin L$,那么对于所有可能的证书 $c$,$V(x, c)$ 都必须输出“拒绝”。任何伪造的“证据”都无法通过验证。
- 多项式时间验证器:
- NP 的关键在于这个验证过程必须是“高效”的。
- 一个验证器被称为多项式时间验证器,如果它的运行时间是关于原始输入 $x$ 长度的多项式,即 $O(|x|^k)$。
- 证书的长度:
- 一个重要的隐含条件是,证书 $c$ 本身的长度也必须是 $|x|$ 的多项式。
- 原因很简单:如果证书是指数级长度,验证器光是把它读一遍就需要指数时间,那它就不可能是多项式时间验证器了。
💡 [数值示例]
- 问题: 3-着色问题,输入图 $G$ 是一个三角形。
- 问题实例 x: $\langle G \rangle$。
- 证书 c: 一个具体的着色方案,例如 $c = \{(v_1, \text{红}), (v_2, \text{绿}), (v_3, \text{蓝})\}$。
- 验证器 V: 接收 $\langle G \rangle$ 和 $c$。它的算法就是检查 $G$ 中的每一条边,看其端点的颜色在 $c$ 中是否不同。
- 运行:
- 如果 $G$ 是可 3-着色的: 那么至少存在一个有效的着色方案 $c$(比如上面那个)。当 $V$ 收到这个 $c$ 时,它会检查所有边,发现都符合要求,于是接受。
- 如果 $G$ 不可 3-着色: 那么任何你提供给 $V$ 的着色方案 $c'$,都必然至少有一条边的端点颜色相同。$V$ 在检查到这条边时就会拒绝。所以不存在任何 $c'$ 能让 $V$ 接受。
- 效率: 验证器 $V$ 的运行时间是 $O(|E|)$,是关于输入 $|x| \approx |V|+|E|$ 的多项式。证书 $c$ 的长度是 $O(|V|)$,也是多项式的。
⚠️ [易错点]
- 验证器 vs. 判决器: 判决器 (decider) 自己从头解决问题,只接受 $x$。验证器 (verifier) 是一个“法官”,它不主动破案,只根据别人提供的“证据” $c$ 来判断 $x$ 是否有罪。
- 证书是什么: 证书不是凭空来的,它就是那个能证明 "$x \in L$" 的具体事物。对于 3-着色,它是一个着色方案;对于哈密顿路径问题,它是一条具体的路径;对于子集和问题,它是一个具体的子集。
📝 [总结]
本段提供了 NP 的第二个、等价的、也是更直观的定义:NP 是所有拥有一个多项式时间确定性验证器的语言的集合。这个定义将 NP 的本质刻画为“解的验证是容易的”,其中“解”就是那个证书 $c$。
🎯 [存在目的]
这个基于验证器的定义比基于 NTM 的定义更容易理解和使用。它避免了“非确定性”这个抽象且容易误解的概念,将其替换为“证书”和“验证”这两个更具体、更贴近我们解决问题直觉的术ăpadă好的,我将严格按照您的要求,根据 解释工作流WorkFlow.md 的规范,对您提供的 Handout_11A_Complexity_Problems_复杂性理论练习.ZH.md 文件内容进行逐段、逐句、逐个细节的超详细解释。我将一次性在本次回复中提供完整的、不省略、不截断的全部内容。
5讲义 11A:复杂性回顾
William Pires
这份讲义包含了我们在课堂上有时未加证明就提出的主张的证明。你只负责课堂上所教的内容,但将这些证明(或者更好的是,自己想出它们)作为练习可能会很有用。此外,这里的一些后期材料将在下一次讲座中讲解。
📖 [逐步解释]
这部分是讲义的引言。作者 William Pires 首先明确了这份讲义的目的和内容。
- 目的:这份讲义旨在提供一些在课堂上可能被提及但没有详细证明的理论主张的完整证明。这在理论课程中很常见,因为课堂时间有限,无法深入每一个细节。讲义作为补充材料,让学生可以深入研究。
- 内容:讲义包含了一些关键主张的证明。
- 学习建议:作者建议学生将阅读和理解这些证明,甚至尝试自己独立推导它们,作为一种学习练习。这有助于加深对概念的理解,而不仅仅是记忆结论。
- 范围说明:作者强调,学生的考试或课程要求范围仅限于课堂上讲授的内容。这份讲义是可选的补充学习材料,但对深入理解非常有益。
- 后续内容预告:引言最后提到,讲义的后半部分内容可能会在未来的讲座中进行讲解,这暗示了讲义内容的重要性以及与课程进度的衔接。
💡 [数值示例]
这个引言部分没有技术性内容,因此不涉及具体数值示例。我们可以举一个学习方法上的例子:假设课堂上老师说“两个多项式的和仍然是多项式”,但没有证明。这份讲义就会详细写出证明过程,比如设 $f(n)=O(n^a)$,$g(n)=O(n^b)$,那么 $f(n)+g(n) \le c_1 n^a + c_2 n^b \le (c_1+c_2) n^{\max(a,b)}$,所以 $f(n)+g(n) = O(n^{\max(a,b)})$,因此它也是多项式。学生通过自己推导这个过程,能更好地理解多项式的封闭性。
⚠️ [易错点]
- 误解学习范围:一个常见的易错点是学生可能会误以为这份补充讲义里的所有内容都是必考的。作者明确指出“你只负责课堂上所教的内容”,所以学生应以课堂笔记和要求为准,将此讲义视为拓展和加深理解的工具。
- 忽视练习价值:学生可能因为内容不强制要求而完全跳过。但作者建议将其作为练习,这对于培养理论推导能力至关重要。
📝 [总结]
本段是讲义的开篇引言,阐明了讲义的核心价值——为课堂上省略的证明提供详细推导。它定位了讲义的角色(补充学习材料),并给出了学习建议(主动练习证明),同时预告了内容与后续课程的关联。
🎯 [存在目的]
本段的目的是设定读者的预期,明确讲义的性质、范围和学习方法。它帮助学生理解为什么需要这份材料,以及如何最有效地利用它来辅助学习,避免学生因内容看似超出课堂范围而感到困惑或直接忽略。
🧠 [直觉心智模型]
可以将这份讲义看作是电影的“导演剪辑版”或附带的“幕后制作花絮”。课堂教学是公映版,为了流畅性和时间控制,会剪掉一些推导细节。而这份讲义则把所有被剪掉的、解释“为什么”的精彩细节都完整地呈现了出来,让真正感兴趣的观众能看到全貌。
💭 [直观想象]
想象你在学习一本武功秘籍。课堂上师傅只教了你一些招式(比如“飞龙在天”),告诉你它们很厉害。而这份讲义就像是秘籍中不轻易示人的内功心法部分,它详细解释了每一招每一式背后的运气法门、经络走向(即数学证明)。虽然只学招式也能用,但理解了内功心法,你才能真正融会贯通,甚至创造出新的招式。
62 时间复杂性
📜 [原文18]
1 时间复杂性
假设我们有一个图灵机 $M$ 判决一个语言 $L$。给定某个输入 $x$,我们想了解 $M$ 接受或拒绝 $x$ 需要多长时间。为此,我们考虑 $M$ 的运行时间作为 $x$ 长度的函数(将 $x$ 作为输入需要多少个字母表符号)。
📖 [逐步解释]
这一段引入了计算复杂性理论的核心概念之一:时间复杂性。
- 基本设定:我们从一个理论计算模型——图灵机 $M$ 开始。这个图灵机被设计用来“判决”一个语言 $L$。“判决”(decide)一个语言意味着对于任何给定的字符串,图灵机总能在有限的时间内停机,并明确地回答“是”(接受)或“否”(拒绝)。“是”表示该字符串属于语言 $L$,“否”则表示不属于。
- 核心问题:我们关心的不仅仅是图灵机 能否 解决问题,更关心它解决问题需要 多长时间。这个“多长时间”就是时间复杂性要研究的对象。
- 衡量标准:时间不是用秒或分钟来衡量的,因为那取决于具体的计算机硬件。在理论计算机科学中,时间是用图灵机执行的“步骤”数来衡量的。一个步骤通常指一次状态转换。
- 输入的角色:解决一个问题的耗时通常不固定,它依赖于问题的“规模”。对于图灵机来说,问题的规模就是输入字符串 $x$ 的长度,记为 $|x|$。输入越长,通常处理时间也越长。
- 函数关系:因此,我们不讨论某个具体输入的运行时间,而是将运行时间看作是输入长度 $n$ 的一个函数。这个函数描述了运行时间随着输入长度增长的变化趋势。
💡 [数值示例]
- 语言 L:所有由偶数个‘1’组成的字符串的语言。例如,"11", "1111", "" (空字符串) 属于 $L$;"1", "111" 不属于 $L$。
- 图灵机 M:一个简单的图灵机,从左到右扫描磁带上的输入 $x$。它用一个状态来记录目前为止读到的‘1’是奇数个还是偶数个。
- 输入 x:假设输入是 "1111"。它的长度 $|x| = 4$。
- 运行时间:$M$ 会从左到右读取这4个'1',每读取一个就改变一次状态,读完后停机。可能需要4步读取,加上起始和结束的一些步骤,比如总共6步。如果输入是 "111111",长度为6,可能需要8步。我们可以看到,运行时间大致和输入长度成正比。
⚠️ [易错点]
- 时间单位的混淆:初学者容易将理论上的“步骤数”与物理世界的“秒”混淆。时间复杂性是独立于硬件的抽象度量。
- 忽略对最坏情况的考虑:对于同一个长度的输入,算法的运行时间也可能不同。例如,搜索算法在数组的第一个位置就找到目标,和在最后一个位置才找到,耗时完全不同。时间复杂性通常(如下文定义)关心的是“最坏情况”下的运行时间。
📝 [总结]
本段为时间复杂性的讨论设定了舞台。它明确指出,我们将使用图灵机作为计算模型,通过计算其在处理不同长度的输入时所需执行的步骤数,来量化算法的效率。这建立了一个从输入长度到运行时间的函数关系,是后续所有复杂性分析的基础。
🎯 [存在目的]
本段的目的是从最基本的计算模型(图灵机)和问题(判决语言)出发,引出衡量计算资源消耗(特别是时间)的必要性。它将一个模糊的“快慢”问题,转化为一个精确的数学问题——分析一个函数,即运行时间函数。
🧠 [直觉心智模型]
可以把图灵机想象成一个非常笨拙但很守规则的工人,他有一张长长的纸带(磁带)和一本操作手册(转换函数)。输入是写在纸带上的初始任务。时间复杂性就像是在问:对于一个长度为 $n$ 的任务清单,这个工人最多需要翻阅多少次操作手册、涂改多少次纸带才能完成任务?
💭 [直观想象]
想象你在做一道很长的数学题,比如计算 100 个数字的连加。题目的长度就是这 100 个数字。你每做一次加法,就算是一个“步骤”。完成这道题总共需要 99 次加法。如果题目变成 200 个数字,就需要 199 次加法。你的“运行时间”大约是 $n-1$ 步,其中 $n$ 是数字的个数(输入长度)。时间复杂性就是研究这种“步骤数”和“题目长度”之间的关系。
2.1 定义 1:运行时间
📜 [原文19]
定义 1. 设 $M$ 是一个在每个输入上都停机的图灵机。$M$ 的运行时间记作 $t(n)$,是 $M$ 在任何长度为 $n$ 的输入上所需的最大步骤数。也就是说:
$$
t(n):=\max _{x \in \Sigma^{*},|x|=n} \text { number of steps } M \text { takes before it halts on } x
$$
在上面,输入 $x$ 上 $M$ 的步骤数是 $M$ 停机前所经历的转换数。
📖 [逐步解释]
这个定义精确地形式化了上一段引入的“运行时间函数”的概念。
- 前提条件:这个定义只适用于“在每个输入上都停机”的图灵机。这种图灵机也叫判决器 (decider)。如果一个图灵机在某些输入上会无限循环,那么它的运行时间就是无限,这个定义就没意义了。
- 运行时间的记号:运行时间函数通常用 $t(n)$ 表示,其中 $n$ 是输入长度。
- 核心思想:最坏情况分析 (Worst-Case Analysis):定义中的 max 是关键。对于所有长度为 $n$ 的输入字符串,它们的实际运行步数可能各不相同。$t(n)$ 取的是这些步数中的“最大值”。这意味着我们总是在为最坏的情况做准备。无论你给我哪个长度为 $n$ 的输入,我都能保证在 $t(n)$ 步之内解决它。
- 形式化定义:
- $t(n) :=$ 这是定义 $t(n)$ 的符号。
- $\max_{x \in \Sigma^{*},|x|=n}$:这部分是说,我们要考虑所有可能的、长度为 $n$ 的输入字符串 $x$。$\Sigma^{*}$ 表示由字母表 $\Sigma$ 中所有符号组成的所有有限长度字符串的集合。
- number of steps M takes before it halts on x:这是对单个输入 $x$ 的运行步数。
- 步骤的定义:一段补充说明,明确了“步骤数”就是图灵机从开始到停机所执行的状态转换的总次数。
💡 [数值示例]
- 问题:在一个包含 $n$ 个整数的无序数组中,查找是否存在数字 5。
- 算法(图灵机 M):从数组的第一个元素开始,逐个检查是否为 5。如果找到,立即停机并回答“是”。如果检查完所有元素都没有找到,停机并回答“否”。
- 输入长度 n = 4:数组有 4 个元素。
- 情况 1 (最好情况):输入是 {5, 8, 2, 10}。算法第一步就找到了 5,运行 1 步。
- 情况 2:输入是 {8, 5, 2, 10}。算法第二步找到 5,运行 2 步。
- 情况 3 (最坏情况):输入是 {8, 2, 10, 5} 或者 {8, 2, 10, 9}。
- 在 {8, 2, 10, 5} 中,算法需要检查 4 次才找到。
- 在 {8, 2, 10, 9} 中,算法需要检查全部 4 个元素才确定 5 不存在。
这两种情况都代表了最坏的情况,运行 4 步。
- 计算 t(4):在所有长度为 4 的输入中,最多的运行步数是 4。所以 $t(4) = 4$。
- 推广:对于长度为 $n$ 的输入,最坏情况下需要检查所有 $n$ 个元素。所以 $t(n) = n$。
⚠️ [易错点]
- 平均情况 vs. 最坏情况:这个定义只关心最坏情况。在实际应用中,有时平均情况复杂性也很有用,但理论分析通常从最坏情况开始,因为它提供了一个性能的上限保证。
- 对所有输入的误解:$t(n)$ 不是所有长度为 $n$ 的输入的运行时间之和或平均值,而是在这些输入中挑选出的那个最长的运行时间。
📝 [总结]
定义1给出了时间复杂性分析的基石——最坏情况运行时间 $t(n)$。它是一个关于输入长度 $n$ 的函数,其值为在所有长度为 $n$ 的输入中,算法所需的最大执行步骤数。这个定义为我们提供了一个衡量算法效率的、有保证的、与具体机器无关的标尺。
🎯 [存在目的]
这个定义的目的是将算法效率的分析从对具体、单个输入的讨论,提升到对一类(相同长度)输入的一般性、系统性讨论。通过关注“最坏情况”,它为算法提供了一个可靠的性能保证,这在设计关键系统时至关重要。
🧠 [直觉心智模型]
$t(n)$ 就像是一个“承诺”。一个算法向你承诺:“无论你给我一个多难的、规模为 $n$ 的任务,我向你保证,我完成它所需的时间绝不会超过 $t(n)$。” 这个承诺让你能预估最糟糕的情况下需要等待多久。
💭 [直观想象]
想象你在快递公司分拣包裹。$n$ 是今天需要分拣的包裹总数。
- 最好情况:所有包裹都去同一个地址,你把它们放进一个箱子就行了,很快。
- 最坏情况:$n$ 个包裹,每个都去一个不同的、偏远的地址,你需要一个一个地查找地址、贴标签、分区。这是最费时的。
$t(n)$ 就是这个最坏情况所花费的时间。公司老板用 $t(n)$ 来安排人手和规划下班时间,因为他必须保证即使在最忙乱的一天,工作也能完成。
2.2 大 O 符号
📜 [原文20]
精确计算图灵机在输入上所需的步骤数相当麻烦,因此我们使用大 O 符号来隐藏常数。回想一下,对于两个函数 $f, g: \mathbb{N} \rightarrow \mathbb{R}^{+}$,我们说 $f(n)=O(g(n))$,如果存在常数 $c>0$ 和 $n_{0} \in \mathbb{N}$,使得对于所有 $n \geq n_{0}$:
$$
f(n) \leq c \cdot g(n)
$$
📖 [逐步解释]
这一段解释了为什么我们需要以及如何使用大O符号 (Big O Notation)。
- 动机:精确计算 $t(n)$,比如 $t(n) = 5n^2 + 10n + 3$,是非常繁琐且不必要的。我们更关心的是当输入规模 $n$ 变得非常大时,运行时间的“增长趋势”或“数量级”。是像 $n$ (线性),还是像 $n^2$ (平方),或是像 $2^n$ (指数)?
- 大 O 的作用:大O符号提供了一种标准的方式来忽略那些在 $n$ 很大时变得不重要的部分(低阶项和常数系数),而只关注起主导作用的部分。它描述了函数增长的“上界”。
- 形式化定义:$f(n) = O(g(n))$ 这句话的含义是“函数 $f(n)$ 的增长速度不会比函数 $g(n)$ 快太多”。
- 前提:有两个从自然数($\mathbb{N}$)映射到正实数($\mathbb{R}^{+}$)的函数 $f$ 和 $g$。在我们的场景中,$f(n)$ 就是运行时间 $t(n)$。
- 条件:要满足 $f(n) = O(g(n))$,必须能找到两个常数:一个正的比例因子 $c$ 和一个起始点 $n_0$。
- 核心不等式:对于所有大于等于这个起始点 $n_0$ 的 $n$,不等式 $f(n) \leq c \cdot g(n)$ 必须恒成立。这意味着,从 $n_0$ 开始,$f(n)$ 的曲线总是在 $c \cdot g(n)$ 曲线的下方(或与之重合)。
💡 [数值示例]
示例 1: 证明 $f(n) = 3n^2 + 10n + 4$ 是 $O(n^2)$。
- 目标: 我们需要找到 $c$ 和 $n_0$ 使得对于所有 $n \geq n_0$,$3n^2 + 10n + 4 \leq c \cdot n^2$。
- 推导:
- 我们希望右边只有 $n^2$ 项,所以尝试把左边的所有项都用 $n^2$ 来表示。
- 当 $n \geq 1$ 时,$10n \leq 10n^2$。
- 当 $n \geq 1$ 时,$4 \leq 4n^2$。
- 所以,$3n^2 + 10n + 4 \leq 3n^2 + 10n^2 + 4n^2 = 17n^2$。
- 选择 c 和 n0: 在这个推导中,我们让 $c=17$。这个不等式成立的前提是 $n \geq 1$。所以我们可以选择 $n_0 = 1$。
- 结论: 我们找到了 $c=17$ 和 $n_0=1$,使得对于所有 $n \geq 1$,$f(n) \leq 17n^2$。因此,根据定义,$f(n) = O(n^2)$。
示例 2: 证明 $f(n) = 100n + 50$ 是 $O(n)$。
- 目标: 找到 $c, n_0$ 使得 $100n + 50 \leq c \cdot n$ for all $n \geq n_0$。
- 推导:
- 当 $n \geq 1$ 时,$50 \leq 50n$。
- 所以,$100n + 50 \leq 100n + 50n = 150n$。
- 选择 c 和 n0: 我们选择 $c=150$ 和 $n_0=1$。
- 结论: $f(n) = O(n)$。
⚠️ [易错点]
- “等于”的误解:$f(n) = O(g(n))$ 中的等号不是真正的等于,更准确的理解是 $f(n) \in O(g(n))$(f 属于 O(g) 这个集合)。它描述的是一种单向的“小于等于”关系。例如,$n = O(n^2)$ 是正确的,但 $n^2 = O(n)$ 是错误的。
- 追求最紧的上界:虽然 $3n^2 + 10n + 4 = O(n^3)$ 甚至 $O(2^n)$ 在技术上都是正确的,但这么说没有意义。我们总是希望能找到一个尽可能“紧”的上界,即 $O(n^2)$。
- c 和 n0 不唯一:能使定义成立的 $c$ 和 $n_0$ 有无数组。例如在示例1中,我们也可以选择 $c=4$ 和 $n_0=11$。因为当 $n \ge 11$ 时,$10n+4 \le n^2$ 成立,所以 $3n^2+10n+4 \le 3n^2+n^2 = 4n^2$。只要能找到一对即可。
📝 [总结]
大O符号是分析算法效率的通用语言。它通过忽略低阶项和常数因子,让我们聚焦于算法运行时间随输入规模增长的核心趋势。$f(n) = O(g(n))$ 意味着当 $n$ 足够大时,$f(n)$ 的增长速度由一个更简单的函数 $g(n)$ 给出了一个上界。
🎯 [存在目的]
引入大O符号的目的是为了简化和标准化算法复杂性的讨论。如果没有它,我们将陷入对 $5n^2+3$ 和 $6n^2-10n$ 孰优孰劣的无尽争论中。大O符号让我们能够清晰地断言:它们都是 $O(n^2)$,属于同一个复杂性级别。这使得比较不同算法的根本效率成为可能。
🧠 [直觉心智模型]
大O符号就像是给函数贴上“增长速度”的标签。$O(n)$ 是“线性增长”标签,$O(n^2)$ 是“平方增长”标签,$O(\log n)$ 是“对数增长”标签。我们给一个算法的运行时间函数贴上最合适的、最简单的标签,以便快速了解其性能特征。
💭 [直观想象]
想象有两匹马赛跑。马A的速度函数是 $f(n)=n^2$ 米/秒,马B的速度函数是 $g(n)=100n$ 米/秒。在开始的几秒($n<100$),马B更快。但是,一旦比赛距离足够长($n \ge 100$),马A就会远远超过马B,并且它们之间的差距会越来越大。大O分析就像是在说:“从长远来看,马A的潜力(增长率)是‘平方级’的,而马B只是‘线性级’的。所以对于长跑比赛,马A最终会是赢家。” 它关注的是最终的、起决定性作用的增长趋势。
2.3 定义 2:TIME(t(n)) 复杂性类
📜 [原文21]
定义 2 ($\operatorname{Time}(t(n))$). 设 $t: \mathbb{N} \rightarrow \mathbb{N}$,我们定义复杂性类 $\operatorname{Time}(t(n))$ 为以下语言集合:
$$
\operatorname{Time}(t(n)):=\{L \mid L \text { is a language decided by an } O(t(n)) \text { time Turing machine }\}
$$
展开定义,这意味着存在某个常数 $c$ 和 $n_{0}$,使得对于所有 $n \geq n_{0}$,$M$ 在所有长度为 $n$ 的输入上在 $\leq c * t(n)$ 的时间内停机,并且 $M$ 判决 $L$。
我们注意到 $\operatorname{Time}(t(n))$ 的定义对我们所考虑的计算模型敏感:单带图灵机与多带图灵机、算法(如 Python)等。但是我们将关注的 P 的定义不依赖于这些。因此你可以安全地忽略这些问题,并使用(确定性)伪代码来描述算法。
📖 [逐步解释]
这个定义利用大O符号将具有相似运行时间的语言(或问题)归为一类,这个“类”就是复杂性类。
- 什么是复杂性类:复杂性类是一个“装语言的篮子”。所有能被某种特定资源限制(比如时间或空间)的计算模型解决的语言,都被放进同一个篮子里。
- TIME(t(n)) 的定义:这个特定的篮子叫做 $\operatorname{Time}(t(n))$。它装的是所有那些“可以被一个运行时间为 $O(t(n))$ 的图灵机判决”的语言。
- 展开定义:一个语言 $L$ 属于 $\operatorname{Time}(t(n))$,当且仅当:
- 存在一个图灵机 $M$ 能够判决 $L$。
- 这个图灵机 $M$ 的运行时间是 $O(t(n))$。根据大O定义,这意味着存在常数 $c$ 和 $n_0$,使得对于所有长度 $n \geq n_0$ 的输入,$M$ 的最坏情况运行步数不超过 $c \cdot t(n)$。
- 对计算模型的敏感性:这是一个非常重要的理论细节。$\operatorname{Time}(t(n))$ 的确切内容取决于你用的是哪种图灵机。例如,一个问题用“多带图灵机”可能在 $O(n)$ 时间解决,但用“单带图灵机”可能需要 $O(n^2)$ 时间。这意味着同一个语言 $L$ 可能属于多带图灵机的 $\operatorname{Time}(n)$,但属于单带图灵机的 $\operatorname{Time}(n^2)$。
- P 的优越性:作者提前剧透,后续将要定义的复杂性类 P(多项式时间)有一个非常好的特性,就是它对这些常见的确定性计算模型不敏感。一个问题在多带图灵机上是多项式时间可解的,那么在单带图灵机上也是(尽管多项式的次数可能会增加)。这使得我们可以忽略底层模型的细节,直接用更方便的伪代码来讨论算法,只要能证明它是多项式时间的就行。
💡 [数值示例]
- t(n) = n:$\operatorname{Time}(n)$ 就是所有能被线性时间图灵机判决的语言的集合。例如,在数组中找最大值的问题对应的语言就属于 $\operatorname{Time}(n)$。
- t(n) = n^2:$\operatorname{Time}(n^2)$ 就是所有能被平方时间图灵机判决的语言的集合。例如,一个朴素的比较排序算法(如冒泡排序)所解决的排序问题,就属于这个类别(在特定模型下)。
- 关系: 如果一个语言属于 $\operatorname{Time}(n)$,那么它肯定也属于 $\operatorname{Time}(n^2)$,因为一个 $O(n)$ 的算法也满足 $O(n^2)$ 的定义。所以 $\operatorname{Time}(n) \subseteq \operatorname{Time}(n^2)$。
⚠️ [易错点]
- 类与问题的混淆:$\operatorname{Time}(t(n))$ 是一个集合(一个类),里面装着很多很多语言(问题)。而 $O(t(n))$ 是描述单个算法运行时间的一个函数上界。
- 忽略模型依赖性:在进行精细的复杂性分析时(例如,区分 $\operatorname{Time}(n)$ 和 $\operatorname{Time}(n \log n)$),计算模型至关重要。只有在讨论像 P 这样“健壮”的复杂性类时,我们才能在一定程度上忽略模型差异。
📝 [总结]
$\operatorname{Time}(t(n))$ 是一个复杂性类,它将所有能被运行时间为 $O(t(n))$ 的图灵机解决的问题(语言)收集在一起。这个定义为我们提供了一种根据计算效率对问题进行分类的方法。同时,本段也指出了这种分类方式可能对底层计算模型敏感,并预示了 P 类的重要性,因为它克服了这个问题。
🎯 [存在目的]
这个定义的目的是从对单个问题效率的分析,过渡到对一整类问题效率的分析。通过创建复杂性类,理论计算机科学家可以研究不同类别问题之间的关系(例如,$\operatorname{Time}(n)$ 和 $\operatorname{Time}(n^2)$ 是否真的不同?),从而构建起整个计算复杂性的大厦。
🧠 [直觉心智模型]
复杂性类就像是图书馆里的书架。
- $\operatorname{Time}(n)$ 书架上放的是“读一遍就能懂”的书(线性时间问题)。
- $\operatorname{Time}(n^2)$ 书架上放的是“需要把每一页和其他所有页都比较一遍才能懂”的书(平方时间问题)。
- $\operatorname{Time}(2^n)$ 书架上放的是“需要尝试所有可能的解读组合才能懂”的书(指数时间问题)。
一个语言属于哪个复杂性类,就好像一本书应该被放在哪个书架上。
💭 [直观想象]
想象一个城市里的交通工具系统。
- $\operatorname{Time}(n)$ 类比于“步行可达”的问题。问题的规模 $n$ 就像距离,时间花费和距离成正比。
- $\operatorname{Time}(\log n)$ 类比于“坐地铁可达”的问题。即使距离 $n$ 增加很多,花费的时间(站数)增长得很慢。
- $\operatorname{Time}(n^k)$ 类比于“开车可达”的问题。在城市里,这通常是可接受的。
- $\operatorname{Time}(2^n)$ 类比于“只能靠运气找到一条不堵的小路”才能到达的问题。随着距离 $n$ 的增加,找到这条路的时间呈爆炸性增长,非常不靠谱。
复杂性类就是给城市里的每个地点(问题)贴上一个标签:“步行区”、“地铁区”、“驾车区”或“禁区”。
2.4 多项式
📜 [原文22]
定义 3. 我们说 $t: \mathbb{N} \rightarrow \mathbb{N}$ 是多项式的,如果存在某个常数 $k$ 使得 $f(n)=O\left(n^{k}\right)$。
示例:$n^{5}+1000, \log (n)$ 都是多项式的。$2^{n}+n^{3}$ 不是多项式的。
好消息:如果 $f: \mathbb{N} \rightarrow \mathbb{N}$ 和 $g: \mathbb{N} \rightarrow \mathbb{N}$ 都是多项式的,那么以下也是多项式的:
- $f+g$,其中 $(f+g)(x)=f(x)+g(x)$。
- $f * g$,其中 $(f * g)(x)=f(x)*g(x)$。
- $f \circ g$,其中 $(f \circ g)(x)=f(g(x))$。
你可以不加证明地使用这一点,尽管证明起来并不难,我们将在定理 6 的证明中给出最后一个的详细证明。
📖 [逐步解释]
这段内容定义了什么是“多项式”时间,并列举了它的关键性质。这是区分“高效”算法和“低效”算法的核心分界线。
- 多项式的定义:一个函数 $f(n)$ 被认为是多项式的,如果它的增长速度被某个 $n^k$ 函数所限制。换句话说,它的大O表示是 $O(n^k)$,其中 $k$ 是一个固定的常数,与 $n$ 无关。
- 示例分析:
- $n^5 + 1000$:是 $O(n^5)$,因为当 $n$ 很大时,$n^5$ 项占主导。这里的 $k=5$,所以它是多项式的。
- $\log(n)$:对数函数的增长比任何 $n^k$(只要 $k>0$)都要慢。例如,$\log(n) = O(n^1)$。所以它也是多项式的。这是一个需要注意的点,比多项式增长慢的函数也算在多项式时间的范畴里。
- $2^n + n^3$:这是一个指数函数。无论你选择多大的常数 $k$,当 $n$ 足够大时,$2^n$ 的增长最终都会超过 $n^k$。因此,$2^n$ 不是 $O(n^k)$,这个函数不是多项式的。
- 多项式的封闭性 (Closure Properties):这部分是“好消息”,因为它说明了多项式时间算法的良好组合特性。
- 和 (Addition):两个多项式时间算法顺序执行,总时间是两者之和,结果仍然是多项式时间。
- 积 (Multiplication):一个多项式时间的循环,其内部再执行一个多项式时间的操作,总时间是两者之积,结果仍然是多项式时间。
- 复合 (Composition):一个多项式时间的算法,其输入是另一个多项式时间算法的输出,总时间是函数的复合,结果仍然是多项式时间。这在归约(后面会讲)中非常重要。
- 使用许可:作者允许我们直接使用这些封闭性质,无需每次都重新证明,这大大简化了后续的复杂性分析。
💡 [数值示例]
- 多项式定义:
- $f(n) = 10n^3 + 5n^2 + 100$ 是 $O(n^3)$,所以是多项式的 ($k=3$)。
- $f(n) = n \log n$ 是 $O(n^2)$,所以是多项式的 ($k=2$)。
- 非多项式定义:
- $f(n) = n!$ (阶乘),增长比任何指数函数都快,不是多项式的。
- 封闭性:
- 和: 设 $f(n) = n^2$ (算法A),$g(n) = n^3$ (算法B)。顺序执行两个算法,总时间是 $n^2+n^3$。这是 $O(n^3)$,仍然是多项式。
- 积: 算法A循环 $n^2$ 次,每次循环内部执行算法B ($n^3$ 步)。总时间是 $n^2 \cdot n^3 = n^5$。这是 $O(n^5)$,仍然是多项式。
- 复合: 算法B的输入大小是算法A的输出。假设算法A输出大小为 $m=n^2$。算法B在输入 $m$ 上的时间是 $m^3 = (n^2)^3 = n^6$。这是 $O(n^6)$,仍然是多项式。
⚠️ [易错点]
- k 必须是常数:在 $O(n^k)$ 中,$k$ 必须是一个不依赖于 $n$ 的常数。像 $O(n^{\log n})$ 这样的函数,虽然看起来有点像多项式,但它的指数不是常数,所以它不是多项式的。它被称为“准多项式时间”。
- 对数函数是多项式时间:这是一个常见的混淆点。因为 $\log n$ 增长得非常慢,它被任何正次方的多项式(如 $n^{0.001}$)作为上界,所以它属于多项式时间的范畴。更准确地说,多项式时间意味着“时间复杂度不差于多项式”。
📝 [总结]
本段定义了“多项式增长”,这是衡量算法是否“高效”或“可行”(tractable)的理论标准。任何运行时间可以被 $n^k$($k$为常数)限制的算法都被认为是多项式时间算法。此外,本段还强调了多项式时间算法在组合(顺序、嵌套、复合)下的稳定性,即多项式时间的“积木”搭建起来的仍然是多项式时间的。
🎯 [存在目的]
本段的目的是建立一条明确的、具有良好数学性质的界线,来区分“快”算法和“慢”算法。这条线就是“多项式时间”。这个定义对于后续引入复杂性类 P 至关重要,并且“多项式的封闭性”是多项式时间归约理论的基石。
🧠 [直觉心智模型]
多项式时间就像是“可预测的、可控的”增长。如果你告诉一个工程师,他的算法是多项式时间的,他会感到安心,因为他知道即使问题规模加倍,运行时间也只是增加一个固定的倍数(比如 $2^k$ 倍),而不是失控地爆炸。而非多项式时间(如指数时间)则是“失控的、不可用的”增长,问题规模稍微增大一点点,就可能需要宇宙毁灭那么长的时间来计算。
💭 [直观想象]
想象你在盖房子。
- 多项式时间 $O(n^2)$:盖一个 $n \times n$ 平米的地基。如果 $n$ 翻倍,面积变成原来的 4 倍,工作量是可预期的。这是可行的。
- 指数时间 $O(2^n)$:假设要盖一座塔,每加盖一层($n \to n+1$),之前的所有楼层都要重新加固一遍,导致总工作量翻倍。当塔盖到一定高度时,再加盖一层就会变得几乎不可能。这就是不可行的。
“多项式”意味着任务的复杂度增长是“有理性的”,而“指数”则意味着增长是“疯狂的”。
73 P 和 NP
📜 [原文23]
2 P 和 NP
📖 [逐步解释]
这是讲义的核心章节标题,预示着本章将介绍计算机科学领域最核心、最著名的两个复杂性类:P 和 NP。这个标题将整个讨论从基础定义(时间复杂性,大O)提升到了对重大理论概念的探讨。P vs NP 问题是计算机科学中最重要、悬而未决的百万美元大奖问题。本章将分别定义它们,并为后续讨论它们之间的关系打下基础。
📝 [总结]
这是一个章节标题,标志着讲义进入了中心议题:定义和解释复杂性类 P 和 NP。
🎯 [存在目的]
作为章节标题,它的作用是组织文章结构,并告知读者即将进入一个全新的、更高级别的主题。
3.1 P
📜 [原文24]
21 P
📖 [逐步解释]
这是“P 和 NP”章节下的一个小节标题,表示将首先开始详细定义和讨论复杂性类 P。
🎯 [存在目的]
结构化内容,让读者清晰地知道,下面的内容将专门围绕 P 展开。
31.1 定义 4: P
📜 [原文25]
定义 4 (P).
$\mathrm{P}=\{L \mid L \text { is a language and there exists a decider } M \text { for } L \text { running in polynomial time } \}$ 等价地,
$$
\mathrm{P}:=\bigcup_{k \in N} \operatorname{Time}\left(n^{k}\right)
$$
📖 [逐步解释]
这个定义正式引入了复杂性类 P。
- 第一种定义(描述性):
- $\mathrm{P}$ 是一个语言的集合(一个复杂性类)。
- 一个语言 $L$ 属于 P,条件是:存在一个确定性图灵机(decider) $M$ 能够判决 $L$,并且这个 $M$ 的运行时间是多项式时间。
- “多项式时间”的含义是,存在某个常数 $k$,使得 $M$ 的运行时间是 $O(n^k)$。
- 简而言之,P 包含了所有可以被确定性算法在多项式时间内“高效”解决的问题。
- 第二种定义(数学化):
- $\mathrm{P}$ 是所有 $\operatorname{Time}(n^k)$ 复杂性类的并集。
- $k$ 取遍所有自然数 $N=\{0, 1, 2, \dots\}$。
- $\operatorname{Time}(n^0)$, $\operatorname{Time}(n^1)$, $\operatorname{Time}(n^2)$, $\operatorname{Time}(n^3)$, ... 把所有这些复杂性类里的语言都倒进一个大篮子里,这个大篮子就是 P。
- 这个定义更精确地表达了“存在某个常数 $k$”的含义。只要你能找到任何一个 $k$ 使得问题能在 $O(n^k)$ 时间解决,那它就属于 P。
💡 [数值示例]
- 线性搜索:在一个无序数组中查找一个元素。最坏情况需要 $O(n)$ 时间。因为 $n=n^1$,所以 $k=1$。这个问题属于 $\operatorname{Time}(n^1)$,因此它属于 P。
- 冒泡排序:对一个数组进行排序。最坏情况需要 $O(n^2)$ 时间。$k=2$。这个问题属于 $\operatorname{Time}(n^2)$,因此它属于 P。
- 矩阵乘法:两个 $n \times n$ 矩阵相乘的标准算法需要 $O(n^3)$ 时间。$k=3$。这个问题属于 $\operatorname{Time}(n^3)$,因此它属于 P。
- 一个不被认为在 P 中的例子(目前):旅行商问题(TSP)。找到访问 $n$ 个城市并返回起点的最短路径。已知的最优确定性算法都需要指数时间,如 $O(n^2 2^n)$。目前没有找到多项式时间算法,所以 TSP 通常被认为不属于 P。
⚠️ [易错点]
- P 不等于“实用”:虽然 P 被理论上定义为“高效”的,但一个 $O(n^{100})$ 的算法在实际中是完全不可用的。然而,根据定义,它仍然属于 P。幸运的是,大多数现实中遇到的 P 类问题的多项式次数都很低(如 2, 3)。这被称为“多项式时间的经验性假设”。
- 确定性是关键:P 的定义严格要求是确定性图灵机 (decider)。每一步的选择都是唯一确定的。这与后面 NP 的非确定性形成对比。
📝 [总结]
复杂性类 P 是所有能够被一个确定性算法在多项式时间内解决的判定问题的集合。它被广泛认为是理论上“高效可解”问题的边界。其数学定义是所有 $\operatorname{Time}(n^k)$ 类的并集。
🎯 [存在目的]
定义 P 的目的是为了给“简单问题”或“易解问题”一个形式化的、健壮的分类。这个类构成了我们计算能力的一个基准,也是复杂性理论中进行比较和归类的出发点。
🧠 [直觉心智模型]
P 类问题就是那些“我们知道如何按部就班、在合理时间内解决”的问题。就像是有一本详细的菜谱,只要你严格按照步骤做,即使食材再多(输入规模 $n$ 变大),你花的时间也只是按一个可控的方式(多项式)增加,最终总能做出菜来。
💭 [直观想象]
想象你在一个巨大的图书馆里找一本书。
- P 类查找方式:图书馆的书是按字母顺序排列的。你可以用二分查找法。每一步都排除掉一半的书。即使书的总数 $N$ 非常大,你需要的步数也只是 $\log N$。这是一个非常高效的、在 P 类中的方法。
- 另一个 P 类方式:假设你要检查所有书的标题是否包含“龙”字。你必须一本一本地翻阅,需要 $O(N)$ 时间。虽然慢,但也是多项式时间,所以也属于 P。
- 非 P 类方式:假设图书馆的书是完全混乱的,你要找出一个包含 10 本书的子集,它们的厚度加起来正好是 1 米。除了把所有可能的 10 本书的组合都试一遍之外,没有更好的办法。组合的数量是指数级的,这就不属于 P。
31.2 证明模板 1:展示 L 属于 P
📜 [原文26]
证明模板 1 (展示 $L \in \mathrm{P}$ ).
- 给出 $L$ 的确定性判决器的伪代码。
- 展示如果 $x \in L$,你的算法接受。
- 展示如果 $x \notin L$,你的算法拒绝。
- 展示 $M$ 在多项式时间内运行,即在 $O\left(n^{k}\right)$ 时间内,其中 $k$ 为某个常数。(无需具体证明 $k$ 是什么)。
我们将在第 4 节中使用此模板进行证明。
📖 [逐步解释]
这段提供了一个标准的、结构化的方法来证明一个问题(语言 $L$)是属于复杂性类 P 的。
- 步骤 1: 描述算法。首先,你必须设计一个具体的、一步一步的确定性算法(用伪代码描述就足够了)。这个算法的输入是问题的一个实例 $x$。
- 步骤 2: 证明完备性 (Completeness)。你必须证明,对于任何一个“是”的实例(即 $x \in L$),你的算法总能正确地输出“接受”。这保证了你的算法不会漏掉任何正确的解。
- 步骤 3: 证明可靠性 (Soundness)。你必须证明,对于任何一个“否”的实例(即 $x \notin L$),你的算法总能正确地输出“拒绝”。这保证了你的算法不会把不是解的误判为解。步骤2和3合起来证明了你的算法是“正确”的。
- 步骤 4: 证明效率。最后,也是最关键的一步,你必须分析你的算法的运行时间,并证明它是多项式时间的。也就是说,它的运行时间是 $O(n^k)$,其中 $n$ 是输入 $x$ 的长度,$k$ 是某个常数。这一步证明了你的算法是“高效”的。
同时满足这四点,就严格地证明了 $L \in \mathrm{P}$。
💡 [数值示例]
问题 L: PATH = $\{\langle G, s, t \rangle \mid G \text{ 是一个有向图,且存在从节点 } s \text{ 到节点 } t \text{ 的路径}\}$。证明 PATH $\in \mathrm{P}$。
- 伪代码: 使用广度优先搜索 (BFS)。
```
Algorithm BFS_PATH(G, s, t):
- 创建一个队列 Q,并将 s 放入 Q。
- 创建一个集合 visited,并将 s 放入 visited。
- while Q is not empty:
- u = Q.pop()
- if u == t: return "Accept"
- for each neighbor v of u:
- if v is not in visited:
- add v to visited
- add v to Q
- return "Reject"
```
- 完备性: 如果存在从 $s$ 到 $t$ 的路径,那么 BFS 保证能够找到它。因为 BFS 会层层探索所有从 $s$ 可达的节点,所以最终一定会到达 $t$ 并接受。
- 可靠性: 如果不存在从 $s$ 到 $t$ 的路径,那么 $t$ 永远不会被加入队列。算法最终会因为队列变空而终止,并返回“拒绝”。
- 效率: 假设图有 $V$ 个节点和 $E$ 条边,输入大小 $n$ 大致是 $V+E$ 的量级。
- 每个节点最多被入队和出队一次 ($O(V)$)。
- 每条边最多被检查一次 ($O(E)$)。
- BFS 的总运行时间是 $O(V+E)$。这是一个关于输入规模 $n$ 的线性时间算法,是多项式时间的($k=1$)。
- 结论: 满足所有四个条件,因此 PATH $\in \mathrm{P}$。
⚠️ [易错点]
- 忘记证明正确性: 很多人在证明一个问题属于 P 时,只给出了一个多项式时间算法,但忘记了严格证明这个算法对于所有情况都是正确的(完备性和可靠性)。
- 时间复杂度分析错误: 对算法的时间复杂度分析可能出错,比如错误地估计了循环次数。例如,对于图算法,需要清楚地说明复杂度是关于节点数 $V$ 还是边数 $E$ 的函数。
- 忽略输入表示: 算法的时间必须是输入规模 $n$ 的多项式。需要清楚输入的表示方式,例如,一个图是用邻接矩阵($n=V^2$)还是邻接表($n=V+E$)表示,这会影响复杂度的具体形式,但通常不影响它是否为多项式。
📝 [总结]
该模板为证明一个语言 $L$ 属于 P 类提供了一个清晰的四步路线图:1. 提出一个确定性算法,2. 证明它能解决所有“是”实例,3. 证明它能解决所有“否”实例,4. 证明它在多项式时间内完成。
🎯 [存在目的]
这个模板的目的是将一个抽象的证明任务具体化、流程化。它为学生提供了一个可以遵循的、不会遗漏关键点的框架,使得证明过程更加严谨和规范。
🧠 [直觉心智模型]
这个模板就像是申请一项专利的流程。
- 提交设计图纸(伪代码)。
- 证明你的发明能工作(完备性)。
- 证明你的发明不会产生副作用(可靠性)。
- 证明你的发明制造成本低廉且高效(多项式时间)。
只有全部满足,你的“高效解决问题”的专利($L \in \mathrm{P}$)才会被批准。
💭 [直观想象]
想象你要向法官证明被告“有能力在一天内从纽约走到波士顿”($L \in P$)。
- 提供路线方案(伪代码):先坐火车到纽黑文,再转巴士...
- 证明路线可行性(完备性):如果被告真的这样走,时刻表显示他确实能在一天内到达。
- 排除其他可能性(可靠性):如果他不按这个路线走,或者中途出现意外,我们算法会“拒绝”(这里类比不太恰当,但核心是算法的确定性)。
- 计算时间成本(多项式时间):整个行程的时间是坐火车时间+巴士时间,这是一个“合理”的时间,不是需要几百年。
法官审核了你的全部证据,最终裁定:被告确实具备这个能力。证明成功。
3.2 NP
📜 [原文27]
22 NP
非确定性图灵机 (NTM) 是一种允许进行非确定性转换的图灵机。也就是说,给定当前状态 $q$ 和图灵机磁头在不同磁带上看到的符号,可能存在多个可能的转换。例如,对于具有状态 $Q$ 和字母表 $\Sigma$ 的单带图灵机,转换函数现在是 $\delta: Q \times \Sigma \rightarrow \mathcal{P}(Q \times \Sigma \times\{L, R\})$ 的形式。
📖 [逐步解释]
这一段开始介绍复杂性类 NP,首先从它的计算模型——非确定性图灵机 (Nondeterministic Turing Machine, NTM)——开始。
- 与确定性的区别:确定性图灵机 (DTM) 在任何时刻,根据当前状态和读到的符号,下一步的动作是唯一确定的。而 NTM 在同样的情况下,可能有多种选择。
- “非确定性”的含义:它不是指随机,而是指“多分叉的可能性”。你可以把 NTM 的计算过程想象成一棵树,每个节点代表一个机器状态,每条树枝代表一个可能的转换。DTM 的计算是一条单一的路径,而 NTM 的计算是探索一整棵树。
- 转换函数的形式化:
- DTM 的转换函数 $\delta(q, a) = (q', a', d)$ 返回一个元组,表示下一个状态、要写入的符号和移动方向。
- NTM 的转换函数 $\delta(q, a)$ 返回一个集合,这个集合里包含多个可能的元组。例如 $\delta(q_1, 'a') = \{ (q_2, 'b', R), (q_3, 'a', L) \}$,意味着在 $q_1$ 状态读到 'a' 时,机器可以变成 $q_2$ 状态、写入 'b' 并右移,或者可以变成 $q_3$ 状态、写入 'a' 并左移。
- $\mathcal{P}(\dots)$ 表示幂集 (Power Set),即所有子集的集合。返回一个转换元组的集合,就意味着可以选择其中任何一个来执行。
💡 [数值示例]
问题:在一个图中寻找是否存在一个长度为 2 的从节点 A 到节点 D 的路径。
假设图中有边 A->B, A->C, B->D, C->E。
- 从 A 出发,选择第一条边 A->B。
- 从 B 出发,选择第一条边 B->D。
- 路径是 A->B->D,长度为 2,终点是 D。找到路径,停机并接受。
- 在节点 A,非确定性地猜测下一步要去哪里。它有两个选择:B 或 C。
- 计算路径 1: 猜测走向 B。现在在 B。
- 从 B 出发,下一步只能去 D。路径 A->B->D。终点是 D,长度为 2。这条计算路径接受。
- 计算路径 2: (回到第1步) 猜测走向 C。现在在 C。
- 从 C 出发,下一步只能去 E。路径 A->C->E。终点不是 D。这条计算路径拒绝。
NTM 的计算产生了两个分支,因为其中一个分支(路径1)接受了,所以整个 NTM 就接受了这个输入。
⚠️ [易错点]
- 非确定性不等于随机:NTM 不是随机选择一个分支走下去。它被认为是“并行”地探索所有分支,或者说它具有“神奇的猜测能力”,总能做出“正确”的猜测(如果存在正确路径的话)。
- 非确定性不是量子计算:虽然都有并行性的味道,但它们的数学模型和计算能力有本质区别。
📝 [总结]
本段引入了非确定性图灵机 (NTM) 作为定义 NP 的基础。与确定性图灵机每一步动作唯一不同,NTM 在每一步可能有多个选择,其计算过程可以看作是同时探索所有可能的计算路径。
🎯 [存在目的]
为了定义复杂性类 NP,必须首先定义其底层的计算模型 NTM。NTM 的“多选择”特性是理解 NP“猜测并验证”本质的关键。
🧠 [直觉心智模型]
NTM 就像一个拥有无数个“分身”的侦探。在案发现场的每个岔路口,他不是只选择一条路走,而是派出分身去探索每一条路。只要有任何一个分身最终找到了凶手(找到了一个接受路径),整个侦探团队(NTM)就立刻宣布破案。
💭 [直观想象]
你正在走一个迷宫。
- 确定性图灵机:你只有一个人,遵循“右手扶墙”之类的固定策略。你可能会走很多冤枉路,但最终能走遍整个迷宫。
- 非确定性图灵机:在每个岔路口,你都可以分裂成多个你,每个你去探索一条路。只要其中任何一个你找到了出口,你就瞬间知道了出口在哪里。这种“分裂”的能力就是非确定性。
32.1 NTM 的判决和运行时间
📜 [原文28]
回想一下,我们说非确定性图灵机 $M$ 判决语言 $L$,如果:
- $M$ 总是对输入 $x$ 停机,无论它做了什么转换选择。
- 如果 $x \in L$,存在一些转换选择使得 $M$ 接受 $x$。
- 如果 $x \notin L$,对于所有转换选择,$M$ 拒绝 $x$。
我们通常认为这样的图灵机能够“猜测”,然后检查这个猜测是否是解。如果存在任何幸运的猜测,则该字符串属于该语言。
定义 5. 设 $M$ 是一个在每个输入上都停机的非确定性图灵机。我们说 $M$ 的运行时间为 $t(n)$,如果对于任何长度为 $n$ 的输入 $x$, $M$ 在 $x$ 上的所有可能计算在最多 $t(n)$ 步骤内终止。
特别是,对于 $M$ 在时间 $t(n)$ 内运行,在最多 $t(n)$ 次转换后,$M$ 必须在任何长度为 $n$ 的输入上停机。这与我们在每一步非确定性选择的转换无关。因此,NTM 的运行时间概念是所有可能的计算路径中的最坏情况概念。接受概念是最好的情况:如果存在某个猜测(计算路径)接受,则输入在语言中。
📖 [逐步解释]
这部分定义了 NTM 如何“判决”一个语言以及如何衡量其运行时间,这两点都与 DTM 有着微妙但关键的区别。
NTM 的判决规则:
- 停机要求:首先,NTM 必须是一个判决器,意味着在任何输入上,沿着任何计算分支,都必须在有限步内停机。不能有无限循环的分支。
- 接受条件 (Asymmetry):
- 对于属于 L 的输入 ($x \in L$):只需要至少存在一个计算分支(一条“幸运”的路径)最终到达“接受”状态。
- 对于不属于 L 的输入 ($x \notin L$):所有的计算分支都必须最终到达“拒绝”状态。
- “猜测与验证”模型:这个不对称的接受规则完美地诠释了“猜测并验证”的模式。NTM 首先“猜测”一个可能的解(通过一系列非确定性选择),然后沿着这个分支的剩余部分“验证”这个猜测是否正确。如果 $x \in L$,那么至少存在一个正确的“解”可以被猜到并验证成功。如果 $x \notin L$,那么任何“猜测”都无法通过验证。
NTM 的运行时间定义 (定义 5):
- 最长路径决定:NTM 的计算是一棵树,运行时间 $t(n)$ 不是由接受路径的长度决定的,而是由这棵树的深度决定的。
- 最坏情况中的最坏情况:和 DTM 一样,NTM 的运行时间也是一个最坏情况的度量。它是指,在所有长度为 $n$ 的输入中,那个能产生最深的计算树的输入的树深。
- 接受与时间的对比:
- 接受: 是“最好情况”逻辑——只要有一个分支接受就算接受。
- 时间: 是“最坏情况”逻辑——必须等到所有分支(即使是那些最终拒绝的)都停机,取其中最长的时间。
💡 [数值示例]
问题:判断一个数 $N$ 是否为合数(非素数)。设 $N=9$。
- 猜测: 非确定性地猜测一个整数 $i$,范围在 $2$ 到 $N-1$ 之间。
- 验证: 检查 $N$ 是否能被 $i$ 整除。如果能,接受;否则,拒绝。
- 输入 N=9:
- 分支 1: 猜测 $i=2$。$9 \pmod 2 \neq 0$。拒绝。
- 分支 2: 猜测 $i=3$。$9 \pmod 3 = 0$。接受。
- 分支 3: 猜测 $i=4$。$9 \pmod 4 \neq 0$。拒绝。
- ...
- 分支 7: 猜测 $i=8$。$9 \pmod 8 \neq 0$。拒绝。
- 判决: 因为存在一个分支($i=3$)接受了,所以 NTM 最终判决 9 是合数。这是正确的。
- 输入 N=7 (素数):
- 无论猜测 $i=2, 3, 4, 5, 6$,都不能整除 7。所以所有计算分支都会拒绝。因此 NTM 判决 7 不是合数。这也是正确的。
- 运行时间: 猜测一步,验证(做一次除法)假设需要 $\log^2 N$ 步。那么每个分支的长度大约是 $1+\log^2 N$。这棵树的深度(即运行时间)就是 $O(\log^2 N)$。
⚠️ [易错点]
- 接受/拒绝条件混淆:最常见的错误是混淆接受和拒绝的条件。记住:接受是“存在主义的”(存在一个就行),拒绝是“普遍主义的”(所有都必须是)。
- 运行时间的计算:NTM 的运行时间不是接受分支的长度,而是所有分支中最长那条的长度。即使接受分支很短,你也必须等待最长的那个拒绝分支运行完。
📝 [总结]
本段阐明了 NTM 的两个核心运作原则:非对称的接受/拒绝逻辑(存在一个接受路径即接受 vs. 所有路径都拒绝才拒绝),以及由所有计算路径中最长路径决定的运行时间。这个模型完美地形式化了“猜测一个解并验证它”的计算过程。
🎯 [存在目的]
这两个定义是构建复杂性类 NP 的最后一块基石。没有对 NTM 判决和运行时间的清晰定义,NP 的定义将是无源之水。特别是这个不对称的接受规则,是 NP 区别于 P 和其他复杂性类的本质所在。
🧠 [直觉心智模型]
NTM 的判决就像一个庞大的法律团队为被告辩护。
- 如果被告是无辜的 ($x \in L$): 只要团队中有一个律师能找到一个证据(一个计算分支)证明被告无罪,法官就判被告无罪(接受)。
- 如果被告是有罪的 ($x \notin L$): 只有当控方检查了所有律师提出的所有辩护理由(所有计算分支),发现没有一个能证明其无罪时,法官才判被告有罪(拒绝)。
- 运行时间:整个审判结束的时间,不取决于那个聪明的律师多快找到证据,而取决于最笨的那个律师花了多长时间陈述完他那毫无道理的辩护(最长的计算路径)。
💭 [直观想象]
你在一场寻宝游戏中,地图上标有多个可能的藏宝点。
- NTM 的判决:你派出无数个机器人,每个去一个点。
- 宝藏确实存在 ($x \in L$): 只要有一个机器人报告“我找到了!”,游戏就成功结束。
- 宝藏不存在 ($x \notin L$): 你必须等到所有机器人都报告“这里没有”,你才能确定地说这个地方没有宝藏。
- NTM 的运行时间:是你派出机器人到所有机器人都返回报告这个过程所花费的总时间,这取决于去最远的那个藏宝点的机器人花了多长时间。
32.2 定义 6 和 7: NTIME 和 NP
📜 [原文29]
与确定性情况一样,我们有以下两个定义:
定义 6 (NTime($t(n)$)). 设 $t: \mathbb{N} \rightarrow \mathbb{N}$,我们定义复杂性类 NTime$(t(n))$ 为以下语言集合:
NTime$(t(n)):=\{L \mid L$ is a language decided by an $O(t(n))$ time non-deterministic Turing machine $\}$
复杂性类 NP 定义如下:
定义 7 (NP).
$\mathrm{NP}=\{L \mid L \text { is a language decided by a polynomial time non-deterministic Turing machine } \}$ 等价地,
$$
\mathrm{NP}:=\bigcup_{k \in N} \mathrm{NTime}\left(n^{k}\right)
$$
📖 [逐步解释]
这两个定义完全仿照 P 的定义方式,但把计算模型从确定性图灵机 (DTM) 换成了非确定性图灵机 (NTM)。
定义 6 (NTIME(t(n))):
- 这是一个非确定性时间复杂性类。
- 它是一个语言的集合。
- 一个语言 $L$ 属于 $\operatorname{NTIME}(t(n))$,当且仅当存在一个非确定性图灵机 (NTM) 能够在 $O(t(n))$ 的时间内判决它。
- 这个定义与 $\operatorname{Time}(t(n))$ 的唯一区别就是把 "Turing machine" 换成了 "non-deterministic Turing machine"。
定义 7 (NP):
- NP 的全称是 Nondeterministic Polynomial time(非确定性多项式时间)。
- 描述性定义: NP 是所有可以被一个非确定性图灵机 (NTM) 在多项式时间内判决的语言的集合。
- 数学化定义: NP 是所有非确定性多项式时间复杂性类的并集。即 $\mathrm{NP} = \mathrm{NTIME}(n^0) \cup \mathrm{NTIME}(n^1) \cup \mathrm{NTIME}(n^2) \cup \dots$。
- 这个定义与 P 的定义在结构上是平行的,唯一的区别就是把 $\operatorname{Time}$ 换成了 $\operatorname{NTIME}$。
💡 [数值示例]
- 合数问题 (Composite):前面例子里的判断一个数 $N$ 是否为合数的问题。我们设计的 NTM 算法运行时间是 $O(\log^2 N)$。输入是 $N$ 的二进制表示,长度为 $n = \lceil \log_2 N \rceil$。所以运行时间是 $O(n^2)$,这是多项式时间。因此,Composite $\in \mathrm{NP}$。
- 子集和问题 (Subset Sum):给定一个整数集合 $S$,是否存在一个非空子集,其元素之和为 0?
- NTM 算法:
- 猜测: 对于 $S$ 中的每个元素,非确定性地猜测“选”或“不选”它,从而构成一个子集。
- 验证: 计算这个子集中所有元素的和。如果和为 0,接受;否则拒绝。
- 分析:
- 猜测阶段:需要 $|S|$ 步。
- 验证阶段:计算和需要 $|S|$ 次加法。
- 总时间是多项式的。如果存在这样的子集,那么必然有一条猜测路径能选中它并接受。如果不存在,所有猜测路径都会拒绝。
- 结论: Subset Sum $\in \mathrm{NP}$。
⚠️ [易错点]
- NP 的常见误解:NP 不是 "Non-Polynomial"(非多项式)的缩写!这是一个极其常见的误解。NP 指的是“非确定性多项式时间”。很多 NP 问题目前没有找到多项式时间解法,但这不代表 NP 的定义是“难解问题”。
- NP 问题不一定难:所有 P 类问题都在 NP 中(后面会讲)。所以 NP 里面既有简单问题,也可能包含难问题。
📝 [总结]
复杂性类 NP 被定义为所有能被一个非确定性图灵机在多项式时间内解决的判定问题的集合。这等价于说,NP 问题就是那些解的正确性可以在多项式时间内被确定性地验证的问题(“猜测并验证”模式)。
🎯 [存在目的]
定义 NP 的目的是为了形式化一大类在计算机科学、运筹学、人工智能等领域非常重要的“搜索问题”和“优化问题”的判定版本。这些问题的共同特点是:找到一个解可能很难,但验证一个给定的解是否正确却相对容易。NP 的定义精确地捕捉了这一特性。
🧠 [直觉心智模型]
NP 类问题就像是那些“答案写出来很容易检查对错,但要你想出答案却很难”的数学题。比如,一个复杂的数独谜题。
- 验证解(容易):给你一个填好的数独,你只需要花几分钟检查一下每行、每列、每宫是否都满足1-9不重复的规则。这个验证过程是多项式时间的。
- 找出解(困难):让你从一个空白的数独开始解题,你可能需要大量的尝试和回溯,可能会花上几个小时。找到解的过程似乎是指数时间的。
NP 就是所有这类“验证容易,求解难(可能)”的问题的集合。
💭 [直观想象]
想象你是一个艺术评论家,要判断一幅画是不是伦勃朗的真迹。
- 确定性求解(P类问题):如果有一个鉴定机器,你把画放进去,它几分钟后就输出“真”或“假”,这就是 P 类问题。
- 非确定性求解(NP类问题):没有这样的机器。但是,如果有人拿来一幅画,同时提供了一大堆证据(签名细节、颜料年代分析报告、流传历史记录...),你可以通过多项式时间(比如一天)的分析来验证这些证据是否可靠,从而判断画的真伪。NP 就是所有这类“给定证据可以高效验证”的问题。那个证据就是“证书”。
32.3 证明模板 2:使用 NTM 展示 L 属于 NP
📜 [原文30]
证明模板 2 (使用非确定性图灵机展示 $L \in \mathrm{NP}$ ).
- 给出 $L$ 的非确定性判决器 $M$ 的伪代码。这里 $M$ 只以 $x$ 作为输入。
- 展示如果 $x \in L$,存在一些非确定性选择使得 $M$ 接受。
- 展示如果 $x \notin L$,无论非确定性步骤如何,$M$ 总是拒绝 $x$。
- 展示 $M$ 在多项式时间内运行,即在 $O\left(n^{k}\right)$ 时间内,其中 $k$ 为某个常数。(无需具体证明 $k$ 是什么)。
📖 [逐步解释]
这个模板提供了使用 NTM 定义来证明一个语言 $L$ 属于 NP 的标准流程。它和证明 $L \in \mathrm{P}$ 的模板非常相似,但核心区别在于算法的性质(非确定性)和正确性证明的逻辑(存在性 vs. 普遍性)。
- 步骤 1: 设计 NTM 算法。你需要描述一个非确定性算法。这个算法的核心通常是“猜测”一个潜在的解。伪代码中需要明确指出哪些步骤是非确定性的。
- 步骤 2: 证明完备性 (“是”的情况)。你必须证明,如果输入 $x$ 是一个“是”实例($x \in L$),那么至少存在一条“幸运”的非确定性选择路径,能引导算法最终接受 $x$。这通常对应于“猜中”了那个能证明 $x \in L$ 的解。
- 步骤 3: 证明可靠性 (“否”的情况)。你必须证明,如果输入 $x$ 是一个“否”实例($x \notin L$),那么无论非确定性步骤做出何种选择,算法的每一条可能路径都必须最终拒绝 $x$。这保证了算法不会接受一个不该接受的输入。
- 步骤 4: 证明效率。你必须分析 NTM 的运行时间。根据定义,这指的是其最长计算路径的长度(即计算树的深度)。你必须证明这个最长路径的长度是输入长度 $n$ 的多项式,即 $O(n^k)$。
💡 [数值示例]
问题 L: CLIQUE = $\{\langle G, k \rangle \mid G \text{ 是一个图,且 G 中包含一个大小为 k 的团 (clique)}\}$。一个团是指一个顶点子集,其中任意两个不同的顶点之间都有一条边。证明 CLIQUE $\in \mathrm{NP}$。
- NTM 伪代码:
```
Algorithm NTM_CLIQUE(G, k):
- // 猜测阶段
- 创建一个空集合 S。
- 非确定性地从图 G 的 V 个顶点中选择 k 个顶点,并将它们加入 S。
4.
- // 验证阶段
- for each pair of distinct vertices {u, v} in S:
- if there is no edge between u and v in G:
- Reject.
- Accept.
```
- 完备性: 如果 $G$ 中确实存在一个大小为 $k$ 的团 $C$,那么在步骤3中,存在一个非确定性选择路径,恰好选中了 $C$ 中的所有顶点。对于这个选择,验证阶段的 for 循环将检查 $C$ 中的每一对顶点,因为 $C$ 是一个团,它们之间都有边,所以 if 条件永远不会触发,算法最终会接受。
- 可靠性: 如果 $G$ 中不存在大小为 $k$ 的团,那么无论步骤3中非确定性地选择了哪 $k$ 个顶点的子集 $S$,这个 $S$ 必然不是一个团。这意味着,在 $S$ 中至少存在一对顶点 ${u,v}$ 之间没有边。因此,验证阶段的 for 循环必然会找到这对 ${u,v}$,触发 if 条件,并拒绝。所以,所有计算路径都会拒绝。
- 效率:
- 步骤 3: 猜测 $k$ 个顶点。这可以看作是 $k$ 次猜测,每次从剩余顶点中猜一个。这可以在多项式时间内完成。
- 步骤 6-8: 验证阶段。集合 $S$ 中有 $k$ 个顶点,需要检查 $\binom{k}{2} = \frac{k(k-1)}{2}$ 对顶点。$k$ 小于等于总顶点数 $V$,所以这是 $O(V^2)$。检查一对顶点间是否有边,在邻接矩阵表示下是 $O(1)$。总验证时间是 $O(V^2)$。
- 整个算法的最长路径长度是多项式的(关于输入大小 $V^2 + \log k$)。因此 CLIQUE $\in \mathrm{NP}$。
⚠️ [易错点]
- 猜测步骤不清晰: 必须明确指出哪一步是“非确定性”的。
- 混淆 NTM 和 DTM 的时间: NTM 的多项式时间指的是“猜测+验证”的总时间是多项式的。不要把它和用确定性算法暴力搜索所有可能性的指数时间混淆。
- 忘记分析验证所需的时间: 光猜测是不够的,验证步骤本身也必须是多项式时间的。
📝 [总结]
该模板为使用 NTM 模型证明一个问题属于 NP 类提供了清晰的四步流程:1. 设计一个“猜测并验证”的非确定性算法;2. 证明如果解存在,算法能“猜”到并接受;3. 证明如果解不存在,所有“猜测”都会失败并拒绝;4. 证明整个“猜测+验证”过程在多项式时间内完成。
🎯 [存在目的]
这个模板将 NP 的抽象定义与具体的算法设计和证明实践联系起来。它让证明过程标准化,帮助学生理解如何将一个问题的“搜索”特性转化为一个形式化的非确定性算法,并对其进行正确的复杂性分析。
🧠 [直觉心智模型]
这个模板就像是在指导你怎么写一篇“寻宝可行性报告”。
- 提出寻宝策略(NTM 算法):在地图上所有可能的位置(猜测)进行挖掘。
- 证明能找到宝藏(完备性):如果宝藏真的在某个位置,我的策略包含了对该位置的挖掘,所以一定能找到。
- 证明不会误报(可靠性):如果地图上根本没有宝藏,我挖遍了所有位置,自然一个也找不到,报告也就都是“没找到”。
- 估算寻宝时间(多项式时间):挖掘并确认每个位置是否是宝藏的时间是合理的(多项式的)。(这里的时间是单个机器人的时间,而不是所有机器人加起来的时间)。
💭 [直观想象]
想象你正在参加一个猜谜游戏,谜底是一个名人。
- NTM算法:你拥有特异功能,可以瞬间在脑中“闪现”(非确定性猜测)出世界上所有名人的名字。
- 验证:对于每一个闪现出的名字,你问主持人:“是这个人吗?” 主持人回答“是”或“否”。
- 完备性:如果谜底是“爱因斯坦”,那么你脑中闪现的无数个名字里必然包括“爱因斯坦”,当你问到他时,主持人会说“是”,你就成功了。
- 可靠性:如果谜底其实是一个不在你名人录里的人,那你问遍了所有名字,主持人都会说“否”。
- 时间:你“闪现”名字是瞬间的(1步),问一个名字并得到答案也是很快的(多项式时间)。所以整个过程是多项式时间的。因此,“猜名人”问题在 NP 中。
32.4 示例 1: 3-着色问题
📜 [原文31]
示例 1. 考虑 3-着色问题。在这个问题中,你得到一个图 $G$ 作为输入。$G$ 由 $n$ 个节点的列表 $V$ 和 $m$ 条边的列表 $E$ 给出。目标是知道是否存在 $G$ 的 3-着色。也就是说,对于每个节点,我们是否可以分配颜色 $\{r, b, g\}$ 之一,并且如果 $(u, v)$ 是 $G$ 中的一条边,则 $u$ 和 $v$ 必须具有不同的颜色。
我们想证明 3-着色:$=\{\langle V, E\rangle \mid\langle V, E\rangle$ 代表一个可 3-着色图 $\}$ 属于 NP。
这可以通过非确定性图灵机解决,如下所示。
```
Algorithm 1 3-Coloring NTM
Input: $\langle V, E\rangle$
▷ Here $V$ a list of $n$ nodes. $E$ is a list of edges between the nodes in $V$.
for each vertex $v$ in $V$ do $\quad \triangleright$ Non-deterministically pick a coloring of the graph.
Non-deterministically assign a color from $\{\mathrm{r}, \mathrm{b}, \mathrm{g}\}$ to $v$.
end for
for Every edge $(u, v) \in E$ do $\quad \triangleright$ Verify this is a good coloring.
If $u, v$ have the same color : Reject $\langle V, E\rangle$.
end for
Accept $\langle V, E\rangle$.
```
上述算法在多项式时间内运行,即:给每个顶点分配颜色需要 $O(|V|)$ 时间,因为它只涉及遍历 $V$ 中的所有顶点并为每个顶点选择一种颜色。然后检查每条边 $(u, v)$ 以确保 $(u, v)$ 具有不同的颜色,这需要 $|V| *|E|$ 的多项式时间,因为我们遍历 $E$ 中的每个顶点并查找两个顶点的颜色。
如果图存在 3-着色 $C$,那么存在某个非确定性分支,图灵机将此着色分配给顶点并因此接受。
[^0]如果输入是一个图,但不存在 3-着色,那么图灵机永远不会接受。事实上,对于第一个 for 循环中选择的每种可能的着色,由于不存在 3-着色,我们将找到一条边,其两个顶点具有相同的颜色并拒绝。
📖 [逐步解释]
这个例子应用了“模板2”来证明一个著名的问题——3-着色问题 (3-COLORING)——属于 NP。
- 问题定义:
- 输入: 一个图 $G=(V, E)$,其中 $V$ 是顶点集,E 是边集。
- 问题: 是否能用三种颜色(比如红、绿、蓝)给图中的每个顶点着色,使得任意一条边的两个端点颜色都不同?
- 应用模板2:
- 步骤 1 (NTM 伪代码): 算法分为两阶段:
- 猜测阶段: 遍历图中的每一个顶点 $v$。对于每个 $v$,非确定性地从 {红, 绿, 蓝} 中选择一个颜色赋给它。当所有顶点都赋完颜色后,我们就“猜测”出了一种完整的着色方案。
- 验证阶段: 遍历图中的每一条边 $(u,v)$。检查 $u$ 和 $v$ 的颜色。如果发现它们的颜色相同,则说明这个着色方案是无效的,立即拒绝。如果检查完所有边都没有发现冲突,说明这是一个有效的 3-着色,接受。
- 步骤 2 (完备性证明): 文中解释:“如果图存在 3-着色 $C$,那么存在某个非确定性分支,图灵机将此着色分配给顶点并因此接受。” 这句话的意思是,在猜测阶段,那条“幸运”的、恰好猜出正确着色方案 $C$ 的计算路径是存在的。当算法沿着这条路径执行时,验证阶段不会发现任何冲突,因此会接受。
- 步骤 3 (可靠性证明): 文中解释:“如果输入是一个图,但不存在 3-着色,那么图灵机永远不会接受。” 这是因为,对于猜测阶段产生的所有可能的着色方案(每一个方案都是一条计算路径),它们没有一个是有效的 3-着色。因此,对于任何一个着色方案,验证阶段都必然能至少找到一条边 $(u,v)$,其两端颜色相同,从而导致该路径被拒绝。既然所有路径都被拒绝,NTM 作为一个整体就拒绝该输入。
- 步骤 4 (效率分析):
- 猜测阶段: 有 $|V|$ 个顶点,对每个顶点做一次 3 选 1 的非确定性选择。这个过程的路径长度是 $O(|V|)$。
- 验证阶段: 有 $|E|$ 条边。对于每条边,需要查找其两个端点的颜色并比较。查找颜色的时间取决于实现,如果用数组或哈希表,是 $O(1)$。总验证时间是 $O(|E|)$。因此总时间是 $O(|V| + |E|)$,这是一个多项式时间。原文的 $|V|*|E|$ 是一个宽松但正确的上界。
- 结论: 既然 3-着色问题满足了模板2的所有条件,因此它属于 NP。
💡 [数值示例]
- 输入图 G: 一个三角形,顶点为 {1, 2, 3},边为 {(1,2), (2,3), (3,1)}。
- NTM 运行:
- 猜测阶段: NTM 会产生 $3^3 = 27$ 条不同的计算路径,对应所有可能的着色方案。
- 路径 1 (幸运的猜测): 猜 V1=红, V2=绿, V3=蓝。
- 路径 2 (不幸的猜测): 猜 V1=红, V2=红, V3=绿。
- ...
- 验证阶段:
- 对于路径 1:
- 检查边 (1,2): V1(红) ≠ V2(绿)。OK。
- 检查边 (2,3): V2(绿) ≠ V3(蓝)。OK。
- 检查边 (3,1): V3(蓝) ≠ V1(红)。OK。
- 所有边都 OK,接受。
- 对于路径 2:
- 检查边 (1,2): V1(红) = V2(红)。冲突!拒绝。
- 最终判决: 因为至少存在一条路径(路径1)接受了,所以 NTM 最终接受输入 $\langle G \rangle$,判断该图可 3-着色。
- 另一个输入图 G': 一个四个顶点完全连接的图 (K4)。
- NTM 运行:
- 无论 NTM 猜测何种着色方案,根据鸽巢原理,四个顶点至少有两个颜色相同。由于所有顶点两两相连,所以必然存在一条边,其两端颜色相同。
- 因此,所有 $3^4=81$ 条计算路径最终都会拒绝。
- 最终判决: NTM 拒绝输入 $\langle G' \rangle$,判断该图不可 3-着色。
⚠️ [易错点]
- 时间复杂度分析的细节: 原文中的 $|V|*|E|$ 分析不够精确。一个更好的分析是:猜测 $O(|V|)$,验证时检查 $|E|$ 条边,每次检查花的时间取决于数据结构,如果是邻接表并且颜色用数组存,每次检查是 $O(1)$,总验证时间是 $O(|E|)$。总时间是 $O(|V|+|E|)$,是多项式时间。关键在于结论(多项式时间)是正确的。
- NTM 的幻觉: NTM 并没有真的同时尝试所有 $3^{|V|}$ 种可能性。它是一个数学抽象。这个抽象告诉我们,问题的解空间虽然巨大,但其结构(“证书”)很简单,容易验证。
📝 [总结]
这个例子完美地展示了如何使用“猜测并验证”模型来证明一个问题属于 NP。3-着色问题的“解”(即一个具体的着色方案)可能很难找到,但是一旦有人给出了一个方案,验证它是否正确却非常快(只需检查每条边)。NTM 模型恰好形式化了这一过程:非确定性步骤“猜”出一个方案,后续的确定性步骤在多项式时间内“验证”它。
🎯 [存在目的]
通过 3-着色这个具体、经典且直观的例子,本段旨在巩固读者对 NP 和 NTM 工作方式的理解。它将抽象的证明模板应用到实践中,让读者看到理论是如何与具体问题相结合的。
🧠 [直觉心智模型]
3-着色的 NTM 就像一个拥有特异功能的小孩在做填色游戏。
- 猜测: 小孩看一眼图,瞬间(非确定性)就在脑中想象出了一个完整的填色方案。
- 验证: 然后他一眼扫过去(多项式时间),检查是不是每条线的两端颜色都不一样。
如果这个图真的可以被三种颜色填好,那么他脑中想象出的无数方案里,至少有一个是正确的,他扫一眼后就会高兴地宣布“可以!”。如果这个图根本没法用三种颜色填,那他脑中想象出的所有方案都会被他自己扫一眼后发现有错误,他就会沮丧地说“不行”。
💭 [直观想象]
想象你是一个剧院的选角导演,要为一部有 100 个角色的戏剧选演员。角色之间有复杂的不兼容关系(比如,A 和 B 不能同时出场)。
- 确定性求解 (P?): 你需要自己去面试成千上万的演员,尝试各种组合,这可能要花几年时间。
- NTM 求解 (NP): 你有超能力,可以一瞬间“想”到一种 100 人的演员组合(猜测)。然后,你花一个下午的时间,拿着不兼容关系表,逐一核对这个组合里有没有冲突(验证)。如果这个剧存在一个可行的演员组合,你的超能力总有一次能“想”到它,然后你验证通过。如果不存在,你“想”到的所有组合都会在验证时被发现有冲突。
32.5 NP 的另一种定义:验证器
📜 [原文32]
对于 NP 中的问题,除非你使用检查所有计算的指数时间暴力算法,否则通常很难确定性地判断 $x \in L$,但如果 $x \in L$,我可以给你一个“证明” $y$,通过查看 $y$ 你可以确信 $x \in L$(在多项式时间内)。
这激发了基于验证器的 NP 的另一种定义:
定义 8 (验证器). 语言 $L$ 的验证器 $V$ 是一个确定性算法,使得 $V$ 将 $x$ 和一些字符串 $c$ 作为输入,并且
$$
x \in L \leftrightarrow \exists c \text { such that } V(x, c) \text { accepts. }
$$
如果 $V$ 在 $O\left(|x|^{k}\right)$ 时间内运行(对于某个常数 $k$),则 $V$ 被称为多项式时间验证器。
在上面,我们可以将 $c$ 视为 $x \in L$ 的证明(有时称为“证书”或“证据”)。如果 $x \in L$,则某个证明必须使验证器接受;如果 $x \notin L$,则任何证明都不能使验证器接受。
如果 $V$ 是一个多项式时间验证器,我们必须始终有 $|c|=O\left(|x|^{k}\right)$,也就是说证明的长度必须是 $|x|$ 的多项式长度(否则,图灵机甚至无法在 $O\left(|x|^{k}\right)$ 时间内读取 $c$)。
📖 [逐步解释]
这一段从一个更直观、更实用的角度重新定义了 NP,即通过“验证器” (Verifier) 的概念。
- 核心思想的转换:我们不再谈论神奇的“非确定性图灵机”,而是回到了我们更熟悉的“确定性算法”。但这个算法的任务不是从头解决问题,而是“验证一个解”。
- 验证器的角色:
- 一个验证器 $V$ 是一个确定性的算法。
- 它接受两个输入:原始问题实例 $x$ 和一个所谓的“证书” (certificate) $c$。
- 这个证书 $c$ 就像是宣称“$x$ 属于 $L$”的一个“证据”或“证明”。
- 验证器的逻辑 (定义 8):
- $x \in L \leftrightarrow \exists c \text { such that } V(x, c) \text { accepts.}$
- 这句话是双向的:
- ($\rightarrow$) 如果 $x \in L$: 那么必须存在至少一个证书 $c$,当你把 $(x, c)$ 一起喂给验证器 $V$ 时,$V$ 会输出“接受”。这个 $c$ 就是 $x \in L$ 的“证据”。
- ($\leftarrow$) 如果存在一个证书 $c$ 使得 $V(x,c)$ 接受: 那么我们就可以断定 $x \in L$。
- 推论: 如果 $x \notin L$,那么对于所有可能的证书 $c$,$V(x, c)$ 都必须输出“拒绝”。任何伪造的“证据”都无法通过验证。
- 多项式时间验证器:
- NP 的关键在于这个验证过程必须是“高效”的。
- 一个验证器被称为多项式时间验证器,如果它的运行时间是关于原始输入 $x$ 长度的多项式,即 $O(|x|^k)$。
- 证书的长度:
- 一个重要的隐含条件是,证书 $c$ 本身的长度也必须是 $|x|$ 的多项式。
- 原因很简单:如果证书是指数级长度,验证器光是把它读一遍就需要指数时间,那它就不可能是多项式时间验证器了。
💡 [数值示例]
- 问题: 3-着色问题,输入图 $G$ 是一个三角形。
- 问题实例 x: $\langle G \rangle$。
- 证书 c: 一个具体的着色方案,例如 $c = \{(v_1, \text{红}), (v_2, \text{绿}), (v_3, \text{蓝})\}$。
- 验证器 V: 接收 $\langle G \rangle$ 和 $c$。它的算法就是检查 $G$ 中的每一条边,看其端点的颜色在 $c$ 中是否不同。
- 运行:
- 如果 $G$ 是可 3-着色的: 那么至少存在一个有效的着色方案 $c$(比如上面那个)。当 $V$ 收到这个 $c$ 时,它会检查所有边,发现都符合要求,于是接受。
- 如果 $G$ 不可 3-着色: 那么任何你提供给 $V$ 的着色方案 $c'$,都必然至少有一条边的端点颜色相同。$V$ 在检查到这条边时就会拒绝。所以不存在任何 $c'$ 能让 $V$ 接受。
- 效率: 验证器 $V$ 的运行时间是 $O(|E|)$,是关于输入 $|x| \approx |V|+|E|$ 的多项式。证书 $c$ 的长度是 $O(|V|)$,也是多项式的。
⚠️ [易错点]
- 验证器 vs. 判决器: 判决器 (decider) 自己从头解决问题,只接受 $x$。验证器 (verifier) 是一个“法官”,它不主动破案,只根据别人提供的“证据” $c$ 来判断 $x$ 是否有罪。
- 证书是什么: 证书不是凭空来的,它就是那个能证明 "$x \in L$" 的具体事物。对于 3-着色,它是一个着色方案;对于哈密顿路径问题,它是一条具体的路径;对于子集和问题,它是一个具体的子集。
📝 [总结]
本段提供了 NP 的第二个、等价的、也是更直观的定义:NP 是所有拥有一个多项式时间确定性验证器的语言的集合。这个定义将 NP 的本质刻画为“解的验证是容易的”,其中“解”就是那个证书 $c$。
🎯 [存在目的]
这个基于验证器的定义比基于 NTM 的定义更容易理解和使用。它避免了“非确定性”这个抽象且容易误解的概念,将其替换为“证书”和“验证”这两个更具体、更贴近我们解决问题直觉的概念。这使得证明一个问题属于 NP 变得更加直接。
🧠 [直觉心智模型]
NP 问题就是“有解题步骤的答案”。
- 问题实例 x: 一道复杂的数学题。
- 证书 c: 一份详细的解题步骤。
- 验证器 V: 你(或者老师),一个确定性的检查者。
- 验证过程: 你拿到题目和解题步骤,一步一步检查推导过程是否正确。这个检查过程是很快的(多项式时间)。如果步骤完全正确,你确认答案是对的(接受)。如果步骤有任何错误,你就认为这份答案是错的(拒绝)。
一个问题属于 NP,意味着只要它有解,就一定存在一份这样可以被快速检查的“解题步骤”。
💭 [直观想象]
想象你在玩一个解谜游戏,比如“找到一张中奖彩票”。
- 宇宙(搜索空间): 所有可能存在的彩票号码,数量巨大。
- 问题实例 x: “是否存在一张中奖彩票?”
- 证书 c: 一张具体的彩票,上面写着号码“12345”。
- 验证器 V: 彩票的验奖机。
- 验证: 你把这张彩票 $c$ 插进验奖机 $V$。验奖机在几秒钟内(多项式时间)就能告诉你它是不是中奖了。
这个问题属于 NP,因为虽然你可能要花一辈子时间去试所有彩票(指数时间搜索),但只要给你一张彩票,你验证它是否中奖是非常容易的。
32.6 证明模板 3:使用验证器展示 L 属于 NP
📜 [原文33]
证明模板 3 (使用验证器展示 $L \in \mathrm{NP}$ ).
- 给出 $L$ 的确定性验证器 $V$ 的伪代码。这里 $V$ 将 $x$ 和 $c$(证书)作为输入 ${ }^{a}$
- 展示如果 $x \in L$,存在某个 $c$ 使得 $V$ 接受 $(x, c)$。
- 展示如果 $x \notin L$,对于所有 $c$,$V$ 拒绝 $(x, c)$。
- 展示 $V$ 在多项式时间内运行,即在 $O\left(n^{k}\right)$ 时间内,其中 $k$ 为某个常数。(无需具体证明 $k$ 是什么)。
[^1]示例 2. 回到 3-着色示例,问题的验证器如下:
此算法在 $\mathcal{O}(|E| *|V|)$ 时间内运行,因为我们查看图中的所有边,并且每次都检查边上节点的颜色(每次,我们需要遍历 $C$ 才能找到 $u$ 和 $v$ 的颜色。这需要 $O(|V|)$ 时间)。所以这在 $|E|,|V|$ 的多项式时间内运行(因此在输入长度的多项式时间内运行)。
```
Algorithm 2 3-Coloring verifier
Input: $\langle V, E\rangle, C$
▷ Here $V$ a list of $n$ nodes. $E$ is a list of edges between the nodes in $V . C$ is an assignment of
$\{r, g, b\}$ to each node in $V$
for each edge ( $u, v$ ) in $E$ do
if $u, v$ have the same colors in $C$ then
Reject. $\quad \triangleright C$ isn't a valid coloring
end if
end for
Accept.
```
很明显,如果输入图 $\langle V, E\rangle$ 具有有效的 3-着色,那么存在一个 $C$ ——那个着色——使得上述算法接受 $(\langle V, E\rangle, C)$。另一方面,如果没有着色存在,那么对于任何潜在的着色 $C$,都会存在某条边 $(u, v)$,其中 $u, v$ 都具有相同的颜色(否则这将与没有着色存在相矛盾)。因此,上述算法将拒绝 $(\langle V, E\rangle, C)$。
📖 [逐步解释]
这段内容提供了使用验证器模型来证明一个语言 $L$ 属于 NP 的标准模板,并立即用 3-着色问题作为示例进行了演示。
证明模板 3 的步骤:
- 定义证书并设计验证器: 首先,你要想清楚,对于这个问题,一个“解”或者“证据”(即证书 $c$)应该长什么样。然后设计一个接收问题实例 $x$ 和证书 $c$ 的确定性算法 $V$。
- 证明完备性: 证明对于任何“是”实例 $x \in L$,都确实存在一个对应的“好”证书 $c$,能让你的验证器 $V$ 接受。
- 证明可靠性: 证明对于任何“否”实例 $x \notin L$,无论提供什么样的“坏”证书 $c$(甚至是伪造的证据),你的验证器 $V$ 都绝不会接受,总能发现问题并拒绝。
- 证明效率: 分析验证器 $V$ 的运行时间,确保它是关于原始输入 $|x|$ 的多项式时间。同时要确保你定义的证书 $c$ 的长度也是多项式的。
示例 2: 3-着色问题的验证器
- 定义证书和验证器:
- 问题实例 x: 图 $\langle V, E \rangle$。
- 证书 c: 一个具体的着色方案 $C$。它可以是一个列表或哈希表,将每个顶点映射到一种颜色 {r, g, b}。
- 验证器 V (算法2):
- 接收图 $\langle V, E \rangle$ 和着色方案 $C$。
- 遍历图中所有的边 $(u, v)$。
- 对于每一条边,根据 $C$ 查找 $u$ 和 $v$ 的颜色。
- 如果颜色相同,立即停止并拒绝。
- 如果所有边都检查完毕,没有发现冲突,则接受。
- 完备性: 文中已解释。如果图是可 3-着色的,那么一个正确的着色方案 $C$ 就是存在的。将这个 $C$ 作为证书提供给验证器,验证器检查所有边都不会发现冲突,因此会接受。
- 可靠性: 文中已解释。如果图不可 3-着色,那么任何一个给定的着色方案 $C$ 都必然是错误的。错误意味着至少有一条边的两个端点颜色相同。验证器在遍历边的过程中必然会检查到这条冲突的边,并因此拒绝。所以,没有证书能骗过验证器。
- 效率:
- 证书长度: 方案 $C$ 需要记录 $|V|$ 个顶点的颜色,其长度是 $O(|V|)$,是多项式的。
- 运行时间:
- 算法遍历 $|E|$ 条边。
- 对于每条边,查找两个顶点的颜色。原文分析说这需要 $O(|V|)$,所以总时间是 $O(|V| \cdot |E|)$。这是一个多项式。
- (更精细的分析:如果着色方案 $C$ 是一个以顶点 ID 为索引的数组,查找颜色是 $O(1)$。那么总时间就是 $O(|E|)$,这仍然是多项式的。)
- 结论是正确的:验证器在多项式时间内运行。
结论: 3-着色问题满足验证器定义的所有条件,因此它属于 NP。
💡 [数值示例]
- 问题 x: 图 $G$ 是个正方形,顶点 {1,2,3,4},边 {(1,2), (2,3), (3,4), (4,1)}。
- 证书 c: $C = \{(1,红), (2,绿), (3,红), (4,绿)\}$。
- 验证器 V 运行:
- 查边(1,2): 红≠绿。OK。
- 查边(2,3): 绿≠红。OK。
- 查边(3,4): 红≠绿。OK。
- 查边(4,1): 绿≠红。OK。
- 所有边检查完毕,接受。
- 另一个证书 c': $C' = \{(1,红), (2,红), (3,绿), (4,蓝)\}$。
- 验证器 V 运行:
- 查边(1,2): 红=红。冲突!拒绝。
⚠️ [易错点]
- 模板2 vs. 模板3: 这两个模板是等价的,只是视角不同。模板2(NTM)是“主动猜测”,模板3(验证器)是“被动验证”。通常,使用验证器的模板来思考和证明更自然、更容易。
- 验证器必须是确定性的: 验证器本身不能再有非确定性的步骤。它必须是一个普通的、可预测的算法。
📝 [总结]
本段用验证器的视角,给出了证明问题属于 NP 的第二个、更实用的模板,并用 3-着色问题成功地演示了该模板的应用。这突显了 NP 的核心特性:虽然找到解可能很难,但验证一个给定的解(证书)是容易的(多项式时间)。
🎯 [存在目的]
本段的目的是为了提供一个比 NTM 更易于理解和操作的工具(验证器模板)来证明问题属于 NP。通过并列展示两个模板(模板2和3)都能解决同一个问题(3-着色),强调了 NP 两种定义之间的等价性。
🧠 [直觉心智模型]
验证器模板就像是在法庭上作证的流程。
- 设计作证流程 (验证器V): 律师需要设计一套盘问流程,来核实证据的真伪。
- 呈上证据 (证书c): 检方或辩方提供一份证据(比如一份录音)。
- 盘问与核实 (V(x,c)): 律师按照流程,检查录音是否被剪辑、声音是否匹配、时间是否对得上。这个过程必须高效。
- 证明有效性:
- 完备性: 如果被告真的有罪,那么肯定存在一份真实的录音证据能通过所有核查。
- 可靠性: 如果被告是无辜的,那么任何伪造的录音都经不起严格的核查,会被识破。
- 效率: 核查过程不能花上几年,必须在有限的庭审时间内完成。
💭 [直观想象]
你是一名钻石鉴定师(验证器 V)。
- 问题 x: 判断一颗钻石是否是“希望之钻”。
- 证书 c: 一份关于这颗钻石的“血统证书”,记录了它从被开采出来到现在的每一次交易记录。
- 验证过程:
- 你(V)拿到钻石(x)和血统证书(c)。
- 你用多项式时间的科学方法(检查刻面、激光序列号、历史文献比对)来验证这份证书上的每一条记录是否与钻石本身以及历史事实相符。
- 完备性: 如果这颗真的是“希望之钻”,那么它一定有一份真实的血统证书能够通过你的所有验证。
- 可靠性: 如果这是一颗赝品,那么任何伪造的血统证书在你的火眼金睛下都会露出马脚。
这个问题属于 NP,因为鉴定(验证)一颗有证书的钻石是“容易”的。
32.7 NTM 与验证器的等价性
📜 [原文34]
正如你在课堂上看到的,我们有以下等价的 NP 定义:
定理 1. $\mathrm{NP}=\{L \mid L \text { is a language and there exists a polytime verifier for } L\}$
我们在此不给出上述定理的完整正式证明。如果你感兴趣,可以查阅书籍。主要思想是,给定一个 NTM,我们可以构建一个验证器,其中 $c$ 仅仅是 NTM 在每个非确定性步骤中要做的选择,以便它接受。然后验证器使用 $c$ 来运行 NTM 以选择要遵循的转换。反过来,给定一个验证器,NTM 将首先非确定性地猜测证书 $c$,然后使用该猜测的证书运行验证器。
因此,当被要求证明 $L \in \mathrm{NP}$ 时,你有两种选择:为 $L$ 提供一个多项式时间验证器,或者为 $L$ 提供一个多项式时间非确定性图灵机(我们为 3-着色示例展示了这两种选择)。
📖 [逐步解释]
这段内容陈述了一个非常重要的结论:基于非确定性图灵机 (NTM) 的 NP 定义,和基于多项式时间验证器的 NP 定义,是完全等价的。并简要说明了两者之间相互转换的核心思想。
定理 1: 这句话是验证器定义的重申,但在这里它被提升为“定理”,强调其与 NTM 定义的等价地位。
证明思路 (双向转换):
- 从 NTM 到 验证器 (NTM $\Rightarrow$ Verifier):
- 假设我们有一个多项式时间 NTM $M$ 用于判决语言 $L$。
- 我们如何构造一个验证器 $V$?
- 证书 $c$: 如果一个输入 $x \in L$,那么必然存在一条“幸运”的计算路径(一系列非确定性选择)导致 $M$ 接受。这个证书 $c$ 就是对这条路径的描述。例如,$c$ 可以是一个字符串 "21132...",表示在第1个选择点选第2个分支,第2个选择点选第1个分支,...
- 验证器 $V(x, c)$ 的工作:
- 效率: NTM 的运行时间是多项式 $p(|x|)$。这意味着证书 $c$ 的长度(路径长度)也是多项式的。验证器 $V$ 只是模拟这条路径,所以它的运行时间也是多项式的。
- 这样,我们就从一个 NTM 构造出了一个等价的多项式时间验证器。
- 从 验证器 到 NTM (Verifier $\Rightarrow$ NTM):
- 假设我们有一个多项式时间验证器 $V(x, c)$ 用于语言 $L$。
- 我们如何构造一个 NTM $M$?
- NTM $M(x)$ 的工作:
- 逻辑: 如果 $x \in L$,那么存在一个“好”证书 $c$。NTM 的“幸运”计算分支会恰好猜测到这个 $c$,然后验证通过并接受。如果 $x \notin L$,那么不存在任何“好”证书,所以无论 NTM 猜出什么 $c'$,验证都会失败,所有分支都会拒绝。
- 这正是 NTM 判决语言的定义。
- 效率: 猜测多项式长度的证书需要多项式时间。运行多项式时间的验证器也需要多项式时间。两者相加仍然是多项式时间。
- 这样,我们就从一个验证器构造出了一个等价的多项式时间 NTM。
结论: 因为可以双向转换,所以两种定义是等价的。这给予了我们选择更方便的工具来证明问题属于 NP 的自由。
💡 [数值示例]
- NTM $\Rightarrow$ Verifier: 考虑3-着色的 NTM。它的非确定性选择是为每个顶点选择颜色。
- 证书 c: 就是一个具体的着色方案,如 (V1:红, V2:绿, ...)。
- 验证器 V: 接收图和这个证书 $c$。它不再猜测,而是确定性地检查这个给定的着色方案是否有效。
- Verifier $\Rightarrow$ NTM: 考虑3-着色的验证器。
- NTM:
- 非确定性地生成一个长度为 $|V|$ 的颜色列表 $C$ (猜测证书)。
- 确定性地运行验证器的逻辑:检查图的每条边,看 $C$ 是否会导致冲突。
- 如果验证通过,接受;否则拒绝。
⚠️ [易错点]
- 忽略证明的细节: 虽然这里只给出了思想,但在正式的证明中,需要详细描述图灵机如何实现“模拟”和“猜测”,并进行严格的复杂性分析。
- 认为两种定义有强弱之分: 两种定义在数学上是完全等价的,没有哪个更“强大”。但在人类理解和使用上,验证器的定义通常被认为更直观。
📝 [总结]
本段的核心是宣告并解释了 NP 的两种定义——基于非确定性图灵机和基于多项式时间验证器——是等价的。它通过勾勒双向构造的证明思路,阐明了“非确定性猜测”和“存在一个可验证的证书”在计算上是同一枚硬币的两面。因此,证明一个问题属于 NP 时,我们可以自由选择任一更方便的定义。
🎯 [存在目的]
本段的目的是统一前文的两个视角,消除可能存在的困惑,并正式赋予研究者和学生在证明 NP 问题时选择最便捷工具的权利。这使得 NP 理论的应用变得更加灵活和强大。
🧠 [直觉心智模型]
这两种定义就像是看待“天才”的两种方式:
- NTM 定义: 天才(NTM)能瞬间想出(非确定性猜测)一道难题的答案。
- 验证器定义: 对于一道难题,如果存在一个凡人(确定性验证器)能在合理时间内看懂并确认其正确性的解法(证书),那么这个问题就是天才级的。
本段证明了:一个问题,如果天才声称能瞬间想出答案,那一定是因为它的解法可以被凡人快速验证。反之,如果一个问题的解法能被凡人快速验证,那它就属于天才能够瞬间想出的那一类问题。
💭 [直观想象]
想象两种寻找宝藏的方式:
- NTM 方式: 你有一个“神谕”,它能直接告诉你(非确定性地)宝藏在A地点。你跑到A地点,发现果然有宝藏,你就成功了。
- 验证器方式: 有人给了你一张藏宝图(证书),上面标着A地点。你(确定性的)按照地图跑到A地点,发现有宝藏,你验证了地图的正确性。
本段的结论是:如果一个宝藏能通过“神谕”找到,那么它一定也存在一张能被验证的“藏宝图”。反之,如果宝藏存在一张能被验证的“藏宝图”,那么“神谕”也一定能直接告诉你它的位置。
32.8 P 与 NP 的关系
📜 [原文35]
由于确定性图灵机是非确定性图灵机的特例,我们有以下结论:
定理 2. $\mathrm{P} \subseteq \mathrm{NP}$
一个主要的开放问题是 $\mathrm{NP} \subseteq \mathrm{P}$ 是否成立。
📖 [逐步解释]
这段阐述了 P 和 NP 之间最基本的关系,并引出了著名的 P vs. NP 问题。
定理 2: P 是 NP 的子集 ($\mathrm{P} \subseteq \mathrm{NP}$)
- 证明思路:
- 我们要证明:任何一个属于 P 的语言 $L$,也一定属于 NP。
- 前提: 如果 $L \in \mathrm{P}$,根据 P 的定义,存在一个多项式时间的确定性图灵机 (DTM) $M$ 来判决它。
- 关键点: 一个确定性图灵机可以被看作是一个非常“无聊”的非确定性图灵机 (NTM)。这个 NTM 在每个转换点,只有一个选择。它的计算树不是一棵“树”,而是一条“棍子”。
- 构造: 我们可以直接把这个 DTM $M$ 当作一个 NTM $M'$ 来用。
- 如果 $x \in L$: DTM $M$ 会接受 $x$。那么这个被看作 NTM 的 $M'$ 也有一条(唯一的)计算路径会接受 $x$。根据 NTM 的接受定义(存在一条路径接受即可),$M'$ 接受 $x$。
- 如果 $x \notin L$: DTM $M$ 会拒绝 $x$。那么 $M'$ 的所有(唯一的一条)计算路径都会拒绝 $x$。根据 NTM 的拒绝定义(所有路径都拒绝),$M'$ 拒绝 $x$。
- 效率: 这个 NTM $M'$ 的运行时间和 DTM $M$ 完全一样,也是多项式时间。
- 结论: 我们找到了一个多项式时间的 NTM ($M'$) 来判决 $L$,因此根据 NP 的定义,$L \in \mathrm{NP}$。
- 因为这个结论对所有 $L \in \mathrm{P}$ 都成立,所以 $\mathrm{P} \subseteq \mathrm{NP}$。
P vs. NP 问题:
- 核心问题: 我们已经知道所有“容易解决”的问题(P)也都是“容易验证”的(NP)。那么,反过来是否成立?是不是所有“容易验证”的问题(NP)也都是“容易解决”的(P)?
- 形式化: $\mathrm{NP} \subseteq \mathrm{P}$ 是否成立?
- 两种可能性:
- 如果 $\mathrm{P} = \mathrm{NP}$: 这意味着,任何一个解只要能被快速验证,那么这个问题本身也一定能被快速解决。这将引发技术革命,许多我们认为困难的优化问题(如蛋白质折叠、物流规划、密码破解)都将有高效算法。
- 如果 $\mathrm{P} \neq \mathrm{NP}$: 这意味着,存在一些问题,它们的解容易验证,但找到解本身就是很困难的。这将符合我们大多数人的直觉,并意味着我们目前依赖的许多加密算法是安全的。
- 现状: 这是计算机科学中最著名的未解之谜,克雷数学研究所为其设立了百万美元的千禧年大奖。绝大多数计算机科学家相信 $\mathrm{P} \neq \mathrm{NP}$,但至今无人能给出严格的数学证明。
💡 [数值示例]
- PATH 问题: 我们已经证明了 PATH $\in \mathrm{P}$。现在我们知道,它也一定在 $\mathrm{NP}$ 中。我们可以用验证器来理解:
- 问题 x: $\langle G, s, t \rangle$
- 证书 c: 一条具体的路径,例如一个顶点列表 $(s, v_1, v_2, \dots, t)$。
- 验证器 V: 检查这个列表中的相邻顶点在图 $G$ 中是否真的有边。这个验证是线性时间的,所以是多项式时间。
- 因此,PATH $\in \mathrm{NP}$。这验证了 $\mathrm{P} \subseteq \mathrm{NP}$ 的结论。
⚠️ [易错点]
- 认为 P 和 NP 是互斥的: 一个常见的初学者错误是认为 P 和 NP 是两个不相交的集合,问题要么在 P,要么在 NP。这是错误的。P 是 NP 的一个子集。
- 对 P=NP 的后果过度简化: 虽然 P=NP 会让很多问题变得容易,但这并不意味着一切问题都可解。不可判定问题(如停机问题)仍然是不可判定的。
📝 [总结]
本段建立了 P 和 NP 之间的基本关系:P 是 NP 的子集。这意味着所有可以被确定性算法在多项式时间内解决的问题,也都可以被非确定性算法在多项式时间内解决(或其解可以在多项式时间内被验证)。然后,它抛出了核心的开放问题:这个包含关系是否是严格的($\mathrm{P} \subset \mathrm{NP}$),还是两者相等($\mathrm{P} = \mathrm{NP}$)?
🎯 [存在目的]
本段的目的是将之前对 P 和 NP 的独立定义联系起来,形成一个层次结构,并在此基础上引出整个计算复杂性理论的中心问题——P versus NP。这个问题的重要性、挑战性和深远影响是后续所有关于 NP-完全性讨论的根本动机。
🧠 [直觉心智模型]
- $\mathrm{P} \subseteq \mathrm{NP}$: “会做的题,肯定也知道怎么批改”。如果你自己能从头到尾做出一道题(在 P 中),那么给你一份做好的答案,你当然能判断它对不对(在 NP 中)。
- $\mathrm{P} = \mathrm{NP}$? : “知道怎么批改的题,就一定会做吗?” 这是问题的关键。我们都会批改数独答案(在 NP 中),但这并不意味着我们每个人都能快速做出任何难度的数独(在 P 中?)。绝大多数人认为答案是否定的。
💭 [直观想象]
想象两种超能力:
- P-超能力: “创造之力”。你能快速地(多项式时间)创造出任何你想要的东西。
- NP-超能力: “鉴赏之力”。你虽然不能自己创造,但任何人拿来一件东西,你都能快速地(多项式时间)判断出它是不是杰作。
- $\mathrm{P} \subseteq \mathrm{NP}$: 拥有“创造之力”的人,必然也拥有“鉴赏之力”。因为你自己就能创造出杰作,当然能判断别人的作品是不是杰作了。
- $\mathrm{P} = \mathrm{NP}$? : 问题是,是不是所有拥有“鉴赏之力”的人,也都拥有“创造之力”?是不是所有伟大的评论家,本身也都是伟大的艺术家?这看起来不太可能,但我们无法从逻辑上证明。
44 NP 难度和完全性
📜 [原文36]
3 NP 难度和完全性
NP 中有许多问题,其中许多我们不知道任何多项式时间算法来解决。这激发了 NP 难度和 NP 完全性的概念。
📖 [逐步解释]
这是下一大章的引言,为引入 NP-难 (NP-Hard) 和 NP-完全 (NP-Complete) 这两个关键概念提供了动机。
- 观察到的现象: 在 NP 这个大篮子里,有一大堆问题(比如 3-SAT, TSP, 3-着色, 团问题等)。对于这些问题,尽管我们知道它们的解很容易验证(所以它们在 NP 里),但几十年来,全世界最聪明的头脑都没能为它们找到任何一个多项式时间的确定性算法。所有已知的算法都是指数时间的“暴力搜索”。
- 提出的问题: 这些问题是真的“难”,还是我们只是“不够聪明”?
- 激发的概念: 为了研究这些“最难的” NP 问题,科学家们提出了两个概念:
- NP-难 (NP-Hard): 用来描述一类问题,它们的难度“至少”和 NP 中任何问题一样难。
- NP-完全 (NP-Complete): 用来描述那些既在 NP 中,又是 NP-难的问题。它们是 NP 中“最难的”那一批问题。
📝 [总结]
本段作为章节的开场白,点出了一个事实:NP 中存在大量看似“困难”的问题,我们找不到它们的“高效”解法。为了系统地研究这些困难问题,NP-难和 NP-完全的概念应运而生。
🎯 [存在目的]
本段的目的是承上启下。在定义了 P 和 NP 之后,自然要对 NP 的内部结构进行更深入的探索。本段将读者的注意力引向 NP 中那些“最有趣”、“最难啃的骨头”,并预告了即将用来描述和研究它们的强大工具——归约、NP-难和 NP-完全。
🧠 [直觉心智模型]
NP 就像一个班级。
- P 是班级里那些大家都会做的“送分题”。
- 还有很多题,比如奥数题(NP 中那些难问题),答案拿给你一看就懂,但就是做不出来。
- NP-难和 NP-完全的概念,就像是要从这些奥数题里,找出那个“题王”。这个“题王”具有一种特性:只要你能解出这道“题王”,你就能解出这个班级里所有的奥-数题。
💭 [直观想象]
想象 NP 是一群需要打败的怪兽。
- P 是那些你一刀就能秒杀的史莱姆。
- 还有很多更强大的怪兽(龙、九头蛇、独眼巨人),你不知道怎么高效地打败它们。
- NP-难的概念就是“至少和所有怪兽一样强”。
- NP-完全的概念就是“龙之王”。它本身是 NP 这群怪兽的一员,并且它和所有其他怪兽都有某种联系。只要你能找到打败“龙之王”的方法,你就能用类似的方法打败所有其他怪兽。
4.1 多项式时间映射可归约
📜 [原文37]
定义 9 (多项式时间映射可归约). 语言 $A \subseteq \Sigma^{*}$ 可多项式时间(映射)归约到 $B \subseteq \Sigma^{*}$(记作 $\mathrm{A} \leq_{\mathrm{P}} \mathrm{B}$),如果存在一个多项式时间可计算函数 $f: \Sigma^{*} \rightarrow \Sigma^{*}$ 使得:
$$
x \in A \Longleftrightarrow f(x) \in B
$$
特别是,我们必须有 $|f(x)|$ 是 $|x|$ 的多项式。
📖 [逐步解释]
这是定义 NP-难和 NP-完全的核心技术工具:多项式时间归约 (Polynomial-time reduction)。
- 归约的本质: 归约是一种“问题转化”技术。说“问题 A 可以归约到问题 B”,直观上意味着“如果你有一个能解决问题 B 的黑盒子(算法),那么你就可以利用它来解决问题 A”。这暗示了 A 不比 B “更难”。
- 映射归约: 这里的归约是一种特定类型,叫映射归约 (mapping reduction)。它要求有一个函数 $f$,能把问题 A 的任何一个实例 $x$,转化为问题 B 的一个实例 $f(x)$。
- 多项式时间的要求: 这个转化过程本身必须是“高效”的。函数 $f$ 必须是一个多项式时间可计算函数,即计算 $f(x)$ 的算法的运行时间是 $|x|$ 的多项式。
- 保持答案一致性: 这是归约的核心逻辑。转化函数 $f$ 必须保持“是/否”答案不变。
- 如果 $x$ 是 A 的一个“是”实例 ($x \in A$),那么转化后的 $f(x)$ 必须是 B 的一个“是”实例 ($f(x) \in B$)。
- 如果 $x$ 是 A 的一个“否”实例 ($x \notin A$),那么转化后的 $f(x)$ 必须是 B 的一个“否”实例 ($f(x) \notin B$)。
- 记号: $A \le_P B$ 读作 "A is polynomial-time reducible to B"。这个小于等于号非常形象,暗示了“A 的难度小于等于 B 的难度”。
- 输出大小: 一个重要的技术细节是,输出 $f(x)$ 的大小也必须是输入 $|x|$ 的多项式。如果 $f(x)$ 产生指数级的输出,那么即使解决 B 的算法是多项式的,用它来解决 $f(x)$ 也会变成指数时间。
💡 [数值示例]
- 问题 A: “一个数 $n$ 是否等于 0?”
- 问题 B: “一个数 $m$ 是否等于 1?”
- 归约: 我们可以设计一个归约函数 $f$ 从 A 到 B。
- $f(n) = 1$ 如果 $n=0$。
- $f(n) = 0$ 如果 $n \neq 0$。
- 验证:
- 如果 $n=0$ ($x \in A$),那么 $f(0)=1$ ($f(x) \in B$)。成立。
- 如果 $n \neq 0$ ($x \notin A$),那么 $f(n)=0$ ($f(x) \notin B$)。成立。
- 这是一个有效的归约。它说明,如果你有一个能判断一个数是否为 1 的“黑盒子”,你就能用它来判断一个数是否为 0。
一个更经典的例子:
- 问题 A: 哈密顿路径问题 (HAMPATH),从 s 到 t 是否存在一条经过所有顶点恰好一次的路径?
- 问题 B: 旅行商问题 (TSP) 的判定版本,是否存在一条经过所有城市总距离不超过 k 的回路?
- 归约 (简化思想):
- 给定 HAMPATH 的一个实例 $\langle G, s, t \rangle$。
- 我们构造一个 TSP 的实例 $\langle G', k \rangle$。
- 构造 $G'$: 在图 $G$ 的基础上,如果两个顶点之间有边,设距离为 1。如果没边,设距离为一个很大的数(比如2)。在 $s$ 和 $t$ 之间加一条特殊的边,距离也为 1。
- 设置 k: $k = |V|$ (顶点数)。
- 逻辑: 如果原图 $G$ 中存在一条 $s \to t$ 的哈密顿路径,那么在这条路径的基础上,走 $t \to s$ 的新边,就构成了一个总长为 $(|V|-1)+1 = |V|$ 的回路。所以在 $G'$ 中存在长度为 $k=|V|$ 的回路。反之亦然。
- 这个构造过程可以在多项式时间完成。因此 HAMPATH $\le_P$ TSP。这表明 TSP 至少和 HAMPATH 一样难。
⚠️ [易错点]
- 归约的方向: $A \le_P B$ 意味着 B 至少和 A 一样难。初学者很容易搞反方向。可以记作“用 B 解 A”。
- 归约函数的时间: 归约本身必须是“容易的”(多项式时间),否则这个比较就没有意义。如果你用一个指数时间的归约,你不能得出任何关于难度比较的有效结论。
- 是映射归约: 这只是多种归约中的一种。还有其他类型的归约,如图灵归约,但映射归约是定义 NP-完全的标准工具。
📝 [总结]
多项式时间映射归约是比较问题难度的一种形式化方法。它通过一个高效的“翻译”函数 $f$,将问题 A 的任何实例 $x$ 转化为问题 B 的一个实例 $f(x)$,并保证两者的答案完全相同。$A \le_P B$ 的直观含义是 “A 不比 B 更难”。
🎯 [存在目的]
归约是计算复杂性中最重要的工具,没有之一。它让我们能够建立起不同问题之间的难度关系,形成一个“难度谱系”。正是通过归约,我们才能定义什么是“最难的”问题(NP-难),并证明一个又一个问题属于这个“最难”的类别。
🧠 [直觉心智模型]
归约就像是“翻译”。
- 问题 A: 一本法语小说。
- 问题 B: 一本英语小说。
- $A \le_P B$: 你有一个非常厉害的、翻译速度超快的“法译英”翻译机(多项式时间函数 $f$)。
- 逻辑: 你想知道法语小说的情节是不是悲剧 ($x \in A$?)。你不用自己读法语,你把小说丢进翻译机,得到一本英语小说 ($f(x)$)。然后你找一个只懂英语的朋友,让他读英语小说,告诉你是悲剧还是喜剧 ($f(x) \in B$?)。他的答案就是你想要的法语小说的答案。
- 难度比较: 这说明,对于不懂法语的你来说,理解法语小说的难度,不会超过理解英语小说的难度(加上一点翻译时间)。
💭 [直观想象]
你是一个木匠,要解决“造一张桌子”的问题(问题 A)。但你手上只有一把锤子和一个能“造一把椅子”的神奇机器(问题 B 的解决器)。
- 归约: 你想出了一个聪明的计划(函数 $f$):
- 把四根木料标记为“腿”。
- 把一块木板标记为“座面”。
- 你对神奇机器下指令:“请用这些‘腿’和‘座面’造一把椅子”。
- 神奇机器接收到指令,造出了一把“椅子”($f(x)$)。但因为你给的材料,这把“椅子”看起来正好就是一张桌子!
- 结论: 你成功地用“造椅子”的工具解决了“造桌子”的问题。所以“造桌子” ($A$) 不比“造椅子” ($B$) 更难 ($A \le_P B$)。
41.1 归约的性质
📜 [原文38]
特别是,这意味着:
定理 3. 如果 $A \leq_{\mathrm{P}} B$ 且 $B \in \mathrm{P}$,则 $A \in \mathrm{P}$。
证明. 设 $A \leq_{\mathrm{P}} B$,其中 $B \in \mathrm{P}$。设 $M$ 是 $B$ 的多项式时间算法,$f$ 是一个多项式时间函数,使得 $f(x) \in B \Longleftrightarrow x \in A$。那么我将得到一个 $A$ 的多项式时间算法 $M'$,如下所示:
```
Algorithm 3 A polytime algorithm for $A$ given a polytime decider $M$ for $B$
Input: $x$
Compute $f(x) \quad \triangleright$ This takes polynomial time since $f$ is poly-time computable
Run $M$ on $f(x) \quad \triangleright M$ runs in polynomial time in $|x|$
- Since $|f(x)|$ is polynomial in $|x|$, running $M$ takes polynomial time in $|x|$
if $M$ accepts $f(x)$ then
Accept $x$
else if $M$ rejects $f(x)$ then
Reject $x$
end if
▷ This clearly runs in polynomial time in $|x|$
```
我们想证明上述算法是 $A$ 的多项式时间判决器。
根据定义,$f(x) \in B \Longleftrightarrow x \in A$。算法 3 当且仅当 $M$ 接受 $f(x)$ 时接受。由于 $M$ 是 $B$ 的判决器,算法 3 当且仅当 $f(x) \in B$ 时接受。所以我们可以得出结论,算法 3 当且仅当 $x \in A$ 时接受。
我们现在需要证明这在多项式时间 $O\left(|x|^{k}\right)$ 内运行,其中 $k$ 为某个常数。这遵循于算法中的每一步都在多项式时间内运行,如上述算法中每一步的注释所解释。
一个更详细的论证(不必要,但可能有助于理解)如下。计算 $f(x)$ 在 $|x|$ 的多项式时间内运行,因为我们已知 $f$ 是多项式时间可计算的。
特别是,对于某个常数 $k_{1}$,我们有 $|f(x)| \leq|x|^{k_{1}}$。然后,$M$ 在 $f(x)$ 上运行将花费 $|f(x)|$ 的多项式时间,所以这在 $|f(x)|^{k_{2}}$ 时间内运行,其中 $k_{2}$ 为常数,因此运行 $M$ 需要 $|x|^{k_{1}*k_{2}}$ 时间。总的来说,这在 $O\left(|x|^{k_{1}+k_{2}}\right)$ 时间内运行,这是 $x$ 的多项式。
📖 [逐步解释]
这段内容证明了多项式时间归约的一个关键性质:如果一个问题 A 可以归约到一个“简单”问题 B(B 在 P 中),那么问题 A 本身也是“简单”的(A 也在 P 中)。
定理 3 的直观含义: 难度不会通过“简单”的转换凭空产生。如果你能把一个问题 A,通过一个简单的翻译(多项式时间归约 $f$),变成一个简单的问题 B,那么 A 本身也一定是简单的。
证明详解:
- 目标: 证明 $A \in \mathrm{P}$。根据定义,就是要为 A 设计一个多项式时间的确定性算法 $M_A$。
- 已知条件:
- $A \le_P B$: 存在多项式时间的归约函数 $f$。
- $B \in \mathrm{P}$: 存在多项式时间的算法 $M_B$ 来解决 B。
- 构造算法 $M_A$ (算法 3):
- 输入: A 的一个实例 $x$。
- 步骤 1: 调用归约函数 $f$,计算出 B 的实例 $y = f(x)$。
- 步骤 2: 调用解决 B 的算法 $M_B$,让它在 $y$ 上运行。
- 步骤 3: 完全相信 $M_B$ 的答案。如果 $M_B$ 接受 $y$,那么 $M_A$ 就接受 $x$;如果 $M_B$ 拒绝 $y$,那么 $M_A$ 就拒绝 $x$。
- 证明 $M_A$ 的正确性:
- $M_A$ 接受 $x$ $\Longleftrightarrow$ $M_B$ 接受 $f(x)$ (根据 $M_A$ 的构造)
- $M_B$ 接受 $f(x)$ $\Longleftrightarrow$ $f(x) \in B$ (因为 $M_B$ 是 B 的正确算法)
- $f(x) \in B$ $\Longleftrightarrow$ $x \in A$ (根据归约 $f$ 的定义)
- 把这三条串起来,得到:$M_A$ 接受 $x \Longleftrightarrow x \in A$。所以,$M_A$ 是 A 的一个正确算法。
- 证明 $M_A$ 的效率 (多项式时间):
- $M_A$ 的总时间 = (计算 $f(x)$ 的时间) + (运行 $M_B$ 在 $f(x)$ 上的时间)。
- 计算 $f(x)$ 的时间: 已知 $f$ 是多项式时间的,所以这一步耗时 $O(|x|^{k_1})$。
- 运行 $M_B$ 的时间:
- 已知 $M_B$ 是多项式时间的,所以它在输入 $y=f(x)$ 上的时间是 $O(|y|^{k_2}) = O(|f(x)|^{k_2})$。
- 因为 $f$ 是多项式时间的,其输出大小 $|f(x)|$ 也必须是多项式的,即 $|f(x)| = O(|x|^{k_1})$。
- 把两者结合起来,运行 $M_B$ 的时间是 $O((|x|^{k_1})^{k_2}) = O(|x|^{k_1 \cdot k_2})$。
- 总时间: $O(|x|^{k_1}) + O(|x|^{k_1 \cdot k_2})$。两个多项式的和仍然是多项式(取次数较高的那个),所以总时间是 $O(|x|^{\max(k_1, k_1 \cdot k_2)})$。
- 结论: $M_A$ 是一个多项式时间算法。
- 最终结论: 我们为 A 找到了一个多项式时间的正确算法 $M_A$,因此 $A \in \mathrm{P}$。
原文中的一个小错误:
- 原文最后一行说总时间是 $O(|x|^{k_1+k_2})$。这在多数情况下是正确的,但更严谨的应该是 $O(|x|^{k_1 \cdot k_2})$,因为是函数复合。不过,这不影响最终结论,因为两者都是多项式。$k_1+k_2$ 和 $k_1 \cdot k_2$ 都是常数。正确的总时间上界应该是 $O(|x|^{k_1} + |f(x)|^{k_2})$,而 $|f(x)|$ 本身是 $O(|x|^{k_1'})$,所以总时间是 $O(|x|^{k_1} + (|x|^{k_1'})^{k_2})$,这仍然是一个关于 $|x|$ 的多项式。
💡 [数值示例]
- 假设 $A \le_P B$,$B \in P$。
- 计算 $f(x)$ 的时间是 $10n^2$。
- 运行 $M_B$ 的时间是 $m^3$,其中 $m$ 是其输入大小。
- 归约 $f$ 产生的输出大小是 $|f(x)| = 5n^2$。
- 构造 $M_A$ 的运行时间:
- 计算 $f(x)$: $10n^2$
- 运行 $M_B$ 在 $f(x)$ 上: 时间是 $(|f(x)|)^3 = (5n^2)^3 = 125n^6$。
- 总时间: $10n^2 + 125n^6 = O(n^6)$。
- 因为 $O(n^6)$ 是多项式时间,所以我们为 A 找到了一个多项式时间算法。因此 $A \in \mathrm{P}$。
⚠️ [易错点]
- 忘记分析 $f(x)$ 的大小: 证明的关键环节之一是 $f(x)$ 的大小必须是 $|x|$ 的多项式。如果 $f$ 产生了指数级的输出,整个论证就崩溃了。
- 混淆加法和乘法: 顺序执行是时间相加,嵌套或复合是更复杂的关系,但最终只要每个组件都是多项式的,结果就是多项式的(得益于多项式的封闭性)。
📝 [总结]
定理3和它的证明是归约理论的第一个重要应用。它形式化了我们的直觉:如果你能用一个“简单”的工具($f$)把一个未知问题(A)变成一个已知是“简单”的问题(B),那么这个未知问题(A)本身也一定是“简单”的。这个定理是P 类对多项式时间归约的“向下封闭”性的体现。
🎯 [存在目的]
这个定理是后续证明问题是 NP-难的反向逻辑基础。我们将使用它的逆否命题:如果 $A \le_P B$ 且 $A \notin \mathrm{P}$(A 是难的),那么 $B \notin \mathrm{P}$(B 也一定是难的)。这使得我们可以通过归约来“传递难度”。
🧠 [直觉心智模型]
定理3就像一个“血统论”:
- P 类是个“贵族”俱乐部(简单问题)。
- $A \le_P B$ 意味着 A 和 B 有“血缘关系”(通过简单的 $f$ 转换)。
- 定理3: 如果 B 是一个贵族($B \in P$),并且 A 和 B 有血缘关系,那么 A 也一定是贵族($A \in P$)。“贵族”的身份是会遗传的。
💭 [直观想象]
你有一个食谱 A(问题 A),是用火星语写的。你不知道照着做是快是慢。
- B: 一个用中文写的、保证半小时内能做完的家常豆腐食谱($B \in P$)。
- $f$: 一个非常快的同声传译 APP,能瞬间把火星语翻译成中文($f$ 是多项式时间的)。
- $A \le_P B$: 你用 APP 翻译了火星语食谱 A,发现它翻译过来竟然就是那个家常豆腐食谱 B。
- 定理3 的结论: 既然翻译过程很快,翻译后的食谱也很快能做完,那你就可以断定,原来的火星语食谱 A 也是一个能快速做完的菜。所以 A 也是“简单”的。
41.2 归约与NP
📜 [原文39]
同样,我们有以下结论:
定理 4. 如果 $A \leq_{\mathrm{P}} B$ 且 $B \in \mathrm{NP}$,则 $A \in \mathrm{NP}$。
证明. 证明类似于 $P \in B$ 的情况,但将 $M$ 替换为判决 $B$ 的多项式时间 NTM。
📖 [逐步解释]
这个定理与定理3平行,它说明NP这个复杂性类对于多项式时间归约也是“向下封闭”的。
定理 4 的直观含义: 如果一个问题 A 可以归约到一个“可以在非确定性多项式时间解决”的问题 B(B 在 NP 中),那么问题 A 本身也一定“可以在非确定性多项式时间解决”(A 也在 NP 中)。
证明思路 (使用验证器模型):
- 目标: 证明 $A \in \mathrm{NP}$。根据验证器定义,就是要为 A 设计一个多项式时间的确定性验证器 $V_A(x, c_A)$。
- 已知条件:
- $A \le_P B$: 存在多项式时间的归约函数 $f$。
- $B \in \mathrm{NP}$: 存在一个多项式时间的验证器 $V_B(y, c_B)$ 来验证 B 的实例。
- 构造 A 的验证器 $V_A$:
- 输入: A 的一个实例 $x$,和 A 的一个证书 $c_A$。
- 等等,A 的证书是什么? 这是关键。既然 A 和 B 的答案是一致的,那么能证明 $f(x) \in B$ 的证据,也应该能间接证明 $x \in A$。所以,我们直接定义 A 的证书 $c_A$ 就是 B 的证书 $c_B$。
- $V_A(x, c)$ 的算法:
- 证明 $V_A$ 的正确性:
- 如果 $x \in A$:
- 根据归约定义,有 $f(x) \in B$。
- 因为 $f(x) \in B$,根据 B 的验证器定义,存在一个 B 的证书 $c_B$ 使得 $V_B(f(x), c_B)$ 接受。
- 那么,对于 A 的实例 $x$,我们也存在一个证书(就是这个 $c_B$),使得我们构造的 $V_A(x, c_B)$ 能够接受。
- 如果 $x \notin A$:
- 根据归约定义,有 $f(x) \notin B$。
- 因为 $f(x) \notin B$,根据 B 的验证器定义,对于所有可能的证书 $c_B$,$V_B(f(x), c_B)$ 都会拒绝。
- 那么,对于 A 的实例 $x$,无论我们给 $V_A$ 提供什么证书 $c$,$V_A(x, c)$ 内部调用的 $V_B(f(x), c)$ 都会拒绝,从而 $V_A$ 也会拒绝。
- 这完全符合 A 的验证器的定义。
- 证明 $V_A$ 的效率:
- $V_A$ 的总时间 = (计算 $f(x)$ 的时间) + (运行 $V_B$ 的时间)。
- 计算 $f(x)$ 是多项式时间的。
- 运行 $V_B$ 是多项式时间的(关于其输入 $f(x)$ 的大小)。
- 和定理3的分析完全一样,两个多项式时间操作的组合仍然是多项式时间的。
- 最终结论: 我们为 A 找到了一个多项式时间的正确验证器 $V_A$,因此 $A \in \mathrm{NP}$。
💡 [数值示例]
- 假设我们已经知道 3-SAT 问题 $\in \mathrm{NP}$。(3-SAT 是一个布尔公式可满足性问题)。
- 假设我们有一个新问题 独立集 (INDEPENDENT-SET):图中是否存在大小为 k 的顶点子集,其中任意两点之间都没有边。
- 假设我们已经证明了 INDEPENDENT-SET $\le_P$ 3-SAT。
- 定理 4 告诉我们: 我们不需要再为 INDEPENDENT-SET 重新设计 NTM 或验证器了。我们可以直接得出结论:INDEPENDENT-SET $\in \mathrm{NP}$。
⚠️ [易错点]
- 证明的逻辑: 这个证明依赖于验证器和证书的定义。理解证书可以被“传递”是关键。证明 $f(x) \in B$ 的那个证书,同时也被我们用来作为证明 $x \in A$ 的证书。
- 与定理3的相似性: 这个证明的结构和定理3的证明几乎一模一样,只是把“解决器”换成了“验证器”。这体现了 P 和 NP 定义的平行结构。
📝 [总结]
定理4表明,NP 这个复杂性类也是稳定的,能够通过多项式时间归约“保持”其成员。如果一个问题 A 能被高效地转化为一个已知的 NP 问题 B,那么 A 本身也属于 NP。这再次强化了 $A \le_P B$ 意味着“A 不比 B 更难”的直觉。
🎯 [存在目的]
这个定理在NP-完全性理论中虽然不像定理3那样被频繁地用于反向推导(传递难度),但它同样重要。它保证了通过归约建立起来的复杂性等级结构在 NP 内部是自洽的。它也是证明一个问题是 NP-完全的两个步骤之一(首先要证明问题本身在 NP 里)的有力工具。
🧠 [直觉心智模型]
定理4是“可验证性”的传递。
- NP 类: 一个“谜题”俱乐部,里面的谜题都有一个特点:虽然难解,但答案(证书)很容易验证。
- $A \le_P B$: 谜题 A 可以被快速翻译成谜题 B。
- 定理4: 如果 B 是俱乐部成员($B \in NP$),并且 A 可以被快速翻译成 B,那么 A 也够格加入这个俱乐部($A \in NP$)。因为验证 B 答案的方法,加上翻译步骤,就构成了一种验证 A 答案的方法。
💭 [直观想象]
你是一个只会说中文的侦探。
- B: 一个用英语记录的案子,你知道苏格兰场的同行(一个验证器)能在一天内核实完所有证据。所以 B 是一个 NP 案子。
- A: 一个用火星语记录的案子。
- $A \le_P B$: 你有一个很快的火星语-英语翻译机 $f$。你发现把 A 的卷宗翻译后,得到的正好就是 B 的卷宗。
- 定理4的结论: 你现在可以处理火星语案子 A 了。当有人给你一份火星语的证据(A 的证书)时,你的流程是:
- 把火星语证据翻译成英语。
- 把翻译后的证据连同 B 的卷宗一起交给苏格兰场的同行去验证。
整个过程(翻译+验证)也是高效的。所以,案子 A 也具有“证据可被高效验证”的性质,它也是一个 NP 案子。
4.2 NP-难
📜 [原文40]
多项式时间映射归约激发了以下定义:
定义 10 (NP-Hardness). 语言 $B$ 是 NP 难的,如果对于所有语言 $A \in \mathrm{NP}$,都存在从 $A$ 到 $B$ 的多项式时间归约 ($\mathrm{A} \leq_{\mathrm{P}} \mathrm{B}$)。
特别是,如果我们能够找到一个 NP 难问题的多项式时间算法,那么我们就将有 $\mathrm{NP}=\mathrm{P}$。
📖 [逐步解释]
这一定义引入了计算复杂性中一个至关重要的概念:NP-难 (NP-Hard)。
- NP-难的定义:
- 一个语言(问题)B 被称为 NP-难,它需要满足一个非常强的条件。
- 这个条件是:NP 中的任何一个问题 A,都可以多项式时间归约到 B。
- 形式化地:For every $A \in \mathrm{NP}$, $A \le_P B$ holds.
- NP-难的直观含义:
- NP-难问题是 NP 宇宙的“万能问题”或“难度上限”。
- 它的难度至少和 NP 中任何一个问题一样难。
- 如果你能解决一个 NP-难问题,你就能解决所有 NP 问题。
- NP-难问题不一定在 NP 中:
- 重要推论:
- 这句话是理解 NP-难重要性的关键:“如果我们能够找到一个 NP 难问题的多项式时间算法,那么我们就将有 $\mathrm{NP}=\mathrm{P}$。”
- 证明:
📝 [总结]
NP-难是一类问题的“难度标签”,它表示一个问题的难度“大于或等于”NP 中所有问题的难度。任何 NP 问题都可以高效地转化为一个 NP-难问题。因此,NP-难问题是解锁 P vs NP 问题的钥匙:只要能高效解决任何一个 NP-难问题,就等于高效解决了所有 NP 问题,也就证明了 $\mathrm{P} = \mathrm{NP}$。
🎯 [存在目的]
NP-难这个概念的提出,是为了在成千上万个看似困难的问题中,识别出那些“居于核心地位”的、“最具代表性”的难题。它给了我们一个攻击目标:与其漫无目的地研究各种 NP 问题,不如集中火力研究 NP-难问题,因为攻克任何一个都意味着攻克了整个山头。
🧠 [直觉心智模型]
NP-难问题就像是古代神话里的“万能钥匙”之锁。
- NP 里的每个问题都是一个上了锁的宝箱。
- 一个问题 B 是 NP-难的,意味着它就是那个“万能钥匙之锁”。
- $A \le_P B$ 的过程,就像是告诉你如何把锁 A 的宝箱,改装成一个能用万能钥匙打开的宝箱。
- 如果你能造出那把万能钥匙(找到解决 B 的多项式时间算法),你就能打开所有的宝箱。
💭 [直观想象]
想象一场多语言的国际会议。
- NP 中的问题:各种语言的发言(法语、日语、俄语...)。
- NP-难问题 B:英语。
- $A \le_P B$: 对于任何一种语言 A,都存在一个快速的同声传译员,能把它翻译成英语。
- NP-难的含义: 只要你精通英语(能解决 B),你就能理解会场上所有语言的发言(解决所有 A)。因此,英语(问题B)的“难度”至少和所有其他语言一样高。
- 如果 B $\in$ P: 这意味着“学通英语是容易的”。那么根据上面的逻辑,“学通所有语言”也就变成容易的了。
4.3 NP-完全
📜 [原文41]
另一个重要的定义是 NP 完全性:
定义 11 (NP-Complete). 语言 $B$ 是 NP 完全的当且仅当 $B \in \mathrm{NP}$ 且 $B$ 是 NP 难的。
📖 [逐步解释]
这一定义引入了NP-完全 (NP-Complete) 的概念,它位于复杂性理论的中心。
- NP-完全的两个条件: 一个问题 B 要想成为 NP-完全问题,必须同时满足两个条件:
a. 它本身必须在 NP 中 ($B \in \mathrm{NP}$)。这意味着,B 的解虽然可能难找,但验证是容易的。它必须是“那群怪兽”的一员。
b. 它必须是 NP-难的 ($B$ is NP-Hard)。这意味着,所有 NP 问题都可以多项式时间归约到 B。它必须是那群怪兽里的“龙之王”。
- NP-完全 = NP 中最难的问题:
- NP-完全问题可以被认为是 NP 中“最难”和“最具代表性”的问题。
- 最难: 因为所有其他 NP 问题都能归约到它,所以它的难度是 NP 类的上限。
- 代表性: 任何一个 NP-完全问题的命运,就代表了整个 NP 类的命运。
- P vs NP 问题的关键:
- 如果我们能为任何一个 NP-完全问题找到多项式时间算法,那么:
- 因为它在 P 中,而它又是 NP-难的,根据前面的推论,$\mathrm{P}=\mathrm{NP}$。
- 反过来,如果有人能证明任何一个 NP-完全问题不存在多项式时间算法,那么:
- 这就意味着 $\mathrm{P} \neq \mathrm{NP}$,因为我们找到了一个在 NP 中但不在 P 中的问题。
- 因此,所有 NP-完全问题“同生共死”。要么它们全都有多项式时间算法($\mathrm{P}=\mathrm{NP}$),要么它们全都没有($\mathrm{P} \neq \mathrm{NP}$)。
(这是一个经典的韦恩图,展示了 P, NP, NP-Complete, NP-Hard 之间的关系,假设 P!=NP)
💡 [数值示例]
- 3-着色问题:
- 我们在前面已经证明了它属于 NP (验证一个着色方案是容易的)。
- 事实证明(后面会讲如何证明),它也是 NP-难的。
- 结论: 3-着色问题是一个 NP-完全问题。
- 旅行商问题 (TSP) 的判定版本:
- 属于 NP (验证一条路径的总长度是否小于 k 是容易的)。
- 也是 NP-难的。
- 结论: TSP 是一个 NP-完全问题。
- 停机问题:
- 它是 NP-难的(甚至比所有 NP 问题都难)。
- 但是,它不属于 NP (它甚至是不可判定的,连解都无法保证能验证)。
- 结论: 停机问题是 NP-难的,但不是 NP-完全的。
⚠️ [易错点]
- NP-难 vs. NP-完全: 最关键的区别在于问题本身是否属于 NP。NP-完全是 NP-难和 NP 的交集。可以把 NP-难看作一个更宽泛的“难度等级”,而 NP-完全是这个难度等级中最有代表性的一群问题。
- 不是所有 NP 问题都是 NP-完全的: P 类中的所有问题都在 NP 中,但它们显然不是 NP-难的(除非 P=NP)。此外,可能还存在一些问题的难度介于 P 和 NP-完全之间(如果 P!=NP)。
📝 [总结]
NP-完全是一类特殊的问题,它们同时具备两个特性:(1) 它们的解容易验证 (属于 NP);(2) 它们是 NP 中最难的问题 (是 NP-难的)。这些问题构成了 P vs NP 问题的核心战场,任何一个的解决都将决定所有其他 NP-完全问题的命运。
🎯 [存在目的]
NP-完全性理论的建立,极大地统一和简化了对困难问题的研究。它告诉我们,与其分散地研究几千个不同的难题,不如集中精力研究其中任何一个 NP-完全问题。并且,当我们遇到一个新问题时,如果能证明它是 NP-完全的,我们就可以(在当前认知下)放弃寻找高效的精确算法,转而寻求近似算法、启发式算法等其他策略,避免浪费时间和资源。
🧠 [直觉心智模型]
如果说 NP 是一个班级,P 是里面的“学霸”(总能快速解题),那么 NP-完全就是班级里的“班长”。
- 班长在班级里 ($B \in \mathrm{NP}$)。
- 班长能解决所有同学的问题 (任何同学 A 的问题,都可以转化为班长 B 的问题来解决,$A \le_P B$,所以 B 是 NP-难的)。
- 因此,研究整个班级的学习上限,只需要研究班长的水平就可以了。
💭 [直观想象]
想象 NP 是一群被一种神秘疾病困扰的病人。
- P 类病人: 得的是普通感冒,有已知的特效药。
- NP-完全病人: 他们得的是这种神秘疾病的“典型范例”。
- NP-难属性: 研究发现,所有病人的疾病样本都可以通过简单的生物技术(归约)转化为“典型范例”病人的样本。
- NP 成员属性: “典型范例”病人本身也只是众多病人中的一员。
- 结论: 只要能研发出治愈任何一个“典型范例”病人的特效药,就意味着可以治愈所有病人。因此,医学界将所有资源都投入到对这些 NP-完全“典型范例”的研究上。
4.4 第一个NP-完全问题:Cook-Levin定理
📜 [原文42]
显然,你可能会想,我们最初是如何证明一个问题是 NP 难的。我们如何才能证明 NP 中的所有语言都可以多项式时间归约到某个语言?这是 Cook 和 Levin 独立证明以下定理的开创性成果:
定理 5. 3-SAT 是 NP 完全的。
📖 [逐步解释]
这段介绍了计算复杂性历史上里程碑式的定理——库克-莱文定理 (Cook-Levin Theorem)。
- 一个“先有鸡还是先有蛋”的问题:
- 证明一个问题 B 是 NP-难,需要从一个已知的 NP-难问题 A 出发,证明 $A \le_P B$。
- 但是,第一个 NP-难问题 A 是如何被证明的呢?在它之前,没有任何已知的 NP-难问题可以用来归约。
- Cook 和 Levin 的突破:
- Stephen Cook 和 Leonid Levin 在 1970 年代初独立地解决了这个“创世纪”的问题。
- 他们没有使用归约,而是直接回归到 NP-难的原始定义:证明 NP 中的任何一个问题,都可以多项式时间归约到某一个特定的问题。
- 他们选择的这个问题是布尔可满足性问题 (SAT)。后来发现,它的一个变种 3-SAT 也同样具有这个性质。
- 定理 5 的内容: 3-SAT 问题是 NP-完全的。
- 这意味着 3-SAT 不仅在 NP 中,而且是 NP-难的。
- 它是第一个被验明正身的 NP-完全问题。
- 证明思路简介 (非常高层次):
- 目标: 证明对于任何一个在 NP 中的语言 $L$,都有 $L \le_P \text{3-SAT}$。
- 出发点: 如果 $L \in \mathrm{NP}$,那么存在一个多项式时间的非确定性图灵机 (NTM) $M$ 来判决它。
- 核心构造: Cook 和 Levin 展示了如何构造一个多项式时间的归约 $f$。这个 $f$ 的输入是 NTM $M$ 的一个输入字符串 $x$,输出是一个巨大的 3-CNF 布尔公式 $\phi = f(x)$。
- 构造的属性: 这个公式 $\phi$ 被巧妙地设计成对 NTM $M$ 的整个计算过程的“模拟”。
- 公式中的变量代表了图灵机在每一步的每个带单元上的状态、磁头位置和符号。
- 公式中的子句描述了图灵机的合法行为规则:起始状态是正确的、每一步转换是合法的、最终到达了接受状态。
- 逻辑: NTM $M$ 在输入 $x$ 上存在一条接受路径 $\Longleftrightarrow$ 构造出的布尔公式 $\phi$ 是可满足的。
- 结论: 这个构造本身可以被一个算法在 $|x|$ 的多项式时间内完成。因此,这建立了一个从任意 NP 问题到 3-SAT 的多项式时间归约。这就证明了 3-SAT 是 NP-难的。
- 再加上证明 3-SAT 本身在 NP 中是容易的(给一个变量赋值,验证公式是否为真),所以 3-SAT 是 NP-完全的。
📝 [总结]
Cook-Levin 定理是 NP-完全性理论的基石。它通过一个极其精妙的构造,证明了 3-SAT 问题是第一个 NP-完全问题。这个定理的重大意义在于,它提供了一个“根”,使得后续所有其他问题的 NP-难证明,都可以通过从 3-SAT (或其“后代”) 进行归约来完成,而无需再重复那个复杂的“模拟图灵机”的构造。
🎯 [存在目的]
本段的目的是解释NP-完全性理论的“第一推动力”来自哪里。它解决了证明NP-难的“死循环”问题,为整个理论体系的建立提供了坚实的起点。它就像是物理学中的牛顿,或者生物学中的达尔文,其成果开创了一个全新的研究领域。
🧠 [直觉心智模型]
Cook-Levin 定理就像是在所有语言中,找到了一个“通用语”或“世界语”(3-SAT)。
- 任何语言 (NP问题): 都可以被高效地翻译成这个世界语。
- 翻译过程: 就是模拟图灵机的计算。
- 意义: 从此以后,我们想知道一门新语言有多难,只需要看它和“世界语”之间的翻译难度就可以了,而不需要再和所有其他成百上千种语言一一比较。
💭 [直观想象]
想象你要证明“国际象棋”是所有棋类游戏中最难的之一。
- Cook-Levin 的方法: 他们没有去比较国际象棋和围棋、中国象棋、跳棋...,而是做了一件更绝的事。
- 他们证明了:任何一个棋盘游戏(只要规则能在合理时间内判定输赢),其任何一个局面,都可以被“翻译”成一个国际象棋的残局谜题。并且这个“翻译”过程本身很快。
- 结论: 能解开任何国际象棋残局的人,就等于能解决所有棋类游戏的谜题。因此,国际象棋就是“棋类游戏中的NP-完全问题”。
4.5 利用归约传递难度
📜 [原文43]
继这一突破性成果之后,许多语言也被证明是 NP 完全 / NP 难的。特别是,以下定理是证明其他语言 NP 难度的关键。
定理 6. 如果 $A \leq_{\mathrm{P}} B$ 且 $A$ 是 NP 难的,则 $B$ 是 NP 难的。
证明. 设 $A, B$ 满足 $A \leq_{\mathrm{P}} B$ 且 $A$ 是 NP 难的。设 $L$ 是 NP 中的任何语言(我们想证明 $L \leq_{\mathrm{P}} B$)。根据 NP 难度的定义,我们有 $L \leq_{\mathrm{P}} A$。因此,存在一个多项式时间可计算函数 $g$ 使得 $x \in L \Longleftrightarrow g(x) \in A$。此外,由于 $A \leq_{\mathrm{P}} B$,我们存在一个多项式时间可计算函数 $f$ 使得 $y \in A \Longleftrightarrow f(y) \in B$。
所以我们声称 $f \circ g$ 是一个多项式时间函数,并且 $x \in L \Longleftrightarrow f(g(x)) \in B$。假设这两个主张是真的,我们有 $L \leq_{\mathrm{P}} B$(根据定义),因此任何语言 $L \in \mathrm{NP}$ 都可以多项式时间归约到 $B$,因此 $B$ 是 NP 难的。
首先,让我们证明 $f \circ g$ 可以在多项式时间内计算。由于 $g$ 是多项式时间可计算的,给定 $x$ 作为输入,我们可以在多项式时间内计算 $g(x)$。所以我们知道 $|g(x)|$ 是 $|x|$ 的多项式(否则,我们甚至没有时间在多项式时间内写入 $g(x)$)。由于 $f$ 也可以在多项式时间内计算,我们可以在 $|g(x)|$ 的多项式时间内计算 $f(g(x))$。但这在 $|x|$ 中也是多项式的(因为 $|g(x)|$ 在 $|x|$ 中是多项式的) ${ }^{2}$。所以这表明 $f \circ g$ 可以在多项式时间内计算。
现在我们想证明 $x \in L \Longleftrightarrow f(g(x)) \in B$。
- 对于第一个方向,如果 $x \in L$,那么我们必须有 $g(x) \in A$。但我们知道 $y \in A \Rightarrow f(y) \in B$,所以 $g(x) \in A \Rightarrow f(g(x)) \in B$,所以综合起来,$x \in L \Rightarrow g(x) \in A \Rightarrow f(g(x)) \in B$。
- 对于另一个方向,假设 $f(g(x)) \in B$,那么根据 $f$ 的性质,它必须是 $g(x) \in A$。但根据 $g$ 的性质,如果 $g(x) \in A$,我们必须有 $x \in L$。所以 $f(g(x)) \in B \Rightarrow g(x) \in A \Rightarrow x \in L$。
所以我们证明了这两个方向,因此该主张是正确的。
📖 [逐步解释]
这个定理是证明新问题是 NP-难的标准方法。它表明 NP-难这个属性是可以通过多项式时间归约来“传递”的。
定理 6 的直观含义: 如果问题 A 已经很难了(NP-难),而你又能把 A 转化为 B($A \le_P B$),那么 B 肯定也一样难,甚至更难。
证明详解:
- 目标: 证明 B 是 NP-难的。
- NP-难的定义: 要证明 B 是 NP-难,我们必须证明对于任意一个 NP 问题 L,都有 $L \le_P B$。
- 已知条件:
- $A$ 是 NP-难的。这意味着对于我们选定的任意 L,我们已经知道 $L \le_P A$。存在一个多项式时间归约 $g$。
- $A \le_P B$。这是题目给的。存在一个多项式时间归约 $f$。
- 构造从 L 到 B 的归约:
- 我们需要找到一个函数 $h$,使得 $L \le_P B$。
- 我们的路径是 $L \xrightarrow{g} A \xrightarrow{f} B$。
- 自然的想法是把两个函数复合起来:定义 $h(x) = f(g(x))$。
- 证明 $h$ 是一个有效的归约: 我们需要证明两件事:
a. $h$ 是多项式时间的:
i. 计算 $g(x)$ 需要多项式时间 $O(|x|^{k_g})$。
ii. 其输出大小 $|g(x)|$ 也是多项式的,$O(|x|^{k'_{g}})$。
iii. 计算 $f(y)$ 需要多项式时间 $O(|y|^{k_f})$。
iv. 所以计算 $f(g(x))$ 需要的时间是 $O(|g(x)|^{k_f}) = O((|x|^{k'_{g}})^{k_f}) = O(|x|^{k'_{g} \cdot k_f})$。
v. 总时间是计算 $g(x)$ 的时间 + 计算 $f(g(x))$ 的时间,两个多项式的和仍然是多项式。
- 结论: 复合函数 $h=f \circ g$ 是多项式时间可计算的。
b. $h$ 保持答案一致性:
$x \in L$
$\Longleftrightarrow g(x) \in A$ (因为 $g$ 是 $L \to A$ 的归约)
$\Longleftrightarrow f(g(x)) \in B$ (因为 $f$ 是 $A \to B$ 的归约)
- 结论: $x \in L \Longleftrightarrow h(x) \in B$。
- 最终结论: 我们已经为任意的 $L \in NP$ 构造了一个从 L 到 B 的多项式时间归约 $h$。因此,根据定义,B 是 NP-难的。
📝 [总结]
定理6建立了 NP-难度的传递性。一旦我们有了一个已知的 NP-难问题 A (比如 3-SAT),要证明一个新问题 B 是 NP-难的,我们不再需要从基本定义出发去归约所有的 NP 问题,我们只需要完成一个归约:证明 $A \le_P B$ 即可。这使得证明 NP-难从一项几乎不可能的任务,变成了一项可以通过巧妙构造来完成的、可行的任务。
🎯 [存在目的]
这个定理是 NP-完全性理论能够蓬勃发展的关键。它提供了一个“配方”或“方法论”,使得研究人员可以像搭积木一样,从已知的 NP-难问题出发,不断地将更多的问题纳入 NP-难的大家族中。几乎所有已知的 NP-完全问题的证明(除了第一个 Cook-Levin)都依赖于这个定理。
🧠 [直觉心智模型]
这就像是难度的“传染病”。
- A 是 NP-难的: A 是一个已知的“重症病人”。
- $A \le_P B$: 我们发现 A 和 B 有过“密切接触”(可以高效转换)。
- 定理6: 难度是会传染的,所以 B 也一定是“重症病人”(NP-难)。
💭 [直观想象]
你在玩一个闯关游戏。
- A 关卡 (3-SAT): 这是公认的超级难关。
- B 关卡: 一个新的、看起来也很难的关卡。
- 你的任务: 证明 B 关卡也是超级难的。
- 你的策略 ($A \le_P B$): 你发现了一个秘籍,可以让你把 A 关卡里的任何挑战,都快速地变成 B 关卡里的一个特定挑战。
- 结论 (定理 6): 如果你能通过 B 关卡,那么利用这个秘籍,你就能通过 A 关卡。既然 A 是公认的超级难关,那么 B 关卡也一定至少有那么难。
45.1 证明模板 4:展示 B 是 NP-难的
📜 [原文44]
当被要求证明语言 $L$ 是 NP 难或 NP 完全时,你应该按以下步骤进行:
证明模板 4 (展示 $B$ 是 NP 难的).
- 选择一个 NP 难问题 $A$,你想证明 $A \leq_{\mathrm{P}} B$。
- 给出函数 $f$ 的伪代码。
- 展示给定 $x$ 作为输入,$f(x)$ 可以在多项式时间内计算,即在 $O\left(n^{k}\right)$ 时间内,其中 $k$ 为某个常数。(无需具体证明 $k$ 是什么)。
- 展示如果 $x \in A$,那么 $f(x) \in B$。
- 展示如果 $x \notin A$,那么 $f(x) \notin B$。
📖 [逐步解释]
这个模板将定理6的理论直接转化为一个可操作的证明流程。
- 步骤 1: 选择源问题 A:
- 你不能凭空证明 B 是 NP-难的。你必须站在巨人的肩膀上。
- 这个“巨人”就是一个你已经知道是 NP-难的问题 A。通常,我们会选择一个与我们当前问题 B 在结构上最相似的已知 NP-难问题,这样构造归约会更容易。
- 最早的源头是 3-SAT。后来,3-SAT 被用来证明 3-着色、哈密顿路径、团问题等都是 NP-难的。现在,这个“已知 NP-难问题库”已经非常庞大,你可以从中选择任何一个方便的来用。
- 步骤 2: 构造归约函数 f:
- 这是证明中最具创造性和挑战性的一步。
- 你需要设计一个算法,它能接收问题 A 的任何实例 $x$,并输出问题 B 的一个实例 $f(x)$。
- 这个构造必须非常巧妙,以便保持“是/否”答案的一致性。
- 步骤 3: 证明 f 是多项式时间的:
- 你必须分析你设计的构造算法的运行时间,证明它是一个关于输入 $|x|$ 的多项式时间算法。如果构造过程本身就需要指数时间,那这个归约就无效了。
- 步骤 4: 证明 "是" $\Rightarrow$ "是":
- 这是证明归约正确性的第一个方向。
- 你需要假设 $x$ 是 A 的一个“是”实例,然后通过逻辑推导,证明你构造出的 $f(x)$ 也必然是 B 的一个“是”实例。
- 步骤 5: 证明 "否" $\Rightarrow$ "否":
- 这是证明归约正确性的第二个方向。
- 你需要假设 $x$ 是 A 的一个“否”实例,然后证明你构造出的 $f(x)$ 也必然是 B 的一个“否”实例。
- 有时,证明这个方向的逆否命题会更容易:证明如果 $f(x) \in B$,那么 $x \in A$。这和步骤 4 结合起来,就构成了 $x \in A \Longleftrightarrow f(x) \in B$。
💡 [数值示例]
目标: 证明 顶点覆盖 (VERTEX-COVER) 是 NP-难的。
(顶点覆盖问题:给定图 G 和整数 k,是否存在一个大小不超过 k 的顶点子集,使得图中每条边都至少有一个端点在该子集中。)
- 选择源问题 A: 我们选择已知的 NP-难问题 独立集 (INDEPENDENT-SET)。
(独立集问题:给定图 G 和整数 k,是否存在一个大小至少为 k 的顶点子集,其中任意两点之间都没有边。)
- 构造归约 f:
- A 的实例是 $\langle G=(V,E), k \rangle$。
- B 的实例也是 $\langle G', k' \rangle$。
- 归约函数 f: $f(\langle G, k \rangle) = \langle G, |V|-k \rangle$。也就是说,图不变,只是把目标数值 k 换成 $|V|-k$。
- f 是多项式时间: 计算 $|V|-k$ 只需要一次减法,是 $O(1)$ 的,显然是多项式时间。
- 证明 "是" $\Rightarrow$ "是":
- 假设 $\langle G, k \rangle$ 是独立集的一个“是”实例。这意味着存在一个大小为 $k$ 的独立集 $S$。
- 考虑剩下的顶点集 $V' = V \setminus S$,其大小为 $|V|-k$。
- 我们要证明 $V'$ 是一个顶点覆盖。对于图中任意一条边 $(u,v)$,因为 $S$ 是独立集,$u$ 和 $v$ 不能同时在 $S$ 中。所以,$(u,v)$ 中至少有一个顶点不在 $S$ 中,即在 $V'$ 中。
- 这正是顶点覆盖的定义。所以我们找到了一个大小为 $|V|-k$ 的顶点覆盖。
- 因此,$f(\langle G, k \rangle) = \langle G, |V|-k \rangle$ 是顶点覆盖的一个“是”实例。
- 证明 "否" $\Rightarrow$ "否": 这个证明是完全对称的,从一个顶点覆盖出发,可以证明其补集是一个独立集。
结论: 我们成功地将已知的 NP-难问题 INDEPENDENT-SET 归约到了 VERTEX-COVER。因此,VERTEX-COVER 也是 NP-难的。
📝 [总结]
这个模板为证明一个问题是 NP-难提供了一个清晰、实用的“菜谱”。其核心就是找到一个已知的 NP-难问题,并巧妙地构造一个从它到新问题的多项式时间归约。
🎯 [存在目的]
该模板的目的是将 NP-难证明这个高度理论化的任务,转化为一个具体的、有固定步骤的工程挑战。它使得研究者可以系统地、严谨地构建证明,而不会遗漏关键环节(如归约的效率和双向正确性)。
45.2 证明模板 5:展示 B 是 NP-完全的
📜 [原文45]
证明模板 5 (展示 $B$ 是 NP 完全的).
- 展示 $B \in \mathrm{NP}$。
- 展示 $B$ 是 NP 难的。
📖 [逐步解释]
这个模板非常直接,它源于 NP-完全的定义。
- 步骤 1: 证明 B 属于 NP ($B \in \mathrm{NP}$):
- 这一步是证明问题 B 的解是“容易验证”的。
- 你需要使用模板 2 (NTM) 或模板 3 (验证器) 来完成。
- 通常,使用验证器模型更简单:
- 步骤 2: 证明 B 是 NP-难的 ($B$ is NP-Hard):
- 这一步是证明问题 B 是“至少和所有 NP 问题一样难”的。
- 你需要使用模板 4 来完成。
同时完成这两步,就证明了 B 既是 NP 的一员,又是 NP 中最难的之一,因此 B 是 NP-完全的。
💡 [数值示例]
目标: 证明 3-着色是 NP-完全的。
- 证明 3-着色 $\in \mathrm{NP}$:
- 我们已经在 示例 2 中使用验证器模型详细地证明了这一点。证书是一个着色方案,验证器检查每条边。这个过程是多项式时间的。
- 证明 3-着色是 NP-难的:
- 这部分非常复杂,但思路是:
- 为公式中的每个变量 $x_i$ 和它的否定 $\bar{x_i}$ 创建一对顶点,并用边连接它们。这确保了在一个有效的着色中,$x_i$ 和 $\bar{x_i}$ 颜色必须不同。
- 引入三个特殊的“基础颜色”顶点(红、绿、蓝),它们两两相连形成一个三角形。
- 对于公式中的每个子句 (例如 $l_1 \lor l_2 \lor l_3$),构造一个复杂的小工具(gadget)图,这个小图连接到代表 $l_1, l_2, l_3$ 的顶点上。这个小工具的设计非常巧妙,使得只有当 $l_1, l_2, l_3$ 中至少有一个被赋予“真”对应的颜色时,这个小工具本身才能被成功地 3-着色。
- 如果 $\phi$ 可满足,那么存在一个真值赋值。我们可以根据这个赋值给图的顶点着色,可以证明整个图是可 3-着色的。
- 如果图 $G$ 可 3-着色,那么从图的着色方案中,我们可以反向推导出一个满足 $\phi$ 的真值赋值。
- 结论: 3-SAT $\le_P$ 3-着色。因此 3-着色是 NP-难的。
最终结论: 因为 3-着色既在 NP 中,又是 NP-难的,所以它是 NP-完全的。
📝 [总结]
这个模板清晰地指出了证明一个问题是 NP-完全所需的两项主要工作:证明它属于 NP,并且证明它是 NP-难的。它是一个“元模板”,它本身的工作需要调用其他更具体的模板(模板 3 和 模板 4)来完成。
🎯 [存在目的]
该模板将 NP-完全的抽象定义分解为两个独立的、可执行的证明任务。这使得证明过程模块化,逻辑清晰。在研究一个新问题时,研究者可以分别攻克这两个部分。
45.3 NP-完全问题示例
📜 [原文46]
以下是一些 NP 完全问题的示例:
- SAT:给定一个 CNF 公式 $\phi$,$\phi$ 是否可满足?以及 3-SAT:给定一个 CNF 公式 $\phi$,其中每个子句有 3 个文字,$\phi$ 是否可满足?
- Hampath。给定图 $G$ 和两个节点 $s, t$,是否存在从 $s$ 到 $t$ 的哈密顿路径?
- Clique。给定图 $G$ 和整数 $k$,$G$ 是否包含一个 k-团($G$ 包含 $k$ 个顶点的完全图作为子图)?
- Sudoku。给定一个 $n^{2} \times n^{2}$ 数独谜题,它是否有解?
- 还有很多很多...
📖 [逐步解释]
本段列举了一些著名的 NP-完全问题,让读者对这类问题有一个具体的感受。
- SAT / 3-SAT (布尔可满足性问题):
- 这是所有 NP-完全问题的“始祖”。
- 问题: 给定一个由逻辑“与”连接的、由逻辑“或”组成的子句构成的布尔表达式,是否存在一种对变量的“真/假”赋值,使得整个表达式为真?
- 3-SAT 是其简化版,要求每个子句中恰好有3个文字(变量或其否定)。它仍然是 NP-完全的,并且在构造归约时更规整、更好用。
- Hampath (哈密顿路径问题):
- 问题: 在一个给定的图中,是否存在一条路径,从起点 s 到终点 t,并且这条路径恰好经过图中每个顶点一次?
- 这是图论中的经典难题。与寻找普通路径(在 P 中)不同,“经过每个顶点一次”这个约束使得问题变得异常困难。
- Clique (团问题):
- 问题: 在一个给定的图中,是否存在一个由 k 个顶点组成的“团”?“团”指的是一个顶点子集,其中任何两个顶点之间都有一条边,即一个完全子图。
- 这在社交网络分析等领域有应用(例如,寻找一个 k 个人的小团体,他们彼此都互相认识)。
- Sudoku (数独):
- 问题: 给定一个部分填好的 $n^2 \times n^2$ 的数独网格,是否存在一个合法的解?
- 经典的 $9 \times 9$ 数独是这个问题的特例 ($n=3$)。因为大小固定,任何 $9 \times 9$ 的数独都可以在有限(常数)时间内解决。但当我们将问题推广到任意大小的 $n^2 \times n^2$ 网格时,它就变成了 NP-完全问题。
📝 [总结]
本段通过一系列具体的例子,丰富了我们对 NP-完全问题的认识。这些问题来自不同的领域(逻辑、图论、娱乐),但它们在计算复杂性上是等价的——它们都处于 NP 中最难的那个级别。
🎯 [存在目的]
列举这些例子的目的是为了展示 NP-完全概念的广泛性和普适性。它告诉我们,这种“看似简单、实则极难”的计算难题,并不仅仅是理论科学家的抽象玩具,而是广泛存在于各种实际和理论问题中。这让读者更深刻地体会到 P vs NP 问题的重要性。
🧠 [直觉心智模型]
这就像一个“武林高手排行榜”:
- 3-SAT: 开山鼻祖,第一个被公认的绝顶高手。
- Hampath: 轻功盖世的路径高手。
- Clique: 精通社交关系、拉帮结派的大佬。
- Sudoku: 玩弄数字于股掌的智者。
他们虽然招式各异,但内力修为(计算复杂性)是同一个级别的。打败任何一个,就等于有了挑战整个武林的能力。
55 值得了解:如何处理子集
📜 [原文47]
4 值得了解:如何处理子集
通常,一个问题需要查看某个集合的大小为 $k$ 的所有子集。如果有一个包含 $n$ 个元素的集合,大小为 $k$ 的子集的数量表示为 $\binom{n}{k}$。重要的是要知道:
$$
\binom{n}{k}=O\left(n^{k}\right)
$$
这是一个 P 中的问题示例。
示例 3. 3-Sum 问题定义如下。给定一个整数集合 $S$,其中包含 $n$ 个整数。目标是知道是否存在任何方式从 $S$ 中选择 3 个不同的元素 $a, b, c$ 使得 $a+b+c=0$。
我们将遵循前面给出的模板来证明这属于 P。
```
Algorithm 4 3-Sum Algorithm
Input: $\langle S\rangle$
▷ Here $S$ is a set of $n$ numbers in $\mathbb{Z}$.
for every subset $\{a, b, c\}$ of $S$ do
If $a+b+c=0$ : Accept.
end for
Reject.
```
上述算法在多项式时间内运行:我们查看 $S$ 中所有 3 个元素的子集,并检查这 3 个元素的和是否为 0。因此,我们需要查看 $\binom{n}{3}$ 个 $S$ 的子集,对于每个子集,我们检查 $a+b+c=0$(你可以假设这个检查需要多项式时间)。所以运行时间是 $O\left(\binom{n}{3}\right)=O\left(n^{3}\right)$,这是多项式的。
这个算法显然是正确的:
- 如果 $\langle S\rangle$ 不在 3-Sum 中,我们永远不会在 for 循环中找到和为 0 的 $a, b, c \in S$,所以我们将拒绝。
- 如果 $\langle S\rangle$ 在 3-Sum 中,那么存在 $a, b, c \in S$ 和为 0。所以当我们查看这三个元素时,算法将接受。
📖 [逐步解释]
这部分讨论了一个在算法分析中常见的组合问题——遍历子集,并用 3-Sum 问题作为例子,说明了当子集大小 $k$ 是一个常数时,暴力搜索算法可以是多项式时间的。
- 子集数量分析:
- 从 $n$ 个元素中选出 $k$ 个组成子集,总共有 $\binom{n}{k} = \frac{n!}{k!(n-k)!}$ 种方式。
- 关键近似: 当 $k$ 是一个常数时,$\binom{n}{k} = \frac{n(n-1)\dots(n-k+1)}{k!}$。这是一个关于 $n$ 的 $k$ 次多项式。因此,我们可以说 $\binom{n}{k} = O(n^k)$。
- 重要对比: 如果 $k$ 不是常数,而是 $n$ 的函数(例如 $k=n/2$),那么 $\binom{n}{n/2}$ 的增长是指数级的 ($O(\frac{2^n}{\sqrt{n}})$),算法就不是多项式时间了。
- 示例 3: 3-Sum 问题:
- 问题: 给定一个含 $n$ 个整数的集合 $S$,是否存在三个不同的元素 $a,b,c \in S$ 使得它们的和为 0?
- 算法 (暴力搜索):
- 遍历 $S$ 中所有大小为 3 的子集。
- 对于每个子集 $\{a, b, c\}$,计算它们的和。
- 如果和为 0,立即停止并回答“是”(Accept)。
- 如果遍历完所有子集都没找到,则回答“否”(Reject)。
- 证明 3-Sum $\in \mathrm{P}$:
- 算法正确性: 算法检查了所有可能性,所以如果存在解,它一定能找到;如果不存在,它会检查完所有情况后正确地拒绝。
- 效率分析:
- 关键在于循环次数,即大小为 3 的子集的数量。这个数量是 $\binom{n}{3}$。
- 因为这里的 $k=3$ 是一个常数,所以 $\binom{n}{3} = \frac{n(n-1)(n-2)}{6} = O(n^3)$。
- 每次循环内部的操作(取三个数,加起来,比较)是常数时间。
- 所以总运行时间是 $O(n^3)$。
- 因为 $O(n^3)$ 是多项式时间,所以 3-Sum 问题属于 P。
- (注:存在更优的 $O(n^2)$ 算法,但 $O(n^3)$ 已足以证明它在 P 中)。
💡 [数值示例]
- 3-Sum 实例: $S = \{-5, 1, 4, -2, 3, 2\}$。$n=6$。
- 算法运行:
- 检查子集 $\{-5, 1, 4\}$。和是 0。接受。算法终止。
- 另一个实例: $S = \{1, 2, 3, 4, 5, 6\}$。
- 算法运行:
- 检查 $\{1,2,3\}$,和=6。
- 检查 $\{1,2,4\}$,和=7。
- ...
- 它会检查所有 $\binom{6}{3} = \frac{6 \cdot 5 \cdot 4}{3 \cdot 2 \cdot 1} = 20$ 个子集。
- 因为找不到和为 0 的子集,最终拒绝。
- 时间分析: 对于 $n=100$,需要检查 $\binom{100}{3} \approx 100^3/6 \approx 161700$ 个子集。如果 $n=1000$,大约需要检查 $1.6 \times 10^8$ 个。虽然数字变大很快,但增长是多项式的。
⚠️ [易错点]
- k 是否是常数: 这个结论的适用性严格依赖于 $k$ 是一个固定的常数。对于 Clique 问题(给定 $G$ 和 $k$,找大小为 $k$ 的团),$k$ 是输入的一部分,不是常数。因此,遍历所有大小为 $k$ 的子集的算法 $\binom{n}{k}$ 就不是多项式时间的,这也是 Clique 是 NP-完全而 k-Sum(对于固定的k)在 P 中的根本原因。
📝 [总结]
本段通过分析子集的数量,阐明了一个重要的算法设计原则:当需要遍历的子集大小 $k$ 是一个固定的常数时,暴力枚举所有 $k$-子集的策略是一个多项式时间算法。3-Sum 问题就是这一原则的一个典型例子,它可以通过 $O(n^3)$ 的暴力搜索被解决,因此属于 P。
🎯 [存在目的]
本段的目的是为了澄清一个常见的混淆点,即涉及“子集”的问题不一定都是指数级的难题。它通过区分“$k$ 是常数”和“$k$ 是变量”这两种情况,帮助读者更深刻地理解多项式时间和指数时间的界限在哪里,并为后续理解 k-Clique 和 Clique 问题的区别打下基础。
66 值得了解:数字的表示
📜 [原文48]
5 值得了解:数字的表示
给定一个数字 $N$,有很多种方式将其作为输入传递给图灵机。最常见的编码方式如下:
- 一元:以 $1^{N}$(即 $N$ 个数字 1)作为输入。
- 二元:以 $N$ 的二元表示作为输入。
在第一种情况下,这需要 $N$ 位。在第二种情况下,这需要 $O(\log (N))$ 位。因此,使用的位数之间存在指数级的差距。在考虑多项式时间算法时,这是一个非常重要的区别。
特别是,除非明确提及,任何作为输入接收的数字都以二元形式给出。因此,给定 $N$,如果你的算法在 $O\left(\log (N)^{k}\right)$ 时间内运行,则它是多项式时间的;如果它在 $O\left(N^{k}\right)$ 时间内运行,则它不是多项式时间的。
📖 [逐步解释]
这部分强调了一个在复杂性分析中极其重要但容易被忽略的细节:数字作为输入时是如何编码的,因为这直接决定了“输入规模 $n$”到底是什么。
- 两种编码方式:
- 一元 (Unary): 用 $N$ 个 "1" 来表示数字 $N$。例如,数字 5 表示为 "11111"。这种表示法的长度就是 $N$。
- 二元 (Binary): 使用标准的二进制表示法。例如,数字 5 表示为 "101"。这种表示法的长度大约是 $\log_2 N$。
- 长度的指数级差异:
- 输入长度,即我们复杂性分析中的变量 $n$,在一元表示下是 $n=N$,而在二元表示下是 $n \approx \log N$。
- 这两者之间的关系是指数的:$N = 2^{\log N} \approx 2^n$。
- 对多项式时间的影响:
- 复杂性分析的核心是判断运行时间是否是输入长度 $n$ 的多项式。
- 假设有一个算法,运行时间是 $O(N)$ 步。
- 如果输入是一元的,那么 $n=N$。运行时间 $O(N) = O(n)$,这是多项式时间的。
- 如果输入是二元的,那么 $n=\log N$。运行时间 $O(N) = O(2^n)$,这是指数时间的!
- 默认约定:
- 在计算复杂性领域,除非特别声明,否则所有数字输入都默认采用二元(或任何其他进制,如十进制,它们在对数尺度上是等价的)表示。这是一种更紧凑、更符合计算机实际存储方式的表示。
- 判断标准:
- 当输入是一个数字 $N$ 时,输入规模是 $n = O(\log N)$。
- 你的算法如果运行时间是 $N$ 的多项式,例如 $O(N^2), O(N^3)$,它被称为伪多项式时间 (Pseudo-polynomial time),但它不是真正的多项式时间,而是关于输入长度 $n$ 的指数时间。
- 只有当你的算法运行时间是 $\log N$ 的多项式,例如 $O((\log N)^2), O((\log N)^3)$,它才是真正的多项式时间算法。
💡 [数值示例]
- 输入数字 N = 1,000,000
- 一元表示: "111...1" (一百万个1)。输入长度 $n_1 = 1,000,000$。
- 二元表示: "11110100001001000000" (20位)。输入长度 $n_2 \approx 20$。
- 一个运行时间为 $O(N)$ 的算法:
- 对于一元输入,运行时间是 $O(n_1)$,多项式。
- 对于二元输入,运行时间是 $O(2^{n_2})$,指数。
⚠️ [易错点]
- 混淆 N 和 n: 这是最核心的易错点。$N$ 是数字的值,而 $n$ 是表示这个数字所需的位数(输入长度)。算法复杂性是关于 $n$ 的函数,而不是 $N$。
- 伪多项式时间: 像 $O(N \cdot |W|)$ 这类复杂性(常见于动态规划解背包问题),当 $N$ 以二元表示时,它是伪多项式时间。如果 $N$ 的值本身受输入长度的多项式限制,它就变成多项式时间了。
📝 [总结]
本段阐明了数字输入的表示方式(一元 vs. 二元)对多项式时间的判定有天壤之别。由于二元表示长度是对数级的,一个关于数字值 $N$ 的多项式 $O(N^k)$ 的算法,实际上是关于输入长度 $n=\log N$ 的指数 $O((2^n)^k)$ 算法。标准约定使用二元表示,因此真正的多项式时间算法必须是关于 $\log N$ 的多项式。
🎯 [存在目的]
本段的目的是为了揭示一个深刻且关键的技术细节,它解释了为什么一些看似高效的、基于数字值的算法(如朴素的素性测试)实际上是指数级的慢算法。这对于正确地分类数论和组合优化中的许多问题至关重要。
6.1 Composite(合数)问题
📜 [原文49]
考虑以下语言:
$$
\text { Composite }:=\{\langle N\rangle \mid N \text { is the binary representation of a composite integer } \geq 2\} .
$$
考虑以下 Composite 的判决器。
```
Algorithm 5 A decider for Composite
Input: $\langle N\rangle$ the binary representation of a number $\geq 2$.
for $2 \leq i<N$ do
If $i$ divides $N$, accept $\langle N\rangle$.
end for
Reject $\langle N\rangle$.
```
显然,上述算法当且仅当存在 $i$ 整除 $N$ 时接受。所以这确实是 Composite 的一个判决器。然而,它在 $O(N)$ 时间内运行,因为 for 循环可能执行 $O(N)$ 次。所以这不是多项式时间!即使我们将循环更改为只遍历 $\sqrt{N}$,它仍然不是多项式时间,因为 $\sqrt{N}=2^{\frac{\log N}{2}}$,所以它在 $2^{n / 2}$ 时间内运行,其中 $n$ 是输入的长度。
📖 [逐步解释]
这部分用合数问题 (Composite) 作为具体例子,来说明上一节中数字表示的重要性。
- 问题定义:
- 语言: Composite。
- 成员: 大于等于2的合数 $N$。
- 输入: 数字 $N$ 的二元表示 $\langle N \rangle$。
- 算法 5 (试除法):
- 这是一个解决该问题的确定性算法。
- 逻辑: 从 2 开始,一直到 $N-1$,逐个尝试是否能整除 $N$。
- 如果找到一个因子 $i$,立即可以确定 $N$ 是合数,接受。
- 如果循环结束都没找到因子,说明 $N$ 是素数,拒绝。
- 算法正确性: 这是判断合数的定义,算法是正确的。
- 效率分析 (关键部分):
- 运行时间 vs. N: for 循环最多执行 $N-2$ 次。每次循环做一次除法。除法的时间是 $\log N$ 的多项式。所以总时间大约是 $O(N \cdot (\log N)^k)$。为简单起见,我们关注主导项 $O(N)$。
- 运行时间 vs. n: 输入是 $\langle N \rangle$,其长度是 $n = O(\log N)$。
- 转换: 我们需要把以 $N$ 表示的时间,转换为以 $n$ 表示。因为 $n \approx \log_2 N$,所以 $N \approx 2^n$。
- 结论: $O(N)$ 的运行时间,换算成输入长度 $n$ 的函数,就是 $O(2^n)$。这是一个指数时间算法,因此它不是多项式时间的。
- 优化后的算法:
- 我们可以优化试除法,只需要检查到 $\sqrt{N}$ 即可。
- 优化后的运行时间是 $O(\sqrt{N})$。
- 但这仍然是指数时间的!因为 $\sqrt{N} = (2^n)^{1/2} = 2^{n/2}$。$O(2^{n/2})$ 仍然是指数级的。
- 引出的问题: 这个简单的确定性算法不是多项式时间的,那么 Composite 问题是否属于 P 呢?(在很长一段时间里,这是未知的)。
📝 [总结]
本段通过对 Composite 问题的朴素试除法算法的分析,生动地展示了伪多项式时间和指数时间的区别。一个运行时间为 $O(N)$ 的算法,当输入是数字 $N$ 的二进制表示时,其复杂性是指数级的,而非多项式级。这揭示了要将合数问题归入 P 类,需要的远比试除法更巧妙的算法。
🎯 [存在目的]
本段的目的是用一个非常基础和直观的数论问题,来实际演练和巩固上一节关于数字编码的理论。它让读者亲身体会到,一个看似“快”的算法(线性于 N),在严格的复杂性定义下,为什么会被判定为“慢”的(指数于输入长度 n)。
61.1 Composite 问题的 NTM 解法
📜 [原文50]
然而,这是一个简单的 NTM 来判决 Composite。
显然,此算法当且仅当 $N$ 是合数时接受。如果 $N$ 是合数,选择整除 $N$ 的 $i$ 将导致接受。如果 $N$ 不是合数,则没有 $i$ 可以整除 $N$,所以我们永远不会接受。
```
Algorithm 6 A polytime NTM for Composite
Input: $\langle N\rangle$ the binary representation of a number $\geq 2$.
Non-deterministically guess $2 \leq i<N$.
if $i$ divides $N$ then
Accept $\langle N\rangle$.
else
Reject $\langle N\rangle$.
end if
```
为什么这是多项式时间的?猜测 $i<N$ 是多项式时间的 ${ }^{3}$。检查 $i$ 是否整除 $N$ 也可以在 $\log (N)$ 的多项式时间内完成(你可以假设这一点)。所以一切都在 $O\left(\log (N)^{k}\right)$ 时间内运行,因此是多项式的。
Composite 实际上属于 P,但其算法(AKS 素数测试)极其复杂。
📖 [逐步解释]
这段展示了,尽管我们没找到合数问题的多项式时间确定性算法(在当时),但为它设计一个多项式时间非确定性算法 (NTM) 却非常容易。这证明了 Composite $\in \mathrm{NP}$。
- 算法 6 (NTM for Composite):
- 这是一个典型的“猜测并验证”算法。
- 猜测阶段: 非确定性地猜测一个介于 2 和 $N-1$ 之间的整数 $i$。这个 $i$ 就是合数的“证据”或“证书”——一个非平凡因子。
- 验证阶段: 确定性地检查 $N$ 能否被猜测出的 $i$ 整除。如果可以,接受;否则拒绝。
- 正确性分析:
- 如果 N 是合数: 根据定义,N 至少有一个在 $[2, N-1]$ 范围内的因子 $i_0$。那么,NTM 的计算树中必然有一条“幸运”的分支,恰好猜测到了这个 $i_0$。在这条分支上,验证步骤 i divides N 会成功,从而该分支接受。根据 NTM 的接受规则,整个 NTM 接受。
- 如果 N 是素数: 那么在 $[2, N-1]$ 范围内不存在任何 N 的因子。因此,无论 NTM 猜测哪个 $i$,验证步骤 i divides N 都会失败,导致该分支拒绝。既然所有分支都拒绝,整个 NTM 拒绝。
- 算法是正确的。
- 效率分析 (多项式时间):
- 输入长度: $n = O(\log N)$。
- 猜测阶段: 猜测一个数 $i$,$2 \le i < N$。这个数 $i$ 的二进制长度最多也就是 $n$ 位。NTM 可以在 $O(n)$ 的时间内猜测出这 $n$ 位。这是一个多项式时间操作。
- 验证阶段: 检查 $i$ 是否整除 $N$。做一次整数除法,标准算法的时间复杂性是两个数位数的多项式,即 $O((\log N)^k)$,也就是 $O(n^k)$。这也是多项式时间的。
- 总时间: NTM 计算树的深度(最长路径)是 $O(n) + O(n^k)$,这仍然是关于输入长度 $n$ 的多项式。
- 结论: 我们找到了一个多项式时间的 NTM,因此 Composite $\in \mathrm{NP}$。
- 一个重要的后记:
- 文中最后提到,Composite 问题实际上也属于 P。
- 2002年,三位印度科学家 Agrawal, Kayal, Saxena 提出了 AKS 素数测试,这是第一个被证明的、确定性的、无条件的、多项式时间的素性测试算法。
- 这证明了 Composite $\in \mathrm{P}$。这是一个里程碑式的发现,但其算法远比这里的 NTM 算法复杂。
📝 [总结]
本段通过为 Composite 问题设计一个简单的 NTM 算法,证明了 Composite $\in \mathrm{NP}$。这个例子清晰地展示了 NP 和 P 的区别:对于某些问题,找到一个“证据”可能很难(需要确定性地搜索),但“猜测”一个证据并验证它却可能很容易。Composite 问题曾经是这种差异的典型例子,直到 AKS 算法的出现证明了它其实也在 P 中。
🎯 [存在目的]
本段的目的是:
- 继续加深对数字表示和多项式时间的理解。
- 提供一个具体、简单的例子,展示如何应用 NTM 模型来证明一个问题属于 NP。
- 通过提及 AKS 测试,展示了计算复杂性领域的动态性:一个长期被认为可能不在 P 中的 NP 问题,也可能在某一天被证明其实就在 P 中。
🧠 [直觉心智模型]
证明 Composite $\in \mathrm{NP}$ 就像是在说:
- 要想证明一份手稿是大作家的真迹(判断 N 是素数),可能需要到处找证据,非常困难(P?)。
- 但要想证伪一份手稿(判断 N 是合数),只需要找到一个败笔(一个因子 $i$)就够了。
- NTM 的“猜测”能力,就像是拥有火眼金睛,能一眼就看到那个败笔(如果存在的话)。验证这个败笔确实是败笔($i$ 能整除 $N$)则很简单。所以“证伪”这件事(判断为合数)在 NP 中。
77 关于 SAT 的说明
📜 [原文51]
6 关于 SAT 的说明
在处理布尔公式时,我们有变量 $x_{1}, \ldots, x_{n}$。对变量 $x_{1}, \ldots, x_{n}$ 的赋值意味着我们将每个 $x_{i}$ 赋值为 0(假)或 1(真)。特别是,如果我们有 $n$ 个变量,则有 $2^{n}$ 种可能的赋值。
文字是一个布尔变量 $x$ 或其否定 $\bar{x}$(即,如果我们赋值 $x=0$ 则 $\bar{x}=1$,如果 $x=1$ 则 $\bar{x}=0$)。
子句是文字的 $\vee$(逻辑或)。例如 $C_{1}:=x_{1}, C_{2}:=\bar{x}_{1} \vee x_{3} \vee \bar{x}_{4}$ 是子句的示例。
给定对每个变量 $x_{i}$ 的 0/1 赋值,如果 $C$ 中至少有一个文字求值为 1,我们说该赋值满足一个子句 $C$。
考虑上面的子句 $C_{2}$。为了满足 $C_{2}$,我们需要至少将 $\bar{x}_{1}, x_{3}$ 或 $\bar{x}_{4}$ 中的一个设为 1。因此,如果我们赋值 $x_{1}=0$ 或 $x_{3}=1$ 或 $x_{4}=0$,则 $C_{2}$ 被满足。如果赋值将 $x_{1}=1, x_{3}=0$ 和 $x_{4}=1$ 都设为 1,则 $C_{2}$ 不被满足,因为 $C_{2}$ 的所有文字都求值为 0。
一个 CNF 公式 $\phi$ 是一系列子句的 $\wedge$。以下是一些示例:
- $\phi:=\left(x_{1}\right) \wedge\left(x_{2} \vee \bar{x}_{3}\right)$
- $\phi:=\left(x_{1}\right) \wedge\left(x_{2} \vee \bar{x}_{4} \vee x_{5}\right) \wedge\left(\bar{x}_{1} \vee \bar{x}_{4}\right)$
满足一个 CNF 公式 $\phi$ 意味着什么?如果 $\phi$ 包含变量 $x_{1}, \ldots, x_{n}$,那么我们必须将每个 $x_{i}$ 赋值为 0 或 1,以便 $\phi$ 的所有子句都被满足。有时,CNF 公式不可能被满足。
定义 12. k-CNF 公式是CNF 公式,其中所有子句最多包含 $k$ 个文字。
这是一个 3-CNF 的示例:
$$
\phi:=\left(x_{1} \vee x_{2} \vee \bar{x}_{3}\right) \wedge\left(\overline{x_{2}} \vee \overline{x_{3}} \vee \bar{x}_{4}\right) \wedge\left(x_{1} \vee x_{2} \vee x_{5}\right)
$$
一个 k-CNF 公式最多有 $\binom{2 n}{1}+\binom{2 n}{2}+\ldots+\binom{2 n}{k}$ 个子句,因为对于每个子句,我们从 $x_{1}, \bar{x}_{1}, \ldots, x_{n}, \bar{x}_{n}$ 中选择最多 $k$ 个文字。
为了检查一个 k-CNF 公式 $\phi$ 是否可满足,我们可以使用暴力算法,遍历所有可能的变量赋值。特别是,我们有 $n$ 个变量,每个变量可以取 0/1 值。有 1 种赋值所有变量都设为 0,$\binom{n}{1}$ 种赋值有 1 个变量设为 1,$\binom{n}{2}$ 种赋值有 2 个变量设为 1,等等……所以总共有:
$$
\sum_{k=0}^{n}\binom{n}{k}=2^{n} \text { many possible assignments }
$$
检查赋值是否满足 $\phi$ 意味着检查每个子句至少有一个文字求值为 1,这需要 $O(|\phi|)$ 时间。所以这种暴力算法的运行时间是 $O\left(2^{n} *|\phi|\right)$。注意,当 $|\phi|$ 是 $n$ 的多项式时(当子句的数量是变量数量的多项式时),这个运行时间是指数级的,这对于 $k$ 为常数的 k-CNF 总是如此。尽管暴力算法效率不高,但找到一个高效算法极其困难,事实上被认为是不可能的。确实,检查可满足性的多项式时间算法存在当且仅当 $\mathrm{P}=\mathrm{NP}$(因为这是一个 NP 完全问题)。
📖 [逐步解释]
这部分详细解释了布尔可满足性问题 (SAT) 的背景知识、相关术语和它的复杂性。
- 基本术语:
- 变量 (Variable): 可以取值为“真”(1)或“假”(0)的符号,如 $x_1, x_2$。
- 赋值 (Assignment): 为所有变量指定一组具体的真/假值。$n$ 个变量有 $2^n$ 种可能的赋值。
- 文字 (Literal): 一个变量或其否定,如 $x_1$ 或 $\bar{x_1}$。
- 子句 (Clause): 若干个文字通过逻辑“或”($\vee$)连接而成。例如 $(x_1 \vee \bar{x_2})$。一个子句被满足,当且仅当其中至少有一个文字为真。
- 合取范式 (CNF Formula): 若干个子句通过逻辑“与”($\wedge$)连接而成。例如 $(x_1 \vee \bar{x_2}) \wedge (\bar{x_1} \vee x_2)$。一个 CNF 公式被满足,当且仅当它的所有子句都被满足。
- SAT 问题:
- 输入: 一个 CNF 公式 $\phi$。
- 问题: 是否存在一个对变量的赋值,使得 $\phi$ 被满足?
- k-CNF 和 k-SAT:
- k-CNF 公式: 每个子句中文字的数量不超过 $k$。
- 3-CNF 就是一个例子,每个子句有1、2或3个文字。
- k-SAT 问题就是输入被限制为 k-CNF 公式的 SAT 问题。
- 暴力解法:
- 思路: 遍历所有 $2^n$ 种可能的变量赋值。
- 对于每一种赋值:
- 总运行时间: $O(2^n \cdot |\phi|)$。
- 暴力解法的复杂性:
- 运行时间是 $2^n$ 的指数函数,所以这是一个指数时间算法。
- 对于 k-CNF,子句数量 $|\phi|$ 是 $n$ 的多项式,所以总时间仍然是指数级的。
- SAT 的核心地位:
- 尽管暴力解法很慢,但至今无人找到多项式时间的确定性解法。
- SAT (特别是 3-SAT) 是第一个被证明的 NP-完全问题。
- 这意味着,如果能为 SAT 找到一个多项式时间算法,那么 $\mathrm{P}=\mathrm{NP}$。反之,如果能证明 SAT 不存在多项式时间算法,那么 $\mathrm{P} \neq \mathrm{NP}$。
📝 [总结]
本段为 SAT 问题提供了全面的背景介绍。它定义了从变量到 CNF 公式的一系列核心概念,解释了如何判断一个公式是否被满足。然后,它分析了解决 SAT 问题的暴力搜索算法,指出了其指数级的复杂性。最重要的是,它强调了 SAT 作为第一个也是最核心的 NP-完全问题的历史地位和理论意义。
🎯 [存在目的]
本段的目的是为了让读者在讨论归约和 NP-完全性时,对那个“万物之源”的 NP-完全问题——SAT——有一个扎实、具体、无歧义的理解。没有对 SAT 的清晰认识,就无法理解 Cook-Levin 定理的意义,也无法理解后续从 SAT 出发的各种归约构造。
7.1 SAT 示例
📜 [原文52]
示例 4.
$$
\phi:=\left(x_{1} \vee x_{2}\right) \wedge\left(x_{2} \vee x_{3}\right) \wedge\left(\bar{x}_{3} \vee \bar{x}_{1}\right) \wedge\left(\bar{x}_{4} \vee \bar{x}_{2}\right) \wedge x_{4}
$$
| $\begin{array}{llll}x_{1} & x_{2} & x_{3} & x_{4}\end{array}$ | $x_{1} \vee x_{2}$ | $x_{2} \vee x_{3}$ | $\bar{x}_{3} \vee \bar{x}_{1}$ | $\bar{x}_{4} \quad \vee \bar{x}_{2}$ | $x_{4}$ | $\phi$ |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 0 | 0 | 0 | 1 | 1 | 0 | 0 |
| 0 | 0 | 0 | 1 | 1 | 1 | 0 |
|...|...|...|...|...|...|...|
| 1 | 1 | 1 | 0 | 0 | 1 | 0 |
你可以看到,对于所有真值赋值,我们总是有一个子句求值为 0(没有文字设为真),因此 $\phi$ 不可满足。
📖 [逐步解释]
这部分通过一个具体的例子和一张(部分展示的)真值表,来演示一个 CNF 公式是如何被判定为“不可满足”的。
- 给定的公式 $\phi$:
- $\phi = (x_1 \lor x_2) \land (x_2 \lor x_3) \land (\bar{x}_3 \lor \bar{x}_1) \land (\bar{x}_4 \lor \bar{x}_2) \land x_4$
- 这是一个有 5 个子句和 4 个变量的 CNF 公式。
- 分析过程 (手工推导):
- 我们要寻找一个赋值 $(x_1, x_2, x_3, x_4)$ 让所有子句都为真。
- 从最简单的子句开始:第五个子句是 $(x_4)$。为了满足它,必须有 $x_4=1$。这立刻将我们的搜索空间从 $2^4=16$ 种可能减少到了 $2^3=8$ 种。
- 现在我们知道 $x_4=1$。看第四个子句 $(\bar{x}_4 \lor \bar{x}_2)$。因为 $x_4=1$,所以 $\bar{x}_4=0$。为了满足这个子句,必须有 $\bar{x}_2=1$,即 $x_2=0$。搜索空间又减少到 $2^2=4$ 种。
- 现在我们知道 $x_4=1, x_2=0$。看第一个子句 $(x_1 \lor x_2)$。因为 $x_2=0$,为了满足它,必须有 $x_1=1$。搜索空间减少到 $2^1=2$ 种。
- 现在我们知道 $x_4=1, x_2=0, x_1=1$。看第二个子句 $(x_2 \lor x_3)$。因为 $x_2=0$,为了满足它,必须有 $x_3=1$。搜索空间只剩下 1 种可能赋值。
- 唯一的候选解: 我们推导出,如果存在解,那它必须是 $(x_1, x_2, x_3, x_4) = (1, 0, 1, 1)$。
- 最后验证: 我们用这个唯一的候选解去检查所有子句,特别是我们还没用过的第三个子句 $(\bar{x}_3 \lor \bar{x}_1)$。
- 代入 $x_1=1, x_3=1$,我们得到 $(\bar{1} \lor \bar{1}) = (0 \lor 0) = 0$。
- 这个子句没有被满足!
- 结论:
- 因为唯一可能满足其他四个子句的赋值,却无法满足第三个子句,所以不存在任何赋值可以同时满足所有五个子句。
- 因此,这个公式 $\phi$ 是不可满足的 (unsatisfiable)。
- 真值表:
- 原文中的表格代表了暴力搜索方法,即列出所有 $2^4=16$ 种赋值,然后逐行计算每个子句的真值和整个公式 $\phi$ 的真值。
- 最后一列 $\phi$ 是前五列(代表五个子句的真值)的逻辑“与”。只有当一行的前五列全为 1 时,$\phi$ 列才为 1。
- 作者指出,如果你填完整个表格,会发现 $\phi$ 这一列永远是 0。这直观地证明了公式的不可满足性。
📝 [总结]
这个例子通过一个具体的、不可满足的 CNF 公式,演示了满足性检查的思维过程。它展示了两种方法:一是通过逻辑推导(链式反应)来快速找到矛盾;二是通过构建完整的真值表(暴力搜索)来系统地验证。最终结论是,该公式不可满足。
🎯 [存在目的]
本段的目的是为了让读者对“不可满足”这个概念有一个具体的实例印象。通过手工推导和真值表的展示,它将 SAT 问题从一个抽象的定义,变成了一个可以动手操作和验证的具体谜题,加深了读者对满足性判断过程的理解。
88 练习
📜 [原文53]
7 练习
练习 1 (判断对错).
1. 所有 $n * \log (n), n^{100}+3 n^{2}, n^{0.001 n}, 2^{\sqrt{n}}$ 都是多项式的。
2. 如果你为语言 $L$ 展示了一个验证器 $V(x, c)$,其中 $V$ 在 $|x|+|c|$ 的多项式时间内运行,那么 $L$ 必须属于 NP。
3. 所有 NP 完全问题都可以多项式时间归约到彼此。
4. 假设图灵机的输入是二元表示的整数 $N$,并且 $M$ 在 $O\left(N^{2}\right)$ 时间内运行,那么 $M$ 在多项式时间内运行。
练习 2. 证明 NP 在并集下是封闭的。也就是说,如果 $L_{1}, L_{2} \in \mathrm{NP}$,那么 $L_{1} \cup L_{2} \in \mathrm{NP}$。
练习 3.
- 固定某个常数 $k$。k-团问题定义如下:给定一个图 $G$ 作为输入,$G$ 是一个包含 $n$ 个节点的图。你可以将 $G$ 视为由一个 $n \times n$ 二元矩阵 $A$ 描述,其中 $A_{i, j}=1$ 当且仅当 $G$ 中存在边 $(i, j)$(因此输入大小为 $n^{2}$)。当且仅当 $G$ 存在一个包含 $k$ 个顶点的子集 $V^{*}$,使得这些顶点构成一个完全图(任意两个节点之间都有一条边)时,你必须接受 $G$。为什么这个问题属于 P?
- NP 完全问题 团定义如下:给定一个图 $G$ 作为输入,$G$ 是一个包含 $n$ 个节点的图。但现在 $k$ 以十进制形式作为输入提供给你。(因此输入大小大约是 $\left(n^{2}+\log _{10}(k)\right)$)。同样,当且仅当 $G$ 存在一个包含 $k$ 个顶点的子集 $V^{*}$,使得这些顶点构成一个完全图时,你必须接受 $G$。为什么之前的证明无法证明这个问题属于 P?
练习 4. 书中问题 7.18。证明如果 $\mathrm{P}=\mathrm{NP}$,那么除了 $A=\emptyset$ 和 $A=\Sigma^{*}$ 之外,所有属于 $\mathrm{P}$ 的语言 $A$ 都是 NP 完全的。(提示:考虑 $L \leq_{\mathrm{P}} A$ 的定义,已知 $L \in \mathrm{NP}$ 意味着 $L \in \mathrm{P}$。)
练习 5. 书中问题 7.21。设 $G$ 表示一个无向图。并且设
- SPATH $=\{\langle G, a, b, k\rangle-G$ 包含一条从 $a$ 到 $b$ 长度至多为 $k$ 的简单路径 $\}$
- LPATH $=\{\langle G, a, b, k\rangle-G$ 包含一条从 $a$ 到 $b$ 长度至少为 $k$ 的简单路径 $\}$
证明 SPATH $\in \mathrm{P}$ 且 LPATH 是 NP 完全的。(简单路径是不重复访问同一节点的路径。)
8.1 练习 1 解答
📖 [逐步解释]
- "所有 $n \log n, n^{100}+3n^2, n^{0.001n}, 2^{\sqrt{n}}$ 都是多项式的。"
- 解答: 错。
- 解释:
- $n \log n = O(n^2)$,所以它是多项式的。
- $n^{100}+3n^2 = O(n^{100})$,所以它是多项式的。
- $n^{0.001n} = (n^{0.001})^n$。这个函数的指数部分包含 $n$,它的增长速度远超任何多项式 $n^k$。它不是多项式的。
- $2^{\sqrt{n}}$。这是一个次指数函数,但它仍然比任何多项式 $n^k$ 增长得快。为了看清这一点,可以取对数:$\log(2^{\sqrt{n}}) = \sqrt{n}$,而 $\log(n^k) = k \log n$。因为 $\sqrt{n}$ 比 $k \log n$ 增长得快,所以 $2^{\sqrt{n}}$ 比 $n^k$ 增长得快。它不是多项式的。
- "如果你为语言 L 展示了一个验证器 V(x, c),其中 V 在 $|x|+|c|$ 的多项式时间内运行,那么 L 必须属于 NP。"
- 解答: 错。
- 解释: NP 的验证器定义要求验证器 $V$ 在关于原始输入 $|x|$ 的多项式时间内运行。如果证书 $|c|$ 的长度是 $|x|$ 的指数级(例如 $|c|=2^{|x|}$),那么 $|x|+|c|$ 的多项式 $O((|x|+2^{|x|})^k)$ 就不再是 $|x|$ 的多项式了。一个有效的 NP 验证器必须能处理一个多项式长度的证书,并在关于 $|x|$ 的多项式时间内完成验证。
- "所有 NP 完全问题都可以多项式时间归约到彼此。"
- 解答: 对。
- 解释:
- 设 A 和 B 是两个 NP-完全问题。
- 证明 $A \le_P B$: 因为 B 是 NP-完全的,所以它是 NP-难的。根据 NP-难的定义,任何 NP 问题都可以多项式时间归约到 B。因为 A 是 NP-完全的,所以 A 也在 NP 中。因此,$A \le_P B$。
- 证明 $B \le_P A$: 因为 A 是 NP-完全的,所以它是 NP-难的。根据 NP-难的定义,任何 NP 问题都可以多项-式时间归约到 A。因为 B 是 NP-完全的,所以 B 也在 NP 中。因此,$B \le_P A$。
- 结论成立。
- "假设图灵机的输入是二元表示的整数 N,并且 M 在 $O(N^2)$ 时间内运行,那么 M 在多项式时间内运行。"
- 解答: 错。
- 解释: 这是关于数字表示的经典陷阱。多项式时间是关于输入长度 $n$ 的多项式。对于二元表示的整数 $N$,输入长度是 $n = O(\log N)$。因此,运行时间 $O(N^2) = O((2^n)^2) = O(2^{2n})$。这是一个关于输入长度 $n$ 的指数时间,而不是多项式时间。
8.2 练习 2 解答
📖 [逐步解释]
目标: 证明如果 $L_1, L_2 \in \mathrm{NP}$,那么 $L_1 \cup L_2 \in \mathrm{NP}$。
证明 (使用验证器模型):
- 已知条件:
- 因为 $L_1 \in \mathrm{NP}$,所以存在一个多项式时间验证器 $V_1(x, c_1)$ 和一个多项式 $p_1$ 使得 $|c_1| \le p_1(|x|)$。
- 因为 $L_2 \in \mathrm{NP}$,所以存在一个多项式时间验证器 $V_2(x, c_2)$ 和一个多项式 $p_2$ 使得 $|c_2| \le p_2(|x|)$。
- 构造 $L_1 \cup L_2$ 的验证器 $V_{union}$:
- 证书: 我们为 $L_1 \cup L_2$ 定义一个新的证书 $c_{union}$。这个证书的结构是一个序对,例如 (b, c),其中 b 是一个比特 (0 或 1),c 是原始的证书。
- 验证器 $V_{union}(x, c_{union})$ 的算法:
- 证明正确性:
- 如果 $x \in L_1 \cup L_2$:
- 情况一: $x \in L_1$。那么存在一个证书 $c_1$ 使得 $V_1(x, c_1)$ 接受。我们可以构造一个新的证书 $c_{union} = (0, c_1)$。当 $V_{union}$ 收到这个证书时,它会执行 $V_1(x, c_1)$ 并接受。所以存在一个能让 $V_{union}$ 接受的证书。
- 情况二: $x \in L_2$。同理,存在一个证书 $c_2$ 使得 $V_2(x, c_2)$ 接受。我们可以构造 $c_{union} = (1, c_2)$,它会让 $V_{union}$ 接受。
- 在任何情况下,只要 $x \in L_1 \cup L_2$,就存在一个让 $V_{union}$ 接受的证书。
- 如果 $x \notin L_1 \cup L_2$:
- 这意味着 $x \notin L_1$ 且 $x \notin L_2$。
- 那么,对于所有的 $c_1$,$V_1(x, c_1)$ 都会拒绝。对于所有的 $c_2$,$V_2(x, c_2)$ 都会拒绝。
- 无论我们提供给 $V_{union}$ 的证书 $c_{union}=(b,c)$ 是什么,它要么调用 $V_1$(被拒绝),要么调用 $V_2$(也被拒绝)。
- 因此,对于所有的 $c_{union}$,$V_{union}$ 都会拒绝。
- 证明效率:
- 证书长度: $|c_{union}| = 1 + |c|$。$|c|$ 的长度是 $|c_1|$ 或 $|c_2|$,以较长的为准,即 $\max(p_1(|x|), p_2(|x|))$。这是一个多项式。所以新证书的长度也是多项式的。
- 运行时间: $V_{union}$ 的时间是解析证书的时间(可忽略)加上运行 $V_1$ 或 $V_2$ 的时间。$V_1$ 和 $V_2$ 都是多项式时间的。所以 $V_{union}$ 也是多项式时间的。
结论: 我们为 $L_1 \cup L_2$ 找到了一个多项式时间验证器,因此 $L_1 \cup L_2 \in \mathrm{NP}$。NP 在并集下是封闭的。
8.3 练习 3 解答
📖 [逐步解释]
- 关键点: 在这个问题中,$k$ 是一个固定的常数,不是输入的一部分。例如,问题可能是“5-团问题”或“10-团问题”。
- 算法:
a. 遍历图中所有大小为 $k$ 的顶点子集。
b. 对于每个子集,检查它是否构成一个团(即检查子集中 $\binom{k}{2}$ 对顶点之间是否都有边)。
c. 如果找到一个团,接受。
d. 如果遍历完所有子集都没找到,拒绝。
- 效率分析:
a. 步骤 a 的次数: 共有 $\binom{n}{k}$ 个大小为 $k$ 的子集。因为 $k$ 是一个常数,所以 $\binom{n}{k} = O(n^k)$,这是一个关于 $n$ 的多项式。
b. 步骤 b 的时间: 检查一个大小为 $k$ 的子集需要检查 $\binom{k}{2}$ 条边。因为 $k$ 是常数,所以这也是一个常数时间的操作。
c. 总时间: $O(n^k) \times (\text{常数}) = O(n^k)$。
- 结论: 由于 $k$ 是常数,算法的运行时间是多项式的。因此,k-团问题属于 P。
- 关键区别: 在 NP-完全的 团 (Clique) 问题中, $k$ 是输入的一部分。
- 算法分析失效: 我们仍然可以使用相同的暴力搜索算法。但是,它的运行时间是 $O(\binom{n}{k})$。
- 为什么不再是多项式: 因为 $k$ 不再是常数,它可以和 $n$ 一样大。例如,考虑 $k=n/2$ 的情况。$\binom{n}{n/2}$ 是一个指数级的函数 (大约是 $2^n/\sqrt{\pi n/2}$)。$O(n^k)$ 这种表示法在这里也不再是多项式,因为指数 $k$ 是一个变量。
- 结论: 当 $k$ 成为输入的一部分时,暴力搜索算法的运行时间是指数级的,因此这个算法无法证明问题属于 P。这并不证明它不属于 P,只是说这个特定的算法不够好。NP-完全的证明需要通过归约来表明,可能不存在任何多项式时间算法。
8.4 练习 4 解答
📖 [逐步解释]
目标: 证明如果 $\mathrm{P}=\mathrm{NP}$,那么任何语言 $A \in \mathrm{P}$ (且 $A \neq \emptyset, A \neq \Sigma^*$) 都是 NP-完全的。
证明:
- NP-完全的两个条件: 我们需要证明 A 满足:
a. $A \in \mathrm{NP}$
b. $A$ 是 NP-难的
- 证明 a. $A \in \mathrm{NP}$:
- 我们已知 $A \in \mathrm{P}$。
- 我们也知道 $\mathrm{P} \subseteq \mathrm{NP}$ (定理 2)。
- 因此,$A \in \mathrm{NP}$ 自动成立。
- 证明 b. $A$ 是 NP-难的:
- 目标: 根据定义,我们需要证明对于任意一个语言 $L \in \mathrm{NP}$,都有 $L \le_P A$。
- 构造归约 $f$ 从 L 到 A:
- 存在一个字符串 $y_{yes}$ 使得 $y_{yes} \in A$。
- 存在一个字符串 $y_{no}$ 使得 $y_{no} \notin A$。
- 输入: $L$ 的一个实例 $x$。
- 算法:
- 验证归约 $f$:
- $f$ 是多项式时间的吗?: 是。计算 $f(x)$ 的过程就是运行多项式时间的算法 $M_L$,然后输出一个固定长度的字符串。整个过程是多项式时间的。
- $f$ 是否保持答案一致性?:
- 如果 $x \in L$: $M_L$ 接受 $x$,所以 $f(x) = y_{yes}$。我们已知 $y_{yes} \in A$。所以 $x \in L \implies f(x) \in A$。
- 如果 $x \notin L$: $M_L$ 拒绝 $x$,所以 $f(x) = y_{no}$。我们已知 $y_{no} \notin A$。所以 $x \notin L \implies f(x) \notin A$。
- 这两点合起来就是 $x \in L \Longleftrightarrow f(x) \in A$。
- 结论:
- 我们已经成功地为任意 $L \in \mathrm{NP}$ 构造了一个多项式时间归约 $L \le_P A$。
- 因此,$A$ 是 NP-难的。
- 最终结论: 因为 $A$ 既属于 NP 又属于 NP-难,所以 $A$ 是 NP-完全的。这个结论适用于任何非平凡的 P 类问题。
为什么要求 $A \neq \emptyset$ 和 $A \neq \Sigma^*$?
- 如果 $A = \emptyset$,那么不存在 $y_{yes} \in A$,我们的归约构造不成立。
- 如果 $A = \Sigma^*$,那么不存在 $y_{no} \notin A$,我们的归约构造也不成立。
这两个平凡的语言无法成为归约的目标。
8.5 练习 5 解答
📖 [逐步解释]
Part 1: 证明 SPATH $\in \mathrm{P}$
- SPATH (Shortest Path): 是否存在一条从 $a$ 到 $b$ 的简单路径(不重复顶点),其长度至多为 $k$。
- 长度在这里指边的数量。
- 算法思路: 我们可以使用一个修改版的广度优先搜索 (BFS)。BFS 天然地按路径长度(边的数量)层层扩展,找到的是最短路径。
- 算法:
- 创建一个队列 Q,初始时放入 (a, path=[a]),其中 path 记录了当前路径。
- 创建一个集合 visited 来防止在同一条路径中重复访问节点,但注意我们需要允许不同的路径访问相同的节点。一个更好的方法是记录 (vertex, length) 的访问状态。
- 修改后的 BFS:
- 队列中存储 (current_node, current_path_length)。
- 从 (a, 0) 开始。
- 当从队列中取出 (u, len_u):
- 如果 len_u >= k,则不再从 $u$ 扩展,因为路径已经太长了。
- 对于 $u$ 的每个邻居 $v$:
- 如果 $v$ 就是 $b$,我们找到了一条长度为 len_u + 1 的路径。因为 BFS 按层搜索,这是找到的第一条路径,它一定是最短的。如果 len_u + 1 <= k,则接受。
- 如果 $v$ 不是 $b$,则将 (v, len_u + 1) 加入队列。
- 一个更简单的图论算法: 在图 $G$ 上运行标准的 BFS/Dijkstra 算法,从源点 $a$ 开始,计算到所有其他节点的最短距离 dist[]。这个算法是多项式时间的(BFS 是 $O(V+E)$)。然后,只需检查 dist[b] 是否小于等于 $k$ 即可。因为 BFS/Dijkstra 找到的路径本身就是简单路径(在无负权重的图中),所以这个解法是正确的。
- 结论: 存在多项式时间算法(如 BFS)可以解决 SPATH。因此 SPATH $\in \mathrm{P}$。
Part 2: 证明 LPATH 是 NP-完全的
- LPATH (Longest Path): 是否存在一条从 $a$ 到 $b$ 的简单路径,其长度至少为 $k$。
- NP-完全证明步骤:
- 证明 LPATH $\in \mathrm{NP}$:
- 证书: 一条具体的路径,即一个顶点序列 $c = (v_0, v_1, \dots, v_m)$。
- 验证器:
- 效率: 所有这些检查都可以在路径长度的多项式时间内完成。路径长度最多为 $|V|$。因此,这是一个多项式时间验证器。
- 结论: LPATH $\in \mathrm{NP}$。
- 证明 LPATH 是 NP-难的:
- 选择源问题: 我们选择已知的 NP-完全问题 哈密顿路径 (HAMPATH)。
- HAMPATH 定义: 给定图 $G$ 和两个顶点 $s, t$,是否存在一条经过所有 $|V|$ 个顶点的简单路径从 $s$ 到 $t$?一条经过所有顶点的简单路径,其长度(边数)恰好是 $|V|-1$。
- 归约: 从 HAMPATH 归约到 LPATH。
- 输入: HAMPATH 的一个实例 $\langle G, s, t \rangle$。
- 归约函数 f: $f(\langle G, s, t \rangle) = \langle G, s, t, |V|-1 \rangle$。
- 解释: 我们将 HAMPATH 实例直接转化为一个 LPATH 实例,图和起点/终点都不变,只是将目标长度 $k$ 设定为 $|V|-1$。
- f 是多项式时间: 是。这个构造只是计算了 $|V|-1$,是 $O(1)$ 的。
- 证明正确性 ($x \in A \iff f(x) \in B$):
- ($\Rightarrow$): 假设在 $G$ 中存在一条从 $s$ 到 $t$ 的哈密顿路径。根据定义,这条路径是简单的,且长度恰好为 $|V|-1$。因此,它满足 LPATH 问题中“长度至少为 $|V|-1$”的要求。所以 $f$ 的输出是一个“是”实例。
- ($\Leftarrow$): 假设 LPATH 实例 $\langle G, s, t, |V|-1 \rangle$ 的答案是“是”。这意味着在 $G$ 中存在一条从 $s$ 到 $t$ 的简单路径,其长度至少为 $|V|-1$。因为简单路径不能重复顶点,一条路径最多只能包含 $|V|$ 个顶点,其长度最多为 $|V|-1$。所以,这条路径的长度恰好是 $|V|-1$。一条包含 $|V|$ 个顶点的简单路径,就是哈密顿路径。所以原始的 HAMPATH 实例是“是”。
- 结论: HAMPATH $\le_P$ LPATH。因为 HAMPATH 是 NP-难的,所以 LPATH 也是 NP-难的。
- 最终结论: 因为 LPATH 既属于 NP 又是 NP-难的,所以 LPATH 是 NP-完全的。
99 行间公式索引
1. 定义1:运行时间函数
$$
t(n):=\max _{x \in \Sigma^{*},|x|=n} \text { number of steps } M \text { takes before it halts on } x
$$
解释: 运行时间 $t(n)$ 定义为图灵机 $M$ 在处理所有长度为 $n$ 的输入中,所需的最大步骤数。
2. 大O符号定义
$$
f(n) \leq c \cdot g(n)
$$
解释: $f(n)=O(g(n))$ 的核心条件,表示当 $n$ 足够大时,$f(n)$ 的增长被 $g(n)$ 的常数倍所限制。
3. TIME(t(n)) 复杂性类
$$
\operatorname{Time}(t(n)):=\{L \mid L \text { is a language decided by an } O(t(n)) \text { time Turing machine }\}
$$
解释: 复杂性类 $\operatorname{Time}(t(n))$ 是所有能被运行时间为 $O(t(n))$ 的确定性图灵机判决的语言的集合。
4. 复杂性类 P 的定义
$$
\mathrm{P}:=\bigcup_{k \in N} \operatorname{Time}\left(n^{k}\right)
$$
解释: 复杂性类 P 是所有多项式时间复杂性类的并集,代表所有能被确定性算法在多项式时间内解决的问题。
5. 复杂性类 NP 的定义
$$
\mathrm{NP}:=\bigcup_{k \in N} \mathrm{NTime}\left(n^{k}\right)
$$
解释: 复杂性类 NP 是所有非确定性多项式时间复杂性类的并集,代表所有能被非确定性算法在多项式时间内解决的问题。
6. 基于验证器的 NP 定义
$$
x \in L \leftrightarrow \exists c \text { such that } V(x, c) \text { accepts. }
$$
解释: 一个问题 $x$ 属于语言 $L$,当且仅当存在一个“证书” $c$,能让验证器 $V$ 接受。
7. 多项式时间映射可归约定义
$$
x \in A \Longleftrightarrow f(x) \in B
$$
解释: 归约的核心条件,表示一个高效的转换函数 $f$ 必须保持问题 A 的实例 $x$ 和问题 B 的实例 $f(x)$ 的“是/否”答案完全一致。
8. 3-CNF 公式示例
$$
\phi:=\left(x_{1} \vee x_{2} \vee \bar{x}_{3}\right) \wedge\left(\overline{x_{2}} \vee \overline{x_{3}} \vee \bar{x}_{4}\right) \wedge\left(x_{1} \vee x_{2} \vee x_{5}\right)
$$
解释: 这是一个 3-CNF 公式的例子,它由三个子句通过逻辑“与”连接而成,每个子句包含三个文字。
9. 变量赋值总数
$$
\sum_{k=0}^{n}\binom{n}{k}=2^{n} \text { many possible assignments }
$$
解释: 这个公式(二项式定理)说明了对于 $n$ 个布尔变量,总共有 $2^n$ 种不同的真/假赋值组合。
10. SAT 示例公式
$$
\phi:=\left(x_{1} \vee x_{2}\right) \wedge\left(x_{2} \vee x_{3}\right) \wedge\left(\bar{x}_{3} \vee \bar{x}_{1}\right) \wedge\left(\bar{x}_{4} \vee \bar{x}_{2}\right) \wedge x_{4}
$$
解释: 这是练习中给出的一个具体的、将被证明为不可满足的 CNF 公式。
[[END_OF_RESPONSE]]所有解释内容已全部输出完毕。