语言和机器

Theory of Computation 关心计算本身的边界,不限定在某一门编程语言怎么写。

一个 language 是字符串的集合。比如:

$$ L = \{w \in \{0,1\}^* \mid w \text{ contains an even number of } 1s\} $$

这个语言包含所有 1 的个数为偶数的二进制字符串。机器的任务是读入一个字符串,然后判断它是否属于这个语言。

所以每一种计算模型都可以对应一类语言:

语言类别识别它的机器核心记忆结构
Regular languagesDFA / NFA有限状态
Context-free languagesPDA有限状态 + stack
Decidable languagesTuring machine decider无限 tape,且必须 halt
Recognizable languagesTuring machine recognizer无限 tape,但 reject 时可以 loop

这门课的主线其实就是不断问同一个问题:给机器多一点记忆,它能识别的语言会不会变多。

DFA:确定性的有限记忆

DFA(Deterministic Finite Automaton)是最简单的计算模型。它由五个部分组成:

$$ (Q, \Sigma, \delta, q_0, F) $$
  • $Q$:有限状态集合
  • $\Sigma$:输入 alphabet
  • $\delta$:状态转移函数
  • $q_0$:起始状态
  • $F$:接受状态集合

DFA 的特点是:每个状态读到每个字符时,都只有一个确定的下一状态。

比如判断一个二进制字符串里 1 的数量是不是偶数,只需要两个状态:

even --1--> odd
odd  --1--> even
even --0--> even
odd  --0--> odd

这个机器不需要记住出现过多少个 1,只需要记住“当前是奇数个还是偶数个”。这就是 finite state 的本质:它不保存完整历史,只把历史压缩成有限个等价情况。

如果一个问题可以被压缩成有限种情况,它通常就是 regular 的。如果它需要记住任意大的数字、任意深的嵌套、任意长的对应关系,那有限状态就不够了。

NFA:分叉不等于更强

NFA(Nondeterministic Finite Automaton)和 DFA 的区别是,NFA 在一个状态读到一个字符时,可以有多个下一状态,也可以没有下一状态,还可以通过 $\epsilon$ transition 不读字符直接跳转。

直觉上,NFA 像是在同时尝试很多条路径。只要其中一条路径接受,整个 NFA 就接受。

这听起来比 DFA 强很多,但在 regular language 这一层,NFA 和 DFA 的表达能力完全相同。

原因是 subset construction。DFA 可以用一个状态来表示 NFA 的一组可能状态:

$$ q_{\text{DFA}} = \{q_1, q_3, q_7\} $$

也就是说,DFA 并没有复制出多条计算路径。它把“当前 NFA 可能在哪些状态”作为自己的状态。代价是状态数可能指数级膨胀,但表达能力不变。

所以:

$$ \text{DFA} \equiv \text{NFA} $$

NFA 的价值在于构造更自然,不在于表达能力更强。很多 closure proof 和 regex 转 automaton 的证明里,用 NFA 会自然很多。

Regex:同一类语言的代数写法

Regular expression 是 regular language 的另一种表示方式。

它更像语言的代数描述,不是程序执行模型。核心操作只有几个:

  • Union:$A \cup B$
  • Concatenation:$AB$
  • Star:$A^*$

Regex、NFA、DFA 三者描述的是同一类语言:

$$ \text{Regex} \equiv \text{NFA} \equiv \text{DFA} $$

这件事的意义是:regular language 有三种互相等价的视角。

  • Regex:怎么把小语言组合成大语言
  • NFA:怎么用分叉的状态图识别语言
  • DFA:怎么用确定性的有限状态识别语言

如果只看表达能力,它们没有区别。如果看构造和证明,三者各有用途。

Closure:语言类是否能承受组合

一个语言类是否 closed under 某个操作,意思是:拿这个类里的语言做完操作之后,结果是否还在这个类里。

Regular languages 对很多操作封闭:

操作是否封闭直觉
UnionYes两台机器并排跑,任意一台 accept 就 accept
ConcatenationYes第一段跑完后通过 $\epsilon$ transition 进入第二台 NFA
StarYes接受后可以跳回开头继续跑
ComplementYesDFA 交换 accept / reject states
IntersectionYesproduct construction 同时追踪两台 DFA 的状态

这里最重要的是 construction 的思路,而不是背结论。

比如 regular languages 对 union 封闭。给定两个 NFA $N_1$ 和 $N_2$,新建一个起始状态,用 $\epsilon$ transition 连到 $N_1$ 和 $N_2$ 的起始状态。新机器可以非确定性地选择跑左边还是右边,只要其中一个接受就接受。

这说明 $L(N_1) \cup L(N_2)$ 仍然是 regular。

Closure proof 的基本模式就是:如果每个输入语言都有机器,那我能不能把这些机器拼成一台新机器,识别操作后的语言。

Pumping Lemma:有限状态一定会重复

Pumping lemma 是 regular language 最重要的反证工具。它用来证明某些语言不是 regular。

核心直觉很简单:DFA 的状态数有限。如果它读入的字符串足够长,就一定会在某个状态上重复。

如果一个 DFA 有 $p$ 个状态,读一个长度至少为 $p$ 的字符串时,根据 pigeonhole principle,前 $p+1$ 个访问位置里一定有两个位置落在同一个状态。中间那段路径就是一个 loop。

所以对于任意 regular language $L$,存在一个 pumping length $p$,使得所有长度至少为 $p$ 的字符串 $s \in L$ 都可以拆成:

$$ s = xyz $$

并满足:

  • $|y| > 0$
  • $|xy| \leq p$
  • 对所有 $i \geq 0$,都有 $xy^iz \in L$

这里的 $y$ 就是可以重复走的 loop。

Pumping lemma 的说法看起来抽象,但它表达的是一个硬限制:有限状态机无法记住 loop 走了几次。如果走一次合法,走两次、三次、零次也应该合法。

为什么 a^n b^n 不是 regular

考虑语言:

$$ L = \{a^n b^n \mid n \geq 0\} $$

它包含:

ab
aabb
aaabbb
aaaabbbb

这个语言要求 a 的数量和 b 的数量完全相同。

假设它是 regular。根据 pumping lemma,存在 pumping length $p$。选字符串:

$$ s = a^p b^p $$

因为 $|xy| \leq p$,所以 $x$ 和 $y$ 都只能落在前面的 a 区域里。又因为 $|y| > 0$,所以 $y$ 至少包含一个 a。

现在 pump down,取 $i = 0$:

$$ xy^0z = xz $$

这会删掉一些 a,但 b 的数量不变。结果变成:

$$ a^{p-k}b^p $$

其中 $k > 0$。这个字符串不再满足 a 和 b 数量相同,所以不在 $L$ 中。

矛盾。因此 $L$ 不是 regular。

这个例子说明 regular language 的边界:DFA 可以记住有限模式,但不能记住无上限的精确计数。

Regular 和 Context-Free 的分界

Regular language 的机器只有有限状态。Context-free language 的机器多了一个 stack。

这个 stack 改变了机器的能力。它可以支持一种受限形式的无限记忆:后进先出。

所以 $\{a^n b^n\}$ 不是 regular,但它是 context-free。PDA 可以先读 a 并压栈,每读一个 b 就弹出一个 a。最后栈正好清空,就说明数量匹配。

这就是 Regular 和 CFL 的本质区别:

  • Regular:只能记住有限种状态
  • CFL:可以记住一段嵌套或配对结构

但 stack 也不是万能的。它只适合处理嵌套结构,不适合同时维护多个独立计数。比如:

$$ \{a^n b^n c^n \mid n \geq 0\} $$

这个语言要求 a、b、c 三段数量都相同,单个 stack 就不够了。

语言层级

计算模型越强,能识别的语言越多。

Regular
  ⊂ Context-Free
    ⊂ Decidable
      ⊂ Turing-Recognizable
        ⊂ All Languages

每一层都有一个核心新增能力:

层级新增能力典型边界
Regular有限状态不能无限计数
Context-FreeStack能处理嵌套,但不能处理多个独立计数
DecidableTuring machine + 必须 halt能算法判定,但仍有问题不可判定
Recognizableaccept 时 halt,reject 可 loop能确认 yes instance,但不一定能确认 no instance
All languages任意字符串集合大多数语言没有算法识别

Part I 的重点是最底层:finite memory 的能力和限制。

DFA、NFA、Regex 看起来是不同工具,但它们都在描述同一件事:只依赖有限状态就能识别的语言。Pumping lemma 则给出了这类语言的边界。只要一个语言要求机器记住任意大的数量或结构,regular model 就会失效。