☰
蒋立源编译原理第三版第三章习题答案PDF:NFA到DFA子集构造与DFA最小化详解
2026/10/14 10:57:34 网站建设 项目流程

简介:这份PDF面向计算机专业学生与编译原理自学者,针对蒋立源《编译原理》第三版第三章的课后习题提供完整解答与思路梳理,帮助读者攻克文法、自动机与正规式等核心难点。资源共1个PDF文件,压缩包约2.97MB,内容涵盖右线性文法构造、状态转换图与状态转换矩阵、NFA确定化与最小化、含ε动作的NFA处理、正规式与DFA互转等典型题型,并配有详细推导过程与答案。目前已有1919人学习下载,适合正在备考或需要对照习题查漏补缺的学习者。通过逐题解析,读者可掌握从文法到自动机的转换方法、子集构造与最小化分划技巧,以及用正规式描述语言的能力,为后续词法分析与编译器设计打下扎实基础。

1. 蒋立源《编译原理》第三版第三章:一份习题答案 PDF 到底能帮你解决什么

如果你正在搜「蒋立源编译原理第三版第三章习题与答案(修改后).pdf」,大概率不是想收藏一份 PDF,而是卡在了词法分析这一章的某道题上:右线性文法怎么写成正规式、NFA 怎么确定化成 DFA、DFA 又怎么最小化。第三章是整本《编译原理》从「文法」跨到「自动机」的转折点,前面第二章还在讲文法的形式定义,第三章直接要求你动手构造状态转移,很多人就是在这里第一次感到「书看懂了,题不会做」。这份答案 PDF 的价值不在于给你抄,而在于它把每道题的中间步骤摊开了——尤其是 NFA 到 DFA 的子集构造过程,答案里那张状态转移表才是真正要盯住的东西。适合正在跟这门课、准备期末或考研复试、以及做编译原理实验前想先把理论题过一遍的人。下面我不复述答案,而是把第三章的题型拆成能自己复现的流程,让你对完答案之后还能独立再算一遍。

2. 第三章题型地图:右线性文法、NFA、DFA 到底怎么串起来

第三章的习题看着杂,其实就一条主线:正规文法 → 正规式 → NFA → DFA → 最小化 DFA。蒋立源这本教材的编排也是按这个顺序走的,习题里出现的每一类题,本质都是这条链上某一环的正向或反向操作。你先把这条链记住,再去看答案 PDF,就不会被零散的题号带偏。

2.1 右线性文法与正规式的互转规则

右线性文法的产生式只有两种形状:A → aB或A → a,右边终结符后面最多跟一个非终结符。这个限制正是它能对应到有限自动机的原因。转换时记住三条映射:

  • A → aB对应自动机里从状态 A 读入 a 跳到状态 B;
  • A → a对应从状态 A 读入 a 跳到终态;
  • 开始符号对应初态,新增一个终态收尾。

反过来,从正规式到右线性文法,按正规式的递归结构拆:连接拆成串联产生式,选择|拆成多条同左部产生式,闭包*引入一个新的非终结符做自环。教材习题里常考的是给一个正规式让你写出等价右线性文法,答案 PDF 里通常只给最终文法,中间那步「为什么引入这个新非终结符」往往省略,这正是你要自己补上的地方。

2.2 从正规式到 NFA 的 Thompson 构造

Thompson 构造是标准做法,把正规式按三种基本运算递归拆解,每种运算对应一个固定的小自动机片段,最后用 ε 边拼起来。三种基本片段的构造规则如下表,这是做第三章构造题必须背下来的:

运算构造方式关键点
连接aba 的接受态用 ε 边连到 b 的初态中间加 ε 边,不合并状态
选择a|b新初态 ε 分叉到 a、b 初态,两者接受态 ε 汇到新终态必须新增首尾状态
闭包a*新初态 ε 到 a 初态和新终态,a 接受态 ε 回 a 初态和新终态自环靠 ε 边实现

以热搜里常见的1(0|1)*101为例,先构造0|1的选择片段,再套闭包,再和首尾的1、101做连接。整个过程会产生大量 ε 边,状态数比最终 DFA 多得多,这是正常的,别在 NFA 阶段就想着优化。

2.3 子集构造法:NFA 到 DFA 的核心步骤

这是第三章最容易翻车的地方,也是答案 PDF 里最值得逐行对照的部分。子集构造法的本质是:DFA 的每一个状态,对应 NFA 状态集合的一个子集。步骤固定为三步循环:

  1. 求初态的 ε-闭包,作为 DFA 的起始状态;
  2. 对当前集合,对每个输入符号求「先移动再求 ε-闭包」,得到新集合;
  3. 新集合若没出现过就加入待处理队列,直到没有新集合产生。

下面用 Python 写一个可复用的子集构造脚本,你可以直接拿它验证答案 PDF 里的转移表:

# NFA 定义:状态用字符串,转移表 move[state][symbol] = set(states) # epsilon 转移单独放在 eps[state] = set(states) def epsilon_closure(states, eps): stack = list(states) closure = set(states) while stack: s = stack.pop() for nxt in eps.get(s, set()): if nxt not in closure: closure.add(nxt) stack.append(nxt) return closure def subset_construction(start, symbols, move, eps, finals): start_closure = epsilon_closure({start}, eps) dfa_states = [start_closure] # 待处理队列,用列表模拟 dfa_trans = {} # (集合, 符号) -> 集合 index = {frozenset(start_closure): 0} i = 0 while i < len(dfa_states): cur = dfa_states[i] for sym in symbols: # 先移动:收集所有能读 sym 到达的状态 moved = set() for s in cur: moved |= move.get(s, {}).get(sym, set()) if not moved: continue # 再求 epsilon 闭包 nxt = epsilon_closure(moved, eps) key = frozenset(nxt) if key not in index: index[key] = len(dfa_states) dfa_states.append(nxt) dfa_trans[(i, sym)] = index[key] i += 1 # 含 NFA 终态的集合即为 DFA 终态 dfa_finals = [idx for idx, st in enumerate(dfa_states) if st & finals] return dfa_states, dfa_trans, dfa_finals

逻辑说明:epsilon_closure用栈做深度优先,避免递归深度问题;subset_construction用列表当队列,index字典把集合映射成 DFA 状态编号,这样输出的转移表可以直接和答案 PDF 对照。参数上,move是普通转移,eps是 ε 转移,两者必须分开传,混在一起是新手最常见的错误。跑完之后dfa_trans的键是(状态编号, 符号),值是新状态编号,把它打印成表格就是标准答案的格式。

2.4 DFA 最小化:划分法怎么手算

DFA 最小化用 Hopcroft 划分法,手算时按这个顺序:先按「终态 / 非终态」分成两组,然后对每组检查同一组内的状态在相同输入下是否跳到同一组,不一致就继续拆分,直到不能再拆。教材习题里状态数一般不超过 8 个,手算完全够用。答案 PDF 里最小化题常只给最终结果,你要做的是把每次划分的中间分组写出来,这样才能确认自己没漏掉某次拆分。注意:最小化前先删掉不可达状态,否则划分会多出一组永远到不了的状态,结果和答案对不上。

3. 拿答案 PDF 对照做题:一套可复现的验证流程

有了上面的规则和脚本,接下来讲怎么把这份 PDF 用出效果。直接看答案等于没做,正确姿势是「先自己做 → 用脚本验证 → 再对答案找差异」。这一章给你一套完整流程,包括环境准备、脚本调用和差异定位。

3.1 环境准备与脚本落地

不需要装编译原理专用工具,Python 3.8 以上加标准库就够。把 2.3 的脚本存成nfa2dfa.py,再补一个把 NFA 用字典描述出来的入口:

if __name__ == "__main__": # 以 1(0|1)*101 的 NFA 为例,状态用 q0..qN 表示 move = { "q0": {"1": {"q1"}}, "q1": {"0": {"q1"}, "1": {"q1", "q2"}}, "q2": {"0": {"q3"}}, "q3": {"1": {"q4"}}, } eps = {} # 本例无 ε 边,若有则填 {状态: {状态}} symbols = ["0", "1"] finals = {"q4"} states, trans, fins = subset_construction("q0", symbols, move, eps, finals) for idx, st in enumerate(states): print(f"D{idx}: {sorted(st)}") for (i, sym), j in sorted(trans.items()): print(f"D{i} --{sym}--> D{j}") print("终态:", fins)

逻辑说明:move里每个状态对应一个字典,键是输入符号,值是可达状态集合;eps为空字典表示没有 ε 边。运行后输出的D0、D1...就是子集构造得到的 DFA 状态,把它们和答案 PDF 里的状态表逐行比对。参数上唯一要注意的是finals必须是 NFA 的终态集合,脚本会自动把包含终态的 DFA 状态标为终态。

3.2 用转移表逐行核对答案

答案 PDF 里的 DFA 通常以表格呈现,行是状态,列是输入符号。你把自己脚本输出的D{i} --sym--> D{j}整理成同样的表格,然后逐格核对。差异一般出在三个地方:一是 ε-闭包漏算,导致某个集合少了状态;二是初态的 ε-闭包没求,直接把初态当 DFA 起始;三是终态判断写成了「等于 NFA 终态」而不是「包含 NFA 终态」。这三个错误在答案里表现为状态数偏少或终态标错,对照时优先查这三处。

3.3 差异定位:状态编号对不上怎么办

状态编号对不上是正常的,因为子集构造的处理顺序不同,D0、D1的编号会变。判断对错不看编号,看集合内容和转移关系。做法是把两边都转成「集合 → 符号 → 集合」的形式再比。如果集合内容一致但编号不同,说明你算对了,只是遍历顺序不一样;如果集合内容不一致,回到 3.2 的三个检查点。我一般会写个小函数把两边的转移关系规范化成字符串集合再做差集,这样差异一目了然,比肉眼一行行看快得多。

4. 避坑与排查:第三章习题里最容易翻车的五个点

这一章是我批改作业和带实验时总结出来的高频错误,每条都按「现象 → 原因 → 解决」写,你对着答案 PDF 卡住时可以直接来查。

4.1 现象:NFA 转 DFA 后状态数比答案多

原因:ε-闭包只算了一层,没有递归求到不动点。比如q1 --ε--> q2 --ε--> q3,只算一层会漏掉 q3。解决:用 2.3 脚本里的栈式闭包,或者手算时反复展开直到集合不再增大。这个错误在含闭包*的题里几乎必现。

4.2 现象:右线性文法写出来带两个非终结符

原因:把A → aBC这种形式当成了右线性。右线性要求右边终结符后最多一个非终结符,出现两个就说明你在转换时没有为中间步骤引入新非终结符。解决:把aBC拆成A → aX、X → BC两步,但注意X → BC已经不是右线性了,正确做法是重新审视正规式,用闭包或选择重新拆解,而不是硬凑产生式。

4.3 现象:DFA 最小化后和答案差一个状态

原因:最小化前没删不可达状态。子集构造有时会生成从初态出发永远到不了的集合,划分法会把它单独成组,导致多一个状态。解决:最小化第一步先做可达性分析,从初态 BFS 一遍,只保留能到达的状态,再划分。

4.4 现象:正规式转 NFA 时 ε 边连错方向

原因:Thompson 构造里闭包的 ε 边方向记反了。闭包a*要求「新初态能跳过 a 直接到新终态」,同时「a 的接受态能回到 a 的初态实现循环」。解决:画图时先画 a 的片段,再在它外面套新首尾状态,四条 ε 边分别是:新初态→a 初态、新初态→新终态、a 接受态→a 初态、a 接受态→新终态,缺一不可。

4.5 现象:答案 PDF 里某题步骤跳跃看不懂

原因:教材答案为了篇幅省略了中间集合的展开,尤其是子集构造的中间步骤。解决:不要试图从答案反推,直接用 2.3 的脚本把 NFA 描述进去跑一遍,得到完整转移表,再和答案的最终表对照。答案省略的是过程,不是结果,脚本补的正是过程。

5. 把第三章吃透的进阶练法:自己出题反向验证

对完答案不等于掌握,真正的检验是你能不能反向出题。我的习惯是:拿答案 PDF 里任意一个最小化 DFA,反推它的正规式,再从这个正规式重新构造 NFA、重新确定化、重新最小化,看能不能回到原来那个 DFA。这个闭环走通一遍,第三章的题型基本就锁死了。

具体做法分三步。第一步,从 DFA 写方程组:每个状态对应一个方程,X = aY | bZ | ...,终态方程加 ε。第二步,用 Arden 引理X = XA | B => X = BA*消元,逐步把方程组化到只剩初态一个未知量。第三步,得到的正规式再用 2.2 的 Thompson 构造重新展开,走一遍完整流程。下面是一个消元的小例子:

# 用 sympy 解 DFA 方程组,验证正规式反推 from sympy import symbols, Eq, solve # 状态方程:X0 = 1*X1, X1 = 0*X1 + 1*X2, X2 = 0*X3, X3 = 1*X4, X4 = ε # 这里仅演示消元思路,实际用 Arden 引理手算更直观 # X4 = ε # X3 = 1 # X2 = 01 # X1 = 0*X1 + 101 => X1 = 0*101 # X0 = 10*101 print("正规式: 1(0)*101")

逻辑说明:这段代码是思路演示,真正手算时按 Arden 引理从终态往前代。参数上注意+在正规式里表示选择,*表示闭包,消元顺序从最靠近终态的方程开始。走完这个闭环,你会发现第三章所有题都是同一套动作的不同排列,答案 PDF 只是帮你确认每一步没算错。

我自己的习惯是每做完一道题,把 NFA 和 DFA 的状态转移表都手抄一遍,抄的过程比看答案管用得多。第三章的坑基本都在细节里,抄一遍能逼你把每个 ε 边和每个集合都想清楚。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询