
简介《编译原理》第二版俗称“龙书”的配套习题答案整理主要面向计算机专业本科生、考研备考者以及需要系统巩固编译原理知识框架的初学者。考虑到原书出版年代较早资源中额外关注了新版教材补充的面向对象编译、类型检查等内容有助于读者在掌握基础的同时对接教学新要求。压缩包共30个文件约3.34MB整体以doc格式的章节课后作业答案为主辅以html讲解页面、gif图解示意和ppt教学课件收录内容涵盖第二至三章总结与练习、第四章、第六章、第八章等课后作业答案适合按章节顺序复习或考前集中刷题使用。该资源目前已有2034人学习下载获取后可对照参考答案反复推敲重点题型思路省去四处查找答案的时间更适合作为编译原理课程学习与考研准备的辅助材料。1. 第二版龙书习题答案比找到答案更重要的事是能证明答案对“第二版龙书习题答案”是每个学编译原理的人都搜过的关键词大家默认找到一份答案就能把龙书第二版刷通。但现实是龙书官方从不公开发布完整标准解网上流传的答案大多是高校课程摘要、教师手册扫描残片、或者学生互相校对产生的手稿经常存在题号错位、过程缺失甚至结论互相矛盾的情况。真正能让你在面试和项目里受益的不是背下某道题的最终结果而是“你凭什么说这个 DFA 是最小的”“这张 LR 分析表为什么没有冲突”。这篇笔记不讲虚的直接从题型拆解、资源鉴别、验证工具和踩坑记录四层展开给你一套能照着做的工程方法让龙书第二版习题变成你的编译知识体检单。2. 先拆题龙书第二版习题到底在考什么龙书第二版的课后习题看起来又多又杂但实际扎堆在几个固定能力域里。如果你不清楚每道题在考什么很容易被答案拉着走看到数字对了就觉得自己会了一旦换个符号表达式就懵。所以动手做题之前先花半小时把教材的习题分布按知识点归类比直接刷二十题更划算。2.1 五大题型与答案里必须出现的成分我把第二版龙书里高频出现的习题按考察方式分成五类。分类的依据不是章节号而是答案的验证方式因为每类题目的“正确答案”长什么样是完全不同的。题型常见章节答案的核心成分最有效的验证方式正则表达式与自动机第 3 章NFA 状态转换表、DFA 状态转换表、最小化过程手工走一条典型输入串文法与语法分析第 4 章FIRST/FOLLOW 集合、LR 项目集规范族、分析动作表用栈模拟一次完整归约语法制导翻译第 5 章属性依赖图、语义规则、属性求值顺序在一棵具体语法树上逐步求值中间代码生成第 6 章三地址码序列、临时变量命名、跳转语句逐条解释执行三地址码优化与代码生成第 8、9 章基本块 DAG、数据流方程、寄存器分配表重启数据流分析直到不动点看答案时先对号入座如果一道自动机题目的答案只给了最终状态数没有转换表那它的推理链就是断的。我最常踩的坑是拿到一份只有结论的答案以为省了时间结果到了后面章节需要用前文的 DFA 做拼接时才发现自己根本不记得状态是怎么迁移的。龙书习题是累积式的前面的结论是后面的前提你可以不抄别人的过程但你不能不会推导过程。具体做法是每学完一章做一张“习题—知识点—验证方式”映射表。表格不用复杂三个字段就够| 习题定位 | 考点 | 我能接受的验收方式 |例如第 3 章某道题考点是“正则表达式到 DFA 的转换”验收方式是“写出每一步子集构造得到的 DFA 状态表”。做这张表的过程本身就是一次复习它逼着你把每一题的答案从“一个数”变成“一条推理链”。2.2 章节之间的依赖链为什么后面的题会沿用前面的结论龙书第二版有一个特点习题之间不是孤立的。第 2 章的语法树你画错了第 3 章根据语法树构造自动机就跟着错第 5 章语法制导翻译里定义的属性在第 6 章生成中间代码时要被原样引用第 8 章数据流分析的输入又是第 6 章产出的三地址码。很多人在网上找到的答案集同一道题在前后章节给出的解析对象完全不是一回事临时变量命名对不上就是因为这份资源是从不同人手里拼出来的。我自己解题的习惯是固定一个“源语言片段”作为贯穿对象。比如确定一个简单的循环语句作为样例先手工画出它的语法树再根据语法树写出三地址码给三地址码划分基本块画控制流图最后在控制流图上跑一遍到达定值分析。这样第 2、5、6、8 章的习题就共用同一套“资产”。你的答案之间互相校验某一步算错了走完整条链时迟早会暴露。如果你只是对着题号一题一题找答案永远建立不起来这种整体感。这个方法还有一层好处它让你识别一份答案靠不靠谱。拿到一份答案集不要只抽单道题看随便抽出两道跨章节的题检查它们用到的语法结构是不是同一个。如果第 4 章的练习题用E - E T第 6 章类似的题却变成了E - E E说明这份答案很可能是多人各做各的没有统一上下文参考价值要大打折扣。2.3 “第二版”内部也有版本分歧怎么快速判断答案对应的是哪一次印刷龙书第二版有英文原版、中文翻译版、国际版等多种印次不同印次之间习题编号可能错位同一道题在某个版本里是 3.7在另一个版本里可能变成 3.9。因为这个问题我见过很多人拿着 A 版答案去对 B 版教材最后得出结论“网上答案全是错的”其实只是编号错位。解决方法是抛弃题号改用“题干特征”来定位。具体就三步把题干里的正表达式、产生式或源程序片段提取出来用这些片段的关键词去匹配答案而不是用“3.7”这样的编号核对答案里给出的变量名和教材例题的风格是否一致比如教材用id表示标识符某份答案却用num说明来源可能不是同一体系。这一步做完你对一份答案资源的版本归属就有了判断力。很多所谓“答案对不上”的困惑其实根本不是答案错而是版本错位。3. 网上的第二版龙书习题答案三步判断一份资源靠不靠谱市面上能找到的第二版龙书习题答案质量差距非常大。有些是教师手册的结论版步骤跳跃有些是课程答案只覆盖到某几章还有些是学生互相校对的手写稿过程完整但常有笔误。如果一上来就全盘接受你会被错误的 DFA 状态表带偏整一章。所以先建立一套自己的筛选流程用最小成本过滤掉低质量资源。3.1 用三道标准化的题做冒烟测试判断一份答案集值不值得用不需要从头读到尾。我会从三个章节里各抽一道有硬性判断标准的题用手算验证大概十五分钟能筛完。这三道题分别是从第 3 章选一道“正则表达式转 DFA”的题。验证标准是答案给出的 DFA 终态集合明确且状态数不大于你手工构造出的 DFA 的状态数如果答案跳过了最小化步骤至少要有未最小化之前的转换表。从第 4 章选一道“给定文法构造 LR 分析表”的题。验证标准是每个状态下的动作要么是移进、要么是归约不能有未被标注的冲突如果答案里出现s/r必须附一句“这里为什么冲突”的说明。从第 6 章选一道“赋值语句生成三地址码”的题。验证标准是临时变量命名有规律每一步语句都能对应到源程序的一个片段不能出现变量凭空产生又凭空消失的情况。这三道题恰好覆盖了词法、语法和中间代码三个核心阶段任何一份答案如果在这里没有过关后面就算写得再漂亮也不值得投入时间。我会把测试结果记在一个小表格里维度合格标准这份答案是否满足DFA终态明确状态数合理是/否LR 表无未标注冲突是/否三地址码临时变量可追溯是/否只要有一项不满足这份资源直接降级为参考不当作标准答案。3.2 三类常见来源教师手册、课程答案和学生的自习笔记根据我的观察网上流传的资源大致分三类每类的优缺点都很明显。第一类是教师手册类。特征是措辞简洁常常用“similarly”“by construction”带过中间步骤。优点是结论通常可靠适合用来做最终校对缺点是你抄不到推理过程遇到不懂的还是不懂。我一般把它放在最后对答案用。第二类是高校课程答案。覆盖范围按学期教学计划裁剪往往只包含老师布置的题号其他章缺失。优点是结构清晰每章一文件很好找缺点是未经完整校对你需要的题可能正好不在里面。第三类是学生自习笔记。特征是过程非常详细甚至会在旁边画箭头写感想。优点是能帮你理解卡点缺点是笔误率高而且每个人对龙书记号的理解不一样容易把自定义记法混进答案里。我的使用策略是课程答案给方向教师手册给结论学生笔记帮我看中间某一步怎么走。三者合在一起交叉验证而不是只盯着一份资源抄。单独依赖任何一类都会在某个章节走进死胡同。3.3 从答案的文字特征识别“机器拼凑”与“人工手写”最近两年出现了很多看起来像模像样的答案文档其实是自动生成后人工润色的。这类资源有一个共同特征主干很顺但细节经不起推敲。比如它会在 FIRST 集合里漏掉ε却依然能构造出 LL(1) 分析表或者在三地址码里使用了一套很规整的临时变量名但跳转目标张冠李戴。我不反对在卡住时用这类资源做参考但它几乎不能作为标准答案。识别方法也很简单看三点一是关键步骤是否有推导痕迹比如 LR 项目集闭包的计算过程二是临时变量的命名风格是否前后一致人工手写的人会复用某个习惯机器拼凑则经常一段一个风格三是遇到“由上可知”“根据定义”这类过渡语时后面是否真的给出了由来的过程。实践中我会用一个简单动作挡住这类问题随便抽一道题把答案盖住自己先在纸上写一遍再打开答案对比。如果发现自己是靠记忆在复现而不是靠逻辑在推导立刻停下回到教材把那个知识点的定义重读一遍。这一招比任何资源筛选都管用。4. 自己推答案并验证一套能落地的做题工作流与其花大量时间找一份完美的答案不如建立自己的验证工具链。龙书习题里很多答案是“构造型”的比如构造 DFA、构造 LR 分析表、生成三地址码这类题有个共同特点——机器可以替你检查。下面三个工具分别对付自动机、文法冲突和 LR 表复核基本能覆盖我在刷龙书第二版最高频的需求。4.1 用 Python 检查两个 DFA 是否等价表填充算法的小实现第 3 章的经典题目是“把正则表达式转换成最小 DFA”。你手工做完之后怎么确认自己构造的 DFA 和同学构造的 DFA 接受的是同一种语言最笨的办法是枚举所有输入串但字符串长度稍微一长就爆炸。用表填充算法可以做多项式时间的等价性检查。下面是我常用的一个简化实现def dfa_equivalent(dfa1, dfa2, alphabet): 判断两个 DFA 是否接受同一语言。 每个 DFA 用元组 (状态集合, 初态, 终态集合, 转移字典) 表示。 转移字典的键是 (状态, 字符)值是下一状态。 返回 True 表示语言等价。 states1, start1, finals1, trans1 dfa1 states2, start2, finals2, trans2 dfa2 # 标记表mark[(s1, s2)] True 表示两个状态可区分 mark {} for s1 in states1: for s2 in states2: mark[(s1, s2)] (s1 in finals1) ! (s2 in finals2) # 迭代修复点如果某两个状态经同一输入到达可区分状态则它们也可区分 changed True while changed: changed False for s1 in states1: for s2 in states2: if mark[(s1, s2)]: continue for ch in alphabet: n1 trans1.get((s1, ch)) n2 trans2.get((s2, ch)) # 漏掉转移时跳过完整版本应该显式补死亡状态 if n1 is None or n2 is None: continue if mark[(n1, n2)]: mark[(s1, s2)] True changed True break # 初始状态若未被标记为可区分则两个 DFA 等价 return not mark[(start1, start2)]这段代码的核心理念很简单如果一个状态是终态、另一个不是它们一定可区分如果两个状态经过同一个字符分别跑到一对已经可区分的状态那它们也可区分。循环重复这个传播过程直到标记不再变化。最后看初始状态对是否被标记就能判断两个 DFA 语言是否等价。参数怎么改alphabet是全部输入符号的列表两个 DFA 必须共享同一字符集转移字典如果缺了某个键说明该状态下没有该字符的转移完整做法是补一个指向非终态死状态的转移否则可能误判。实际做题时我会先手动把 DFA 补成全定义再丢进这个函数。你可以拿两个手工构造的 DFA 来对拍比如一个直接从正则表达式构造另一个经过最小化两者应该返回 True。这个验证跑起来是毫秒级的比用人眼盯状态表靠谱得多。4.2 用 lex/yacc 给文法答案做冒烟测试第 4 章的手工 LR 分析表很容易算错尤其是多产生式的文法。我一般会先用 lex/yacc 工具把文法原样写进去看它会报告哪些冲突再回到纸面上定位问题。这不是为了生成一个生产环境解析器纯粹是拿工具当计算器用。一个最小可用的 yacc 语法文件大概长这样%token ID NUM %left - %left * / %% expr : expr expr | expr - expr | expr * expr | expr / expr | ( expr ) | ID | NUM ; %%把教材里的产生式填进去用%left声明优先级和结合性。运行后工具会生成一个报告文件里面会明确列出哪个状态存在 shift/reduce conflict冲突发生在哪个符号上。这时候回到你手算的项目集看是不是某一步 closure 少算了一个项目。这个验证方式的要点在于%left声明会全局影响所有产生式不是只影响某一条。如果你的教材里某个运算符是右结合声明要换成%right。如果工具报告的冲突点和你的手算结果对不上多半不是工具的问题而是你的项目集漏了状态。再提醒一句工具的默认冲突解决策略是移进优先这和教材里的定义不一定一致所以我不直接用工具生成分析表当答案只让它帮我暴露问题。有时候文法里存在根本性的歧义比如悬空 else。遇到这种情况报告里会稳定出现同一个冲突。很多答案集在这里会选择“继续忽略冲突”这是最危险的做法。龙书第 4 章专门讨论了如何改写文法消除冲突你应该做的是回炉重写文法而不是靠声明优先级压下去。4.3 手算 LR 表之后复核用一个小状态集人工跑一遍工具能告诉你冲突在哪但代替不了你理解那张表是怎么工作的。每次手算完一张 LR 分析表我会选一条短的终结符串比如id id * id然后在纸上模拟完整归约过程。表格是现成的模板输入串剩余状态栈动作对应产生式模拟过程中如果遇到“查表发现动作为空”的情况说明前面的动作表抄错了。如果遇到“归约后状态栈栈顶和 goto 表对不上”说明项目集规范族里有遗漏。这个手工模拟特别适合在工具报告的冲突点附近做因为它在两三次动作内就会暴露错误。模拟一次的时间成本大约十分钟但它给你带来的收益是你能真正讲清楚“为什么在状态 5 看到*要移进而不是归约”。这是面试里最容易被追问的问题也是单纯看答案得不到的能力。我一般会把自己模拟的记录写在题目旁边这样复习时一眼就能看到当时的思考轨迹。5. 避坑指南搜第二版龙书习题答案时最常翻车的五个场景这一章记录的是我在实际使用答案资源时踩过的五个典型问题。每一条都按“现象 → 原因 → 解决”给出你可以直接把这些检查点加进自己的做题流程。5.1 题号对不上教材甚至章节错位现象手头教材的第 3.7 题是一道“正则表达式转 DFA”下载的答案集里 3.7 却是一道“文法改写题”题号和内容完全对不上。原因龙书第二版不同印次、不同语种的版本习题编号存在错位部分翻译版还会把一道原题拆成两问导致题号漂移。解决放弃题号改用题干里的正则表达式、产生式或程序片段作为关键词。搜索时直接搜“它 的 DFA 有 4 个状态”这类内容特征比搜“3.7 答案”可靠得多。对答案前先写一行“题目关键特征”备忘防止二次翻车。5.2 同一道题的两份答案DFA 状态数不一样现象你手里的 A 答案说某 DFA 有 8 个状态B 答案说同一个语言只需要 4 个状态看起来矛盾。原因A 答案给的是直接从 NFA 子集构造得到的 DFAB 答案给的是最小化之后的结果。两者接受的语言完全相同只是化简程度不同。解决先把你的 DFA 最小化再用 4.1 节的等价性检查函数对拍。不要用状态数做唯一判断标准而要比终态集合和转移关系。这是最容易产生误判的场景也是很多人草率断言“答案错”的根源。5.3 ANSWER 里 FIRST/FOLLOW 集合算错导致后面整章跟着错现象用某份答案的 FOLLOW 集合去构造 LL(1) 分析表结果预测分析表里出现了冲突怎么都解释不通。原因手工求 FOLLOW 集合时最常见的错误是漏掉“产生式右部末尾非终结符”的传播规则即B - a时FOLLOW(B)会被并入FOLLOW(A)这一步被跳过。解决对每个非终结符单独列一张计算稿标清楚每一条 FOLLOW 规则来自哪个产生式位置。每次算完用一个小脚本逐项核对非终结符的包含关系。不要只对最终分析表中间集合错一步后面的结论全不可信。5.4 照答案写的语法制导翻译在 yacc 里报大量冲突现象把答案里的语义动作原样填进 yacc 文件运行时报告几十个 shift/reduce conflict 或者语法错误。原因多数情况不是语义动作的问题而是答案里隐含的优先级、结合性声明没有进代码或者手工项目集里少算了自闭包。另一个常见原因是把$$和$1的位置抄错。解决先把不含语义动作的纯文法丢进 yacc确认它没有冲突再加入语义动作逐步调试。如果加入动作后冲突重现问题往往出在中间动作与终结符的相互作用上参考教材里的标准写法不要自己发明动作位置。5.5 答案文档里“过程”拼凑感明显讲不清为什么现象某份答案前面用正规的教材记法后面突然出现手写风格缩写比如用#表示ε用→表示|而且某个临时变量名在步骤 3 凭空出现。原因很多答案文档是多人协作后合并的产物每部分由不同人负责术语习惯不一致。更危险的是部分自动生成的答案会把无关章节的内容缝合在一起。解决用第 3 章的冒烟测试先筛一遍保留推理链最完整、术语最统一的一份作为主参考。剩下的资源只用来查某个具体步骤。补不上的缺口拿到讨论区去问不要硬背一个自己说不通的结论。6. 把答案变成回归测试集一个能反复用的验证习惯刷完一章不是终点你会发现三个月后回看之前的题几乎忘干净。我后来养成一个习惯每做完一道龙书习题就把它的“输入—输出—检查方式”抽象成一条记录攒成自己的回归测试集。这样复习时不是从头翻题而是直接跑记录。先建一个简单表格习题类型输入预期输出检查方式正则转 DFA(ab)*abb最小 DFA 状态数LR 分析某文法无冲突动作表yacc 报告中间代码某循环语句三地址码寄存器分配解释执行然后写一个最朴素的脚本骨架把检查逻辑串起来cases [ { name: 3.4 正则转DFA, input: (a|b)*abb, expected: min_states 3, checker: dfa_equivalent, }, ] for case in cases: result run_check(case) if not result[ok]: print(f{case[name]} 失败{result[reason]})这样做的价值在于当你在后面的章节需要调用前面的结论时直接跑脚本验证而不是依赖记忆。我当年就是只对数字不对过程面试时被追问“为什么这个状态要合并”答不上来回去把第 3 章所有自动机题全部重做才真正把词法分析这一环补牢。这种反思给我最大的教训是龙书习题的价值不在于那张分析表本身而在于你能不能为每一步给出充分理由。希望这篇笔记能帮你少走一点弯路也希望你在重做这些题的时候真的享受到把自己教会的感觉。希望帮到你。本文还有配套的精品资源点击获取