13离散数学及其应用(第8版)

计算模型

可计算性理论

有限状态机图灵机可计算性P vs NP

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第13章 计算模型 (Models of Computation)

一、导读

1.1 本章学习目标

完成本章学习后,你将能够:

理解计算模型的概念和重要性

掌握有限状态机(FSM)的定义和应用

理解确定性有限自动机(DFA)和非确定性有限自动机(NFA)的区别与等价性

掌握正则表达式和正则语言的性质

理解上下文无关文法(CFG)和 pushdown 自动机

掌握图灵机的定义和工作原理

理解可计算性的概念和丘奇-图灵论题

掌握P类和NP类问题的定义,理解NP完全性的概念

理解停机问题的不可判定性及其证明

了解其他计算模型:量子计算、随机算法、并行计算

1.2 为什么本章如此重要

计算模型理论回答了计算机科学的根本问题:什么可以被计算?什么可以被高效计算?

在编译器设计中:词法分析使用有限状态机;语法分析使用上下文无关文法。正则表达式用于模式匹配;LL/LR解析器基于CFG。

在文本处理中:正则表达式用于搜索和替换;grep、sed、awk等工具基于有限自动机。搜索引擎使用后缀树和有限自动机进行索引和查询。

在协议设计中:网络协议、UI状态机都用有限状态机建模。TCP连接状态机是经典例子。

在安全性中:验证系统是否满足安全属性需要形式化方法。模型检查使用自动机理论验证时序逻辑性质。

在算法设计中:理解P vs NP帮助判断问题是否可能有多项式时间解。NP完全问题的识别避免徒劳地寻找高效精确算法。

在人工智能中:状态空间搜索、规划都基于计算模型。马尔可夫决策过程是概率化的有限状态机。

在编程语言理论中:类型系统、语义定义都使用形式化方法。λ演算是函数式编程的理论基础。

在硬件设计中:数字电路的状态机设计;验证使用形式化方法。

1.3 核心问题引导

在学习本章之前,请思考以下问题:

什么是"计算"?图灵机真的能模拟任何计算吗?

为什么有些问题是"不可计算"的?

正则表达式能匹配所有模式吗?有什么局限?

DFA和NFA哪个更强大?为什么它们是等价的?

什么是NP完全问题?为什么它们如此特殊?

P = NP吗?为什么这个问题如此重要?

停机问题为什么不可解?

量子计算会改变可计算性理论吗?

什么是归约?如何证明问题是NP完全的?

计算模型理论与实际编程有什么关系?


二、核心概念详解

13.1 有限状态机

定义

有限状态机(FSM)是一个五元组 M = (Q, Σ, δ, q₀, F),其中:

  • Q:有限的状态集合
  • Σ:有限的输入字母表
  • δ:转移函数 Q × Σ → Q(对于DFA)或 Q × (Σ ∪ {ε}) → P(Q)(对于NFA)
  • q₀ ∈ Q:初始状态
  • F ⊆ Q:接受状态集合

工作原理:从初始状态q₀开始,逐个读取输入符号,根据转移函数改变状态。如果读完输入后处于接受状态,则接受该输入。

语言识别:FSM识别的语言是所有被接受的字符串的集合。

示例:识别以"01"结尾的二进制字符串

状态:q₀(初始),q₁(刚读了0),q₂(刚读了01,接受)

转移:

  • δ(q₀, 0) = q₁, δ(q₀, 1) = q₀
  • δ(q₁, 0) = q₁, δ(q₁, 1) = q₂
  • δ(q₂, 0) = q₁, δ(q₂, 1) = q₀

接受状态:F = {q₂}

测试:

  • "01": q₀ → q₁ → q₂ ✓ 接受
  • "001": q₀ → q₁ → q₁ → q₂ ✓ 接受
  • "10": q₀ → q₀ → q₁ ✗ 拒绝
  • "11": q₀ → q₀ → q₀ ✗ 拒绝

应用

  • 词法分析器:识别关键字、标识符、数字等token
  • 协议状态机:TCP连接状态、HTTP请求处理
  • UI状态管理:React/Vue中的状态机
  • 游戏AI状态:巡逻、追击、逃跑等状态转换
  • 硬件控制:电梯控制、交通灯控制

13.2 DFA与NFA

确定性有限自动机(DFA)

每个状态对每个输入符号有恰好一个转移。

特点:

  • 易于实现(查表即可)
  • 运行时间 O(n),n是输入长度
  • 可能状态数较多

非确定性有限自动机(NFA)

一个状态对同一个输入符号可以有多个转移,也可以有ε转移(不读输入就转移)。

特点:

  • 更容易设计和理解
  • 可能状态数较少
  • 实现较复杂(需要跟踪多个状态)

接受条件:存在至少一条路径从初始状态到接受状态。

等价性定理

定理:DFA和NFA识别相同的语言类(正则语言)。

证明思路:子集构造法。将NFA的状态子集作为DFA的状态。

  • NFA的每个状态子集对应DFA的一个状态
  • DFA的状态数最多为 2^|Q_NFA|
  • 虽然指数增长,但理论上证明了等价性

NFA转DFA算法:

计算初始状态的ε闭包,作为DFA的初始状态

对每个DFA状态(NFA状态子集)和每个输入符号,计算转移后的状态子集

重复直到没有新状态产生

包含NFA接受状态的状态子集成为DFA的接受状态

NFA的优势

NFA通常比等价的DFA更简洁,更容易设计。例如,识别包含子串"abc"的字符串:

NFA:4个状态,简单直观

DFA:可能需要更多状态

但在运行时,DFA更高效(无需回溯)。

13.3 正则表达式

定义

正则表达式递归定义如下:

  • ∅ 是正则表达式(表示空语言)
  • ε 是正则表达式(表示只含空串的语言)
  • a 是正则表达式(表示语言 {a},对每个 a ∈ Σ)
  • 如果 R 和 S 是正则表达式,则 R+S, RS, R* 也是

运算:

  • R+S:并集(R或S),也写作 R|S 或 R∪S
  • RS:连接(R后跟S)
  • R*:克林闭包(R重复0次或多次)
  • R+:正闭包(R重复1次或多次),R+ = RR*
  • R?:可选(R出现0次或1次),R? = R+ε

优先级:* > 连接 > +

正则语言

能被正则表达式描述的语言称为正则语言。

定理:以下三者等价:

正则语言

DFA识别的语言

NFA识别的语言

正则表达式的局限

正则表达式不能描述需要"计数"或"匹配括号"的语言。

泵引理用于证明某些语言不是正则的。

泵引理:如果L是正则语言,则存在常数p(泵长度),使得任何长度≥p的字符串 w ∈ L 可以分解为 w = xyz,满足:

|xy| ≤ p

|y| > 0

对所有 i ≥ 0,xyⁱz ∈ L

应用:证明 L = {aⁿbⁿ | n ≥ 0} 不是正则语言。

证明:假设L是正则的,设p是泵长度。取 w = aᵖbᵖ。由泵引理,w = xyz,其中 |xy| ≤ p,|y| > 0。

由于 |xy| ≤ p,y只包含a。设 y = aᵏ,k > 0。

考虑 xy²z = aᵖ⁺ᵏbᵖ。由于 k > 0,a的数量不等于b的数量,所以 xy²z ∉ L。这与泵引理矛盾。因此L不是正则的。∎

实际意义:正则表达式不能匹配嵌套结构(如HTML标签、平衡括号)。需要使用上下文无关文法或递归下降解析。

13.4 上下文无关文法

定义

上下文无关文法(CFG)是一个四元组 G = (V, Σ, R, S),其中:

  • V:变量(非终结符)集合
  • Σ:终结符集合
  • R:产生式规则集合
  • S ∈ V:起始符号

产生式形式:A → α,其中 A ∈ V,α ∈ (V ∪ Σ)*

推导:从起始符号S开始,反复应用产生式替换变量,直到只剩下终结符。

语言:L(G) = {w ∈ Σ | S ⇒ w}

示例:平衡括号

S → SS | (S) | ε

推导 "()()":

S ⇒ SS ⇒ (S)S ⇒ ()S ⇒ ()(S) ⇒ ()()

推导 "(())":

S ⇒ (S) ⇒ (SS) ⇒ ((S)S) ⇒ (()S) ⇒ (())

解析树

推导过程可以用树表示:

  • 根是S
  • 内部节点是变量
  • 叶是终结符
  • 每个内部节点的子节点对应产生式右侧

歧义文法:如果某个字符串有多棵不同的解析树,则文法是歧义的。

示例:算术表达式 E → E+E | EE | id 是歧义的,因为 id+idid 有两种解析方式。

消除歧义:引入优先级和结合性。

E → E + T | T
T → T * F | F
F → id | (E)

乔姆斯基范式

每个产生式是 A → BC 或 A → a 的形式(A,B,C是变量,a是终结符)。

定理:任何CFG(不含ε)都可以转换为等价的乔姆斯基范式。

应用:CYK算法要求CNF形式。

13.5 Pushdown 自动机

Pushdown 自动机(PDA)是带有栈的有限状态机。

形式定义:PDA = (Q, Σ, Γ, δ, q₀, Z₀, F)

  • Q:状态集合
  • Σ:输入字母表
  • Γ:栈字母表
  • δ:转移函数 Q × (Σ ∪ {ε}) × Γ → P(Q × Γ*)
  • q₀:初始状态
  • Z₀:初始栈符号
  • F:接受状态集合

能力:PDA识别上下文无关语言。

与CFG的关系:PDA和CFG等价,都能描述上下文无关语言。

应用:语法分析器(编译器的前端)。LL解析器对应确定性PDA;LR解析器对应另一种确定性PDA。

13.6 图灵机

定义

图灵机是一个七元组 M = (Q, Σ, Γ, δ, q₀, q_accept, q_reject),其中:

  • Q:有限状态集合
  • Σ:输入字母表
  • Γ:带字母表(Σ ⊆ Γ)
  • δ:转移函数 Q × Γ → Q × Γ × {L, R}
  • q₀:初始状态
  • q_accept:接受状态
  • q_reject:拒绝状态(q_accept ≠ q_reject)

工作原理:图灵机有一条无限长的纸带,读写头可以左右移动。根据当前状态和读到的符号,决定写入什么符号、向哪移动、进入什么状态。

配置:瞬时描述包括当前状态、纸带内容、读写头位置。

计算:从初始配置开始,反复应用转移函数,直到进入接受或拒绝状态。

能力

图灵机能模拟任何算法过程。丘奇-图灵论题断言:任何有效计算都可以由图灵机执行。

证据支持:

  • 所有提出的计算模型(λ演算、递归函数、Post系统等)都与图灵机等价
  • 没有发现任何可计算但不能被图灵机计算的函数

注意:丘奇-图灵论题是一个假说,无法严格证明(因为"有效计算"是非形式化概念)。

变体

以下变体与标准图灵机等价(识别相同的语言类):

  • 多带图灵机
  • 非确定性图灵机
  • 只读输入图灵机
  • 多维纸带图灵机
  • 随机访问图灵机

非确定性图灵机:转移函数可以有多个选择。如果存在一条接受路径,则接受。

等价性:非确定性图灵机可以被确定性图灵机模拟(通过广度优先搜索所有可能的计算路径),但可能需要指数时间。

13.7 可计算性

可判定语言

如果存在图灵机对所有输入都停机并给出正确答案,则该语言是可判定的(递归语言)。

示例:

  • 正则语言的成员性问题
  • 上下文无关语言的成员性问题
  • 素性测试
  • 图的连通性

图灵可识别语言

如果存在图灵机对语言中的字符串接受,对不在语言中的字符串可能拒绝或永远运行,则该语言是图灵可识别的(递归可枚举语言)。

关系:可判定语言 ⊂ 图灵可识别语言

停机问题

停机问题:给定图灵机M和输入w,判断M是否在w上停机。

定理:停机问题是不可判定的。

证明(对角线论证/反证法):

假设存在判定停机的图灵机H,H(<M>, <w>) 接受如果M在w上停机,否则拒绝。(<M>表示M的编码)

构造图灵机D:

D(<M>):
    运行 H(<M>, <M>)
    如果 H 接受,则进入无限循环
    如果 H 拒绝,则停机并接受

考虑 D(<D>):

  • 如果 D 在 <D> 上停机,则 H(<D>, <D>) 接受,D 进入无限循环。矛盾。
  • 如果 D 在 <D> 上不停机,则 H(<D>, <D>) 拒绝,D 停机。矛盾。

因此假设错误,H不存在。∎

意义:存在不可判定的问题,即没有算法能解决所有实例。

其他不可判定问题

  • 波斯特对应问题(PCP):给定两组字符串,判断是否存在序列使得拼接结果相同
  • 希尔伯特第十问题:判断丢番图方程是否有整数解
  • 程序等价性问题:判断两个程序是否对所有输入产生相同输出
  • 空性问题:判断CFG生成的语言是否为空
  • 歧义性问题:判断CFG是否是歧义的

归约:如果问题A可以归约为问题B(A ≤ B),且A不可判定,则B也不可判定。

13.8 计算复杂性

P类

P是所有能在多项式时间内被确定性图灵机判定的语言集合。

多项式时间:存在常数k,使得运行时间为 O(nᵏ)。

示例:

  • 排序:O(n log n)
  • 最短路径:O(V²) 或 O(E + V log V)
  • 最大匹配:O(V³)
  • 线性规划:多项式时间(椭球算法、内点法)
  • 素性测试:AKS算法,O((log n)⁶)

特点:P中的问题被认为是"易解"的。

NP类

NP是所有能在多项式时间内被非确定性图灵机判定的语言集合。

等价定义:NP是那些"是"答案能在多项式时间内验证的语言。即存在多项式时间的验证器V,使得 x ∈ L 当且仅当存在证书c,|c| = poly(|x|),V(x,c) 接受。

示例:

  • SAT:布尔公式的可满足性
  • 旅行商问题(判定版):是否存在长度≤k的哈密顿回路
  • 图着色:是否可以用k种颜色着色
  • 子集和:是否存在子集和为t
  • 整数分解(判定版):n是否有小于k的因子

关系:P ⊆ NP(如果能在多项式时间内求解,当然也能在多项式时间内验证)

开放问题:P = NP 吗?

NP完全

定义:语言L是NP完全的,如果:

L ∈ NP

对所有 A ∈ NP,A 可以在多项式时间内归约到 L(记作 A ≤ₚ L)

归约:A ≤ₚ B 意味着如果有多项式时间算法解决B,则有多项式时间算法解决A。

第一个NP完全问题:SAT(库克-列文定理,1971)。

库克-列文定理:SAT是NP完全的。

证明思路:任何NP问题的验证过程可以编码为一个布尔公式。公式可满足当且仅当存在有效的证书。

著名NP完全问题:

  • 3-SAT
  • 顶点覆盖
  • 独立集
  • 团问题
  • 哈密顿回路
  • 旅行商问题(判定版)
  • 子集和
  • 背包问题(判定版)
  • 图着色(k≥3)
  • 布尔电路可满足性

证明NP完全的策略:

证明问题在NP中(给出验证器)

选择一个已知的NP完全问题B

证明 B ≤ₚ L(构造多项式时间归约)

P vs NP

问题:P = NP 吗?

现状:未知。这是计算机科学中最重要的开放问题之一。克雷数学研究所将其列为七个千禧年大奖难题之一,奖金100万美元。

大多数专家的看法:P ≠ NP。调查显示超过80%的理论计算机科学家相信 P ≠ NP。

意义:

  • 如果 P = NP:许多目前认为困难的问题将变得容易解决。密码系统将崩溃(RSA、椭圆曲线等基于困难问题的加密不再安全)。优化问题可以高效求解。
  • 如果 P ≠ NP:某些问题本质上就是困难的,只能寻求近似算法或启发式方法。

影响领域:密码学、优化、人工智能、生物学、经济学等。

NP难

NP难问题至少和NP中最难的问题一样难,但不一定在NP中。

定义:L是NP难的,如果对所有 A ∈ NP,A ≤ₚ L。

区别:NP完全问题必须在NP中;NP难问题可以不在NP中(如停机问题、最优化版本的TSP)。

示例:

  • TSP优化版(找最短路径)是NP难的,但不是NP完全的(因为不是判定问题)
  • 停机问题是NP难的(实际上更难,是不可判定的)

其他复杂度类

  • co-NP:补集在NP中的语言
  • PSPACE:多项式空间可判定的语言
  • EXPTIME:指数时间可判定的语言
  • BPP:有界误差概率多项式时间(随机算法)
  • NC:多对数时间并行可解的问题
  • #P:计数问题的复杂度类(如计算SAT解的数量)

关系:P ⊆ NP ⊆ PSPACE ⊆ EXPTIME

已知 P ≠ EXPTIME,但其他包含关系是否严格未知。

13.9 其他计算模型

随机图灵机

可以使用随机位的图灵机。

复杂度类:

  • BPP(有界误差概率多项式时间):算法在多项式时间内运行,错误概率 ≤ 1/3
  • RP(单边误差):如果答案是"否",总是正确;如果答案是"是",错误概率 ≤ 1/3

关系:P ⊆ RP ⊆ BPP ⊆ NP( believed to be strict inclusions)

应用:随机化算法如快速排序的随机pivot、素性测试(Miller-Rabin)、哈希。

量子图灵机

使用量子比特的计算模型。量子比特可以处于叠加态,量子门操作是幺正变换。

著名算法:

  • Shor算法:因数分解,多项式时间(经典最好为亚指数)
  • Grover算法:无序搜索,O(√n)(经典为O(n))

复杂度类:BQP(有界误差量子多项式时间)

关系:P ⊆ BPP ⊆ BQP ⊆ PSPACE(believed)

现状:量子计算机尚未达到实用规模,但进展迅速。NISQ(含噪声中等规模量子)时代正在到来。

并行计算

PRAM模型:并行随机存取机器,假设有无限多个处理器共享内存。

复杂度类:NC(Nick's Class),能在多对数时间 O(logᵏ n) 和多项式处理器上解决的问题。

关系:NC ⊆ P(NC是P中可以高效并行化的子集)

开放问题:NC = P 吗?(即所有多项式时间问题都可以高效并行化吗?)

实际应用:GPU计算、MapReduce、分布式计算。


三、程序员的视角

3.1 在计算机科学中的应用

正则表达式引擎

python
import re

# 邮箱验证
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(pattern, email):
    ...

# 注意:正则表达式不能匹配嵌套结构
# 如需匹配HTML标签,需要使用解析器

状态机实现

python
class ConnectionState:
    DISCONNECTED = 0
    CONNECTING = 1
    CONNECTED = 2
    DISCONNECTING = 3

class Connection:
    def __init__(self):
        self.state = ConnectionState.DISCONNECTED
    
    def connect(self):
        if self.state == ConnectionState.DISCONNECTED:
            self.state = ConnectionState.CONNECTING
            # 开始连接...
    
    def on_connected(self):
        if self.state == ConnectionState.CONNECTING:
            self.state = ConnectionState.CONNECTED

语法分析

python
# 简单的算术表达式解析(递归下降)
def parse_expression(tokens):
    # 使用CFG规则解析
    pass

SAT求解器应用

python
# 使用Z3求解器
from z3 import *

x, y, z = Bools('x y z')
solver = Solver()
solver.add(Or(x, y))
solver.add(Or(Not(x), z))
solver.add(Or(Not(y), Not(z)))

if solver.check() == sat:
    print(solver.model())

3.2 常见陷阱与误区

正则表达式的局限

正则表达式不能匹配嵌套结构(如平衡括号)。需要使用上下文无关文法或递归下降解析。

错误示例:尝试用正则表达式匹配HTML标签。

正确做法:使用HTML解析器(如BeautifulSoup)。

回溯灾难

某些正则表达式在最坏情况下可能导致指数级回溯。使用原子组或possessive量词避免。

危险模式:(a+)+b 对字符串 aaaa...c 会导致指数回溯。

误解NP完全

NP完全不等于"无法解决",而是"没有已知的多项式时间精确解法"。启发式和近似算法可能很有效。

实际策略:

  • 小规模实例:精确算法(回溯、分支定界)
  • 大规模实例:近似算法、启发式、局部搜索

混淆可判定性和复杂性

可判定性关注"是否能解决";复杂性关注"能否高效解决"。

停机问题是不可判定的(根本无法解决);SAT是可判定的但是NP完全的(可以解决但可能很慢)。

3.3 与其他层级的联系

  • 硬件层:有限状态机实现控制逻辑;CPU是通用图灵机的物理实现
  • 操作系统层:进程状态转换;死锁检测
  • 网络层:协议状态机;路由算法
  • 应用层:工作流引擎;业务规则引擎

四、代码实战

13.1 有限状态机实现

python
"""
有限状态机实现
"""


class DFA:
    """确定性有限自动机"""
    
    def __init__(self, states, alphabet, transitions, start_state, accept_states):
        """
        初始化DFA
        
        参数:
            states: 状态集合
            alphabet: 字母表
            transitions: 转移函数字典 {(state, symbol): next_state}
            start_state: 初始状态
            accept_states: 接受状态集合
        """
        self.states = states
        self.alphabet = alphabet
        self.transitions = transitions
        self.start_state = start_state
        self.accept_states = set(accept_states)
    
    def accepts(self, input_string):
        """判断是否接受输入字符串"""
        current_state = self.start_state
        
        for symbol in input_string:
            if symbol not in self.alphabet:
                return False
            current_state = self.transitions.get((current_state, symbol))
            if current_state is None:
                return False
        
        return current_state in self.accept_states


def demonstrate_dfa():
    """演示DFA"""
    
    # 识别以"01"结尾的二进制字符串
    dfa = DFA(
        states={'q0', 'q1', 'q2'},
        alphabet={'0', '1'},
        transitions={
            ('q0', '0'): 'q1', ('q0', '1'): 'q0',
            ('q1', '0'): 'q1', ('q1', '1'): 'q2',
            ('q2', '0'): 'q1', ('q2', '1'): 'q0',
        },
        start_state='q0',
        accept_states={'q2'}
    )
    
    print("=== DFA:识别以'01'结尾的字符串 ===")
    
    test_strings = ["01", "001", "101", "0101", "0", "1", "10", "11"]
    
    for s in test_strings:
        result = dfa.accepts(s)
        print(f"  '{s}': {'接受' if result else '拒绝'}")


if __name__ == "__main__":
    demonstrate_dfa()

13.2 正则表达式匹配器(简化版)

python
"""
简化版正则表达式匹配器
支持: . * + ?
"""


def match_regex(pattern, text):
    """
    简化的正则表达式匹配
    
    支持:
    . : 匹配任意字符
    * : 前一个字符重复0次或多次
    + : 前一个字符重复1次或多次
    ? : 前一个字符重复0次或1次
    """
    
    def match_here(regex, txt):
        """在txt的开头匹配regex"""
        if not regex:
            return True
        if regex == '$':
            return not txt
        
        # 处理下一个字符是量词的情况
        if len(regex) > 1:
            if regex[1] == '*':
                return match_star(regex[0], regex[2:], txt)
            if regex[1] == '+':
                return match_plus(regex[0], regex[2:], txt)
            if regex[1] == '?':
                return match_question(regex[0], regex[2:], txt)
        
        # 匹配单个字符
        if txt and (regex[0] == '.' or regex[0] == txt[0]):
            return match_here(regex[1:], txt[1:])
        
        return False
    
    def match_star(char, regex, txt):
        """处理 * 量词"""
        for i in range(len(txt) + 1):
            if match_here(regex, txt[i:]):
                return True
            if i < len(txt) and char != '.' and txt[i] != char:
                break
        return False
    
    def match_plus(char, regex, txt):
        """处理 + 量词"""
        if not txt or (char != '.' and txt[0] != char):
            return False
        return match_star(char, regex, txt[1:])
    
    def match_question(char, regex, txt):
        """处理 ? 量词"""
        # 0次
        if match_here(regex, txt):
            return True
        # 1次
        if txt and (char == '.' or txt[0] == char):
            return match_here(regex, txt[1:])
        return False
    
    # 尝试在每个位置匹配
    for i in range(len(text) + 1):
        if match_here(pattern, text[i:]):
            return True
    
    return False


def demonstrate_regex():
    """演示正则表达式匹配"""
    
    print("=== 简化正则表达式匹配 ===")
    
    tests = [
        ("a*b", "aaab", True),
        ("a*b", "b", True),
        ("a+b", "b", False),
        ("a+b", "ab", True),
        ("a?b", "b", True),
        ("a?b", "ab", True),
        ("a?b", "aab", False),
        (".*", "anything", True),
        ("hel.o", "hello", True),
    ]
    
    for pattern, text, expected in tests:
        result = match_regex(pattern, text)
        status = "✓" if result == expected else "✗"
        print(f"  '{pattern}' vs '{text}': {result} {status}")


if __name__ == "__main__":
    demonstrate_regex()

13.3 图灵机模拟器

python
"""
简单图灵机模拟器
"""


class TuringMachine:
    """图灵机模拟器"""
    
    def __init__(self, states, alphabet, tape_alphabet, transitions, start_state, blank_symbol, accept_state, reject_state):
        self.states = states
        self.alphabet = alphabet
        self.tape_alphabet = tape_alphabet
        self.transitions = transitions  # {(state, symbol): (new_state, new_symbol, direction)}
        self.start_state = start_state
        self.blank_symbol = blank_symbol
        self.accept_state = accept_state
        self.reject_state = reject_state
    
    def run(self, input_string, max_steps=1000):
        """
        运行图灵机
        
        返回: ('accept', steps), ('reject', steps), 或 ('loop', max_steps)
        """
        # 初始化纸带
        tape = list(input_string)
        if not tape:
            tape = [self.blank_symbol]
        
        head_pos = 0
        state = self.start_state
        steps = 0
        
        while steps < max_steps:
            steps += 1
            
            # 读取当前符号
            if head_pos >= len(tape):
                tape.extend([self.blank_symbol] * (head_pos - len(tape) + 1))
            
            current_symbol = tape[head_pos]
            
            # 查找转移
            transition = self.transitions.get((state, current_symbol))
            if transition is None:
                return ('reject', steps)
            
            new_state, new_symbol, direction = transition
            
            # 写入新符号
            tape[head_pos] = new_symbol
            
            # 移动读写头
            if direction == 'L':
                head_pos -= 1
                if head_pos < 0:
                    tape.insert(0, self.blank_symbol)
                    head_pos = 0
            elif direction == 'R':
                head_pos += 1
            
            # 更新状态
            state = new_state
            
            if state == self.accept_state:
                return ('accept', steps)
            if state == self.reject_state:
                return ('reject', steps)
        
        return ('loop', max_steps)


def demonstrate_turing_machine():
    """演示图灵机"""
    
    # 识别 {a^n b^n | n >= 0} 的图灵机
    tm = TuringMachine(
        states={'q0', 'q1', 'q2', 'q3', 'q_accept', 'q_reject'},
        alphabet={'a', 'b'},
        tape_alphabet={'a', 'b', 'X', 'Y', '_'},
        transitions={
            # q0: 找a
            ('q0', 'a'): ('q1', 'X', 'R'),  # 标记a为X
            ('q0', 'Y'): ('q3', 'Y', 'R'),  # 所有a都处理完了
            ('q0', '_'): ('q_accept', '_', 'R'),  # 空串接受
            
            # q1: 向右跳过a和Y
            ('q1', 'a'): ('q1', 'a', 'R'),
            ('q1', 'Y'): ('q1', 'Y', 'R'),
            ('q1', 'b'): ('q2', 'Y', 'L'),  # 找到b,标记为Y
            
            # q2: 向左跳回
            ('q2', 'a'): ('q2', 'a', 'L'),
            ('q2', 'Y'): ('q2', 'Y', 'L'),
            ('q2', 'X'): ('q0', 'X', 'R'),  # 回到起点
            
            # q3: 验证后面没有a
            ('q3', 'Y'): ('q3', 'Y', 'R'),
            ('q3', '_'): ('q_accept', '_', 'R'),
        },
        start_state='q0',
        blank_symbol='_',
        accept_state='q_accept',
        reject_state='q_reject'
    )
    
    print("=== 图灵机:识别 a^n b^n ===")
    
    test_inputs = ["", "ab", "aabb", "aaabbb", "aab", "abb", "abc"]
    
    for inp in test_inputs:
        result, steps = tm.run(inp)
        status = "接受" if result == 'accept' else "拒绝" if result == 'reject' else "循环"
        print(f"  '{inp}': {status} ({steps}步)")


if __name__ == "__main__":
    demonstrate_turing_machine()

五、深入思考

5.1 历史背景

艾伦·图灵(1912-1954)在1936年的论文《论可计算数》中提出了图灵机模型。阿隆佐·丘奇独立发展了λ演算。诺姆·乔姆斯基在1956年提出了形式语言层次结构。斯蒂芬·库克和理查德·卡普在1971-72年建立了NP完全性理论。

约翰·霍普克罗夫特和杰弗里·乌尔曼的教材奠定了计算理论的教育基础。莱斯利·瓦利安特引入了#P复杂度类。

5.2 前沿方向

  • 量子计算:可能打破某些经典计算的极限(如因数分解)
  • 生物计算:DNA计算、分子计算
  • 神经形态计算:模拟大脑的计算模型
  • 全息原理:物理与计算的深层联系
  • 算法信息论:柯尔莫哥洛夫复杂度
  • 同伦类型论:数学基础的新视角

5.3 延伸阅读

  • 《Introduction to the Theory of Computation》by Michael Sipser
  • 《Computational Complexity》by Christos Papadimitriou
  • 《The Annotated Turing》by Charles Petzold
  • 《Introduction to Automata Theory, Languages, and Computation》by Hopcroft, Motwani, Ullman
  • 《Quantum Computation and Quantum Information》by Nielsen and Chuang

六、实验笔记

实验1:正则表达式引擎

目标:实现一个完整的正则表达式引擎。

任务:

将正则表达式转换为NFA(Thompson构造)

将NFA转换为DFA(子集构造)

实现DFA模拟

支持常用语法(分组、反向引用等)

性能优化

提交物:源代码、测试用例、性能分析报告。

实验2:简单语言编译器

目标:为一个小语言编写编译器前端。

任务:

定义语言的CFG

实现词法分析器(使用FSM)

实现语法分析器(递归下降或LR)

生成AST

(进阶)实现简单的语义分析

提交物:源代码、语法文档、测试程序。


七、本章小结

本章介绍了计算模型理论。有限状态机是最简单的计算模型,能识别正则语言。DFA和NFA等价,都能被正则表达式描述。

上下文无关文法比正则表达式更强大,能描述嵌套结构。Pushdown自动机是CFG的计算模型。

图灵机是最强大的计算模型,能模拟任何算法。丘奇-图灵论题断言图灵机捕获了"有效计算"的本质。

可计算性理论研究哪些问题可以被解决。停机问题证明了存在不可判定的问题。

计算复杂性理论研究解决问题所需的资源。P类和NP类的关系是计算机科学中最重要的未解问题之一。NP完全问题是NP中最难的问题,如果任何一个NP完全问题有多项式时间算法,则P=NP。

其他计算模型如量子计算、随机算法、并行计算扩展了我们对计算的理解。


八、习题

习题1(基础):设计DFA识别能被3整除的二进制数。

习题2(基础):将正则表达式 (a+b)*abb 转换为NFA。

习题3(中等):证明 L = {aⁿbⁿcⁿ | n ≥ 0} 不是上下文无关语言(使用泵引理)。

习题4(中等):设计图灵机将输入的二进制数加1。

习题5(中等):解释为什么SAT是NP完全的(概述库克-列文定理的证明思路)。

习题6(较难):实现CYK算法判断字符串是否属于CFG。

习题7(较难):研究并实现一个简单的词法分析器生成器。

习题8(挑战):证明阿克曼函数不是原始递归函数。

习题9(挑战):研究量子计算对P vs NP问题的潜在影响。

习题10(开放):选择一个计算模型(如λ演算、细胞自动机、神经网络),研究其性质并撰写报告。包括:

  • 模型的定义和特点
  • 与图灵机的比较
  • 应用领域
  • 局限性和前景
13/13计算模型
0%