☰
决策计算:从正则语言到停机问题,划出算法的边界
2026/10/22 14:16:40 网站建设 项目流程

如果你让我用一句话总结《计算理论导论(决策计算与语言)》这门课带给我的改变,那就是:从今往后,每当有人跟我说“这个需求写个算法就能搞定”,我脑子里会先蹦出一个问题——这个问题可判定吗?

这门课的核心主线就是“决策计算”,也就是判定问题:给定一个实例,程序能不能在有限的步数内回答“是”或者“否”。标题里的 Decision Computation 翻译成“决策计算”,说的就是这个意思。这篇文章是系列的第二篇,上一篇我们沿着正则语言的版图走了一圈,梳理了有限自动机、正则表达式以及它们之间的联系。这一篇我们继续往上爬:从正则语言的边界出发,进入上下文无关语言和下推自动机,再一路走到图灵机和可判定性理论。

整条路线有一个很清晰的逻辑:语言越来越复杂,你需要的计算模型就必须拥有越来越强的记忆能力。正则语言只靠有限状态,上下文无关语言需要一个栈,到了图灵机则变成一条无限纸带。理解这条升级路线,比记住任何一条定理都更有价值。

1. 正则语言的边界:为什么“有限状态”撑不住嵌套结构

1.1 泵引理:先把“正则”扳倒再谈升级

在上一篇里,DFA、NFA、正则表达式这三兄弟被证明是等价的,能表达的语言范围完全一致。这套模型有一个天然短板:状态数量是有限的,而输入串可以无限长。你可以把 DFA 想象成一条只有几个房间的走廊,走再长的路,你也只能在这些房间之间来回穿梭,不可能凭空“记住”无穷多的历史信息。

泵引理(Pumping Lemma)就是把这种直觉变成一把精确的尺子。它的核心论断是:如果一个语言 L 是正则的,那么一定存在一个正整数 p,使得任意长度超过 p 的字符串 s ∈ L,都能被切成三段 s = xyz,满足三个条件:

|xy| ≤ p,且 |y| ≥ 1,并且对任意 i ≥ 0,字符串 x y^i z 仍然属于 L。

翻译成人话就是:因为 DFA 只有有限个状态,处理长度超过状态数的输入时,必然会有一个状态被“重复访问”。这一段重复的路径就是 y,你可以把这段路径循环任意多次,DFA 根本察觉不到。

这套工具最常见的用途是证明某个语言不是正则的,比如语言 L = { aⁿbⁿ | n ≥ 0 }。假设它是正则的,取泵长度 p,再取 s = aᵖbᵖ。因为 |xy| ≤ p,而 s 的前 p 个字符全是 a,所以 y 必然完全落在 aᵖ 这一段里,不妨设 y = aᵏ,k ≥ 1。现在把 y 复制一遍,得到 x y² z,里面的 a 变成了 p+k 个,b 还是 p 个,显然 a 的数量不等于 b 的数量,这个字符串就不在 L 里,矛盾。

我第一次看完这个证明的感受是:原来“证明一个语言不能用正则表达”不是靠直觉,而是有一套机械化的操作流程。后来实际做开发,看到不少人试图用正则表达式去解析嵌套的括号、HTML 标签、JSON,然后写出一个三四屏的“正则怪物”,我就知道他们一定没被泵引理教育过。遇到需要数数、需要配对的场景,正则这一步就该停下了。

1.2 上下文无关语言:当嵌套结构出现的时候

正则语言描述不了“嵌套”“配对”这类结构,但现实世界里到处是嵌套。算术表达式里括号套括号,前端代码里 div 套 span,文本里引号里面还有转义引号。这些结构的共同点是什么?需要一个“记忆”来记录当前嵌套到第几层、以及每一层分别是什么类型。

形式语言理论把这些语言按表达能力分成了四级。最底层就是我们刚刚讨论的 3 型语言,也就是正则语言;往上是 2 型语言,叫作上下文无关语言;再往上是 1 型和 0 型。这个谱系不是人为划分的,而是由每种语言对应的计算模型决定的:正则语言对应有限自动机,上下文无关语言对应下推自动机。

举几个典型例子:回文串的集合 { wwᴿ | w ∈ Σ* },它不是正则的,但它是上下文无关的;平衡括号串,它也不是正则的,但它也是上下文无关的。你在编译器课上背过的那些文法规则,比如E -> E + T | T,本质上就是在用“上下文无关文法(CFG)”来描述一种结构化的嵌套语言。

所以到了这一步,两个问题摆在面前:第一,上下文无关语言长什么样?第二,什么样的机器能识别它?答案分别是“文法”和“下推自动机”。下面我们一个一个看。

2. 下推自动机:给自动机配一根无限长的草稿纸

2.1 文法推导比背定义重要:CFG 的读写直觉

上下文无关文法的定义并不复杂,它由四部分组成:非终结符集合 V、终结符集合 Σ、产生式规则 R、以及一个开始符号 S。我们通常写 G = (V, Σ, R, S)。每条产生式的左边是单个非终结符,右边是一串由终结符和非终结符组成的符号。

比如下面的文法:

S -> a S b S -> ε

它描述的语言是什么?从 S 出发,可以一直套 a S b,最后用 ε 收尾,得到的串是 aⁿbⁿ。这个文法只有两条规则,表达能力却超过了前面提到的所有正则模型。道理很简单:非终结符可以被“递归地展开”,而递归恰恰是嵌套结构的数学表达。

光看定义没意思,关键是要会“推”。推导的过程就是从开始符号出发,不断把非终结符替换成产生式右边。还是上面的文法,推一个 aabb:

S => a S b => a a S b b => a a ε b b => aabb

每一步替换一个非终结符,这就是最左推导。如果把推导过程画成一棵树,每片叶子是终结符、每个内部节点是产生式的应用,这样一棵语法树能把“结构”直观地呈现出来。这也是语法分析器的雏形。

再举一个稍微现实的例子。描述算术表达式的文法,如果写成E -> E + E | E * E | ( E ) | id,会有一个老问题:比如 id + id * id,既可以先算加法再算乘法,也可以先算乘法再算加法,一棵语法分析树就有两种,这就是文法的二义性。解决二义性的常用手段是引入优先级分层:

E -> E + T | T T -> T * F | F F -> ( E ) | id

这样一来,乘法的优先级高于加法,语法树就只有一棵了。理解这个例子比背诵“什么是二义性”有价值得多,因为你在写表达式解析器的时候,实际要解决的就是这种问题。

2.2 手工设计一个 PDA:aⁿbⁿ 的完整状态机

下推自动机(Pushdown Automaton, PDA),说白了就是在 NFA 的基础上加一个栈。栈可以无限增长,但每次只能访问栈顶。这个“无限”很关键,它让自动机拥有了某种程度的记忆能力。

形式化地看,PDA 是一个六元组:状态集合 Q、输入字母表 Σ、栈字母表 Γ、转移函数 δ、开始状态 q0、以及栈底符号 Z0。转移函数的输入是(当前状态、读到的输入字符或 ε、栈顶符号),输出是(新状态、要压入的字符串)。这里允许 ε 转移,意味着机器可以在不消耗输入的情况下操作栈。

直接用经典的例子:设计一个 PDA 识别 L = { aⁿbⁿ | n ≥ 0 }。思路是:读到一个 a 就往栈里压一个 A,读到一个 b 就从栈里弹出一个 A,如果结束时栈恰好恢复到只剩栈底符号,就说明 a 和 b 的数量相等。

可以设计成这几个核心状态:

q0: 读 a,压入 A(连续读 a 就连续压) q0: 第一个 b 到来时,弹出 A,进入 q1 q1: 继续读 b,每读一个 b 弹出 A q1: 输入结束且栈顶只剩 Z0,进入接受态 q2

当我们把状态写细致一点:

(q0, a, Z0) → (q0, A Z0) (q0, a, A) → (q0, A A) (q0, b, A) → (q1, ε) (q1, b, A) → (q1, ε) (q1, ε, Z0) → (q2, Z0)

这里我把栈顶写在左边,A Z0表示压入 A 后栈内从顶到底为 A、Z0。逐条解释一下:

  • 第一条规则:在 q0,读入 a,栈底是 Z0,压入一个 A。
  • 第二条规则:在 q0,读入 a,栈顶已经有一个 A,再压入一个 A。
  • 第三条规则:在 q0,第一次读入 b,弹出一个 A,进入 q1。
  • 第四条规则:在 q1,继续读 b,每读一个,弹一个 A。
  • 第五条规则:在 q1,输入全部读完,栈顶露出 Z0,转移到接受状态 q2。

拿输入 aabb 手工走一遍:读 a,压 A;读 a,再压 A,此时栈里是 A A Z0;读 b,弹 A,剩 A Z0;读 b,弹 A,剩 Z0;输入读完,q1 看到栈顶 Z0,进入 q2,接受。如果输入是 aab,走到最后剩 A Z0,第五条规则不匹配,机器卡住,不接受。如果输入是 abb,读第二个 b 时栈顶已经是 Z0,没有可用的转移规则,同样不接受。

这里有一个细节值得单独说:PDA 有两种接受方式,一种是“进入接受状态就接受”,叫终态接受;另一种是“栈弹空就接受”,叫空栈接受。教材里一般会证明这两种接受方式等价。工程上,我们更熟悉的是“终态接受”,因为写代码时你不会把“栈空”当作一个神奇的时刻,而会显式地设一个返回 true 的分支。

很多人觉得 PDA 太抽象,其实你早就用过它了。手写的递归下降解析器、括号配对算法、HTML 标签匹配,这些本质上都是一个带栈的自动机。遇到</div>就弹出最近的开始标签,遇到 start tag 就压栈,配合一个有限状态判断当前上下文——这就是 PDA 在现实世界的分身。

2.3 为什么 CFG 和 PDA 总能互相转换

这一节的结论是:一个语言是上下文无关的,当且仅当存在某个 PDA 识别它。但比记住结论更重要的是理解两个转换方向的直觉。

从 CFG 构造 PDA 的方向相对直观,做法叫“自顶向下分析”。PDA 的栈里放的是“还没处理完的符号”,读到一个非终结符就把它展开成产生式右边的串,读到终结符就和输入匹配。这个过程模拟的正是刚才说的“推导”。这就是为什么现代语法分析器全是这一思路的工程化——解析器本质上是一个用代码实现的 PDA。

从 PDA 构造 CFG 的方向复杂很多,我当年啃这部分的证明啃了很久。它的核心想法是:为每一对“栈符号+状态”设计一个非终结符,用它来表示“某个栈符号从状态 p 变到状态 q 的净效果”。这个构造过程很机械,真要手推一遍工作量不小,但对理解“机器行为等价于文法推导”非常有帮助。

理解了这两个方向的转换,你会得到一个很重要的工程直觉:描述一类结构,你可以用“文法”这种声明式的方式,也可以用“栈机器”这种命令式的方式。两者可以互相翻译,所以你写语法解析器的时候,可以先设计文法再转成代码,也可以直接手写状态和栈逻辑,结果是等价的。

3. 图灵机:从一个“足够强”的模型到可计算性论题

3.1 图灵机的形式化组成:接受、拒绝与循环

PDA 尽管有了无限栈,仍然有不擅长的问题。比如语言 { aⁿbⁿcⁿ | n ≥ 0 },它需要同时记住 a、b、c 三者的数量关系,一个栈只够记录一组配对,不够同时记两组。这时候需要一种更通用的机器。

图灵机的形式化定义是一个七元组:(Q, Σ, Γ, δ, q0, q_accept, q_reject)。它比 PDA 多出来的核心部件是:一条无限长的纸带、一个可以左右移动的读写头、以及一个“拒绝状态”。转移函数 δ 不再是“读一个字符、弹出栈顶、压入栈”,而是“读一个格子、写一个字符、向左或向右移动一格”。

三个状态的区分值得多说几句。之前讨论的 DFA 和 PDA,要么串被读完后停在终态,要么卡死在某个状态,要么栈变了却没进终态。图灵机多了一个真正的第三态:循环。它可能一直跑下去,永不停止。这个“循环”不是理论上的边角料,它是整个可判定性理论的题眼。

来看一个简单的图灵机如何识别 aⁿbⁿcⁿ:机器可以反复从左到右扫描,把当前的第一个 a 标记为 X,再向右找到第一个 b 标记为 Y,再向右找到第一个 c 标记为 Z,然后回到纸带左端重新开始。如果某次扫描找不到配对的 b 或 c,就进入拒绝;如果所有 a、b、c 都配完且没有多余字符,就进入接受。这个过程用到了“多个来回扫描”的能力,这远超 PDA 那种“从左到右一遍 + 栈”的模式。

图灵机真正厉害的地方,不是某一个具体机器设计得有多巧妙,而是它提供了通用的计算模型。只要输入足够编码一个问题,你就能设计一台图灵机来计算它,而机器本身的行为完全由有限的状态和规则表控制。

3.2 为什么编程语言和它等价,以及“算法”的边界

学到这里,一个绕不开的问题就是:为什么我们相信图灵机就是“算法”的边界,而不是某个更强大、但是还没被发现的机器?

这就是可计算性论题(通常也叫 Church-Turing thesis)的内容:任何在直觉上“可计算”的函数,都可以被某台图灵机计算。这个论题没法被严格证明,因为“直觉上可计算”不是一个形式化概念。但几十年来,各种被提出来的计算模型——lambda 演算、递归函数、while 程序、寄存器机、随机访问机——全都被证明和图灵机等价,没有任何反例。

等价的意思不是这些模型长得像,而是:一个语言能被图灵机识别,当且仅当它能被 lambda 演算表达,当且仅当它能被现代编程语言写出的算法解决。换句话说,你用 Python、Java、C++ 能写出的算法边界,和图灵机能解决的边界完全一致。区别只在时间、空间这些资源指标上。

我自己从工程角度理解的图灵机,其实很简单:内存就是纸带,CPU 的寄存器就是有限状态控制,程序就是状态转移表。你写的 while 循环、递归调用、闭包,最终都会落到“读写内存、改变状态、移动执行位置”这个模式上。所以讨论“某个问题是否被算法解决”,本质上就是问“某个语言是否被图灵机判定”。

3.3 决策计算的语言学转身:从“解问题”到“认语言”

有了图灵机,我们就可以更精确地定义“问题”了。一个判定问题通常只需要回答“是”或“否”,比如“给定一个图,是否有长度不超过 k 的路径”。把问题的所有实例编码成字符串,再把答案为“是”的那些实例对应的编码收集成一个集合,这个问题就转化成了一个语言。

于是,“解决一个问题”就等价于“用图灵机识别一个语言”。你不需要让图灵机输出一串复杂的结果,只需要让它接受或者拒绝。这就是标题里 Decision 的含义:决策计算研究的是,哪些语言可以被人用算法判定“是/否”。

这个翻译非常关键,因为一旦把问题变成语言,所有形式语言理论里的工具就都能用了。你可以问:这个语言是可判定的吗?如果可判定,它属于哪一层语言谱系?如果不可判定,能不能用归约证明它的不可判定?下一节我们就要面对这个理论最核心的结论之一:有一些语言,算法永远无法判定。

4. 可判定性:停机问题的不可判定证明

4.1 把“问题”翻译成“语言”:Decision 的本质

先明确一个重要的性质:可判定性要求图灵机对所有输入都停机,接受属于该语言的串,拒绝不属于该语言的串。注意“所有输入”这四个字,它排除了“只对某些输入给答案”的偷懒做法。

举个例子,问题“给定一个 DFA 和一个字符串,DFA 是否接受该字符串”是可判定的。你只要模拟这个 DFA 跑一遍输入,最后看停在终态没有即可,模拟过程一定在有限步内结束。问题“给定一个图和两个顶点,是否 s 和 t 连通”也是可判定的,BFS 或 DFS 就能回答,遍历过程不会无限循环。

真正麻烦的是涉及图灵机自身的问题。比如语言 A_TM = { <M, w> | 图灵机 M 接受输入 w }。这里 <M, w> 是编码:把图灵机的状态转移表和输入串按某种规则编码成一个字符串。看上去,给定 M 和 w,我只要模拟 M 在 w 上的运行,它接受了我就接受,拒绝了我就拒绝,这不是很简单吗?

问题出在 M 可能在 w 上循环。你模拟它,它一直不停止,你要不要继续等下去?你无法区分“它还需要跑一万步”和“它永远不会停”。这就是为什么 A_TM 只是一个半可判定的语言,而不是可判定的。

4.2 停机问题的完整证明:对角线笔法

现在来到整个课程最著名的一个结论:语言 HALT_TM = { <M, w> | 图灵机 M 在输入 w 上停机 } 是不可判定的。这个结论通常叫停机问题,它的证明思路很经典:假设存在一个判定器,然后构造一台“专门跟自己作对”的机器。

完整的推演是这样的:

  1. 假设存在一台图灵机 H,它能判定任何 <M, w> 是否为停机问题的一个“是”实例。即 H(<M, w>) 接受当且仅当 M 在 w 上停机。

  2. 基于 H,构造一台新机器 D。D 的输入被解码为某个图灵机的编码 ,然后执行以下逻辑:

D(<M>): 运行 H(<M>, <M>) 如果 H 接受(说明 M 在 <M> 上停机): D 进入一个无限循环,永不停止 如果 H 拒绝(说明 M 在 <M> 上不停机): D 停机并接受
  1. 现在让 D 自己跑在自己身上:D( )。我们分两种情况讨论:
  • 如果 D( ) 停机,那么 H( , ) 会返回接受。可是按照 D 的构造,当 H 返回接受时,D 会故意进入无限循环,矛盾。
  • 如果 D( ) 不停机,那么 H( , ) 会返回拒绝。可是按照 D 的构造,当 H 返回拒绝时,D 会停机接受,矛盾。

两种可能性都导致矛盾,所以假设不成立,H 不存在。

我第一次看这个证明的时候,总觉得有哪里不对:H 说“M 在 上停机”,D 就故意不停机;H 说“M 在 上不停机”,D 就停机。等到问 D 自己的时候,它的话和它的行为完全相反,矛盾自然就出现了。这套思路听起来像绕口令,但它恰恰是严谨的。

这里要澄清一个很容易有的误解:并不是说“某台具体的机器”不能被判断是否停机。每台机器单独拿出来,可能都能写一个专门的分析程序来判断它。不可判定说的是不存在一个统一的、对任意 (M, w) 都正确的算法。你可以一个个处理单个问题,但不存在一劳永逸的通用判定器。

学这个证明的时候,我的建议是不要光看,一定要手写三遍。第一遍照着写,第二遍合上书自己推,第三遍试着给别人讲。讲到你能在每一步都回答“为什么这里要这样设计”的时候,才算真正理解了。

4.3 可判定、半可判定与不可判定的边界

停机问题不是孤立的反例,它开启了一整类“判不了”的问题。我们可以把语言按可判定性分成三个层次:

性质可判定半可判定不可判定
对“是”实例一定在有限时间内接受一定在有限时间内接受不一定能识别
对“否”实例一定在有限时间内拒绝可能拒绝,可能循环不一定能识别
是否存在识别算法存在停机判定器存在“只保证是”的识别器不存在任何判定器

A_TM 就是典型的半可判定:我确实可以模拟 M(w),它接受我就接受,但它拒绝或循环时,我无法在有限时间内给出“否”的结论。而停机问题是连“是”都没法在统一算法下保证判定的。

这两者的差异,放到现实中很有体感。你在调试一个崩溃的程序,程序报错退出,你能确定地知道“它拒绝了”——这就像可判定问题里的“否”。但如果程序一直卡住,你可能完全无法判断它是在计算一个大素数,还是陷入了死循环。抽象的定理和真实的调试体验,在这里是一致的。

还有一个推论值得记住:如果一个语言 L 既是半可判定的,它的补集也是半可判定的,那么 L 就是可判定的。原因是:同时跑两个识别器,一个识别 L,一个识别 L 的补集,总能有一个先停下。这个结论从侧面说明,很多不可判定问题的补集连半可判定都算不上。

5. 归约:用已知的“不可算”证明新的“不可算”

5.1 归约的方向:为什么“用 B 解 A”意味着 A ≤ B

当我们手上已经有了一堆不可判定问题,怎么证明一个新问题是不可判定的?最常用的工具是归约。

映射归约的定义是这样的:语言 A 可以归约到语言 B,记作 A ≤m B,当且仅当存在一个可计算函数 f,使得对任意字符串 w,都有:

w ∈ A 当且仅当 f(w) ∈ B

关键是理解这个方向背后的逻辑:如果 B 有一个判定器,那么我可以先对任意输入 w 计算 f(w),然后把这个结果交给 B 的判定器,最后得到的答案就是在回答“w 是否属于 A”。所以,B 可判定 → A 可判定。

反过来写就是本文最常用的推理链:如果已知 A 不可判定,并且 A ≤m B,那么 B 必然不可判定。因为如果 B 可判定,A 就跟着可判定了,这与已知矛盾。

这里我见得最多的问题就是方向搞反。很多人以为“A 不可判定,A 归约到 B,所以 B 不可判定”中的归约方向是 B 到 A,这是不对的。用个不算特别精确但很贴切的类比:如果你能用验血来判断一个人的心脏病,那么“判断心脏病”这个问题就归结到了“验血”这个问题上。这时候如果有人告诉你,心脏病这个问题本身是无法用通用算法解决的,那验血也不能解决它——因为一旦验血能解决,心脏病也能解决了。

所以记住这个口诀:把已知不可判定的问题归约到新问题,新问题才不可判定。也就是说箭头从已知不可判定的 A 指向待证明不可判定的 B。

5.2 实战归约:从语言接受问题到停机问题

我们已经知道 A_TM 是不可判定的,现在用它来证明停机问题 HALT_TM 也不可判定。假设 HALT_TM 存在判定器,记作 HALT。我们要构造的归约函数 f:输入 <M, w>,输出一台新机器 <M'>,使得 M' 停机当且仅当 M 接受 w。

构造 M' 的思路是“包装”:M' 在任意输入 x 上,先假装什么都没看见,直接去模拟 M 在 w 上的运行。如果 M 接受 w,M' 就停机;如果 M 拒绝 w,M' 就故意进入无限循环;如果 M 在 w 上不停机,M' 当然也跟着不停机。

M'(x): 模拟 M 在输入 w 上的运行 如果 M 接受 w: 停机接受 如果 M 拒绝 w: 进入无限循环

现在调用 HALT(<M'>),询问“M' 是否在所有输入上都停机”。等一下,这里有个细节。M' 的行为和输入 x 无关,所以它要么在任何输入上都停机(当 M 接受 w 时),要么在任何输入上都不停机(当 M 拒绝 w 或循环时)。因此,我只要问 HALT(<M', 0>) 即可,输入 x 随便选什么都可以。

于是有下面的等价关系:

M 接受 w ⇔ M' 停机(在输入 0 上)

右边的判断由 HALT 完成,左边的判断就等价于 A_TM 被判定。这与 A_TM 不可判定矛盾,所以 HALT_TM 不可判定。

这个构造体现了一个很实用的套路:把“接受/拒绝”包装成“停机/不停机”。很多归约证明都在做同一件事——改造目标机器,让它的某种行为恰好对应原机器的接受行为。

类似的归约可以一路做下去。比如问题“给一个图灵机 M,它的语言是否为空”也不可判定。思路是构造 M',仅当 M 接受 w 时 M' 才接受某个固定输入,否则 M' 什么都不接受。这样“语言是否为空”就编码了“是否接受 w”。这类结论可以推广成一句非常强的话:任何关于图灵机“语言的非平凡性质”的判定问题,都是不可判定的。这句话几乎一劳永逸地覆盖了大量看似有价值的自动分析需求。

5.3 换一种眼光看工程中的“做不到”

学完归约之后,再回头看看实际开发里遇到的“自动分析”需求,你会发现很多东西本质上都是不可判定的。

比如静态分析里常见的“这段代码是否永远不会抛异常”。你可以把一段任意程序包装成一个图灵机,把“抛异常”对应成“停机”,于是这个问题就变成了停机问题的变体,不可判定。编译器只能做保守估计:它分析不出就默认可能抛异常,宁可多插桩,也不敢误判安全。

再比如“两个程序是否等价”,这是一个极具诱惑力的问题。如果我能自动判断任意两个函数是否功能相同,代码重构、测试覆盖都可以自动化。但这个问题同样不可判定。所以测试和验证领域会退而求其次,做有限状态空间的模型检测、做符号执行、做差分测试,而不是追求一个万能的等价判定器。

安全领域的“这段二进制代码是否是恶意代码”也是一样。你要检测任意程序是否有恶意行为,本质上也要回答一个不可判定的问题。所以杀毒软件永远只能做特征匹配、行为启发、沙箱观察,而不是“理论上完全判定恶意性”。

我自己的体会是,计算理论不是一门“记忆课程”,而是一门“改变直觉”的课程。当你完整写过一遍停机问题的证明,再遇到那种拍胸脯说“只要给我足够的算力,就能证明任意程序会不会终止”的需求,你只会平静地问一句:你是不是把半可判定当成可判定来用了?

这门课的第二篇到这里,刚好走完了一条完整路线:从正则语言的局限,到上下文无关语言和 PDA,再到图灵机与可判定性,最后用归约把不可判定的结论扩展到一大类问题。下一篇我们可以聊一聊这个框架里剩下的那块版图——计算复杂性,也就是 P 与 NP 的那片疆域。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询