还没有笔记
选中页面文字后点击「高亮」按钮添加
📜 [原文1]
在本节中,我们将证明 DPDA 和 DCFG 描述同一类带结束标记的语言。首先,我们将展示如何将 DCFG 转换为等价的 DPDA。这种转换在所有情况下都有效。其次,我们将展示如何进行反向转换,即将 DPDA 转换为等价的 DCFG。后一种转换仅适用于带结束标记的语言。我们将等价性限制在带结束标记的语言,因为如果没有此限制,这些模型是不等价的。我们之前已经证明结束标记不影响 DPDA 识别的语言类别,但它们确实影响 DCFG 生成的语言类别。没有结束标记,DCFG 只生成 DCFL 的子类——那些无前缀的语言(参见问题 2.22)。请注意,每个带结束标记的语言都是无前缀的。
本段是引言,为后续的理论证明设定了舞台和范围。它核心要传达的思想是:确定性下推自动机 (DPDA) 和 确定性上下文无关文法 (DCFG) 这两种描述语言的工具,在某个特定条件下是等价的,这个条件就是语言必须是“带结束标记的”。
本段的核心思想是建立 DPDA 和 DCFG 之间的等价关系。它明确指出,这种等价性并非无条件成立,而是严格限制在“带结束标记的语言”上。这是因为结束标记确保了语言的无前缀性质,解决了 DCFG 在生成字符串时面临的“何时停止”的不确定性问题,从而使其能力与 DPDA 对齐。
本段的目的是为后续两个核心引理(引理 2.58 和 引理 2.59)的证明设置背景和约束。在计算机科学理论中,精确地定义模型(如 DPDA 和 DCFG)以及它们等价的条件是至关重要的。这确保了理论的严谨性。在实践中,这关系到编译器设计:DPDA 是高效解析算法(如 LR 解析)的理论基础,而 DCFG(或其变体 LR(k) 文法)是描述编程语言语法的工具。证明它们的等价性,意味着我们可以放心地用 DCFG 来设计语言,并相信一定存在一个高效的 DPDA 解析器能够解析它。
想象一下用英语写指令。
📜 [原文2]
一个带结束标记的语言可由确定性上下文无关文法生成当且仅当它是确定性上下文无关的。
我们有两个方向需要证明。首先,我们将证明每个 DCFG 都有一个等价的 DPDA。然后,我们将证明每个识别带结束标记的语言的 DPDA 都有一个等价的 DCFG。我们将在单独的引理中处理这两个方向。
这是一个核心定理,它用最简洁的数学语言,正式陈述了上一段引言中描述的核心思想。
定理 2.57 是本节的中心宣言,它精确地指出:在“带结束标记”这个共同的舞台上,确定性上下文无关文法 (DCFG) 的生成能力与确定性下推自动机 (DPDA) 的识别能力是完全等价的。该定理的证明被拆分成两个独立的引理,分别论证从 DCFG 到 DPDA 的转换和从 DPDA 到 DCFG 的转换。
这个定理的存在是为了在计算理论中建立一个坚实的桥梁。它连接了两种不同但核心的计算模型:一种是描述如何生成合法字符串的语法模型 (DCFG),另一种是描述如何识别合法字符串的机器模型 (DPDA)。这座桥梁的建立,尤其是对确定性模型的关注,直接服务于编程语言的设计与实现。它告诉编译器设计者:你们用来定义语法的工具(DCFG 的变体)和你们用来解析代码的算法(基于 DPDA)在理论上是等效的,这为编译器能够正确、高效地工作提供了理论保障。
定理 2.57 就像在说:“对于任何一本有明确‘全书完’标记的书(带结束标记的语言),‘能够按照一套无歧义的语法规则写出这本书’(可由 DCFG 生成)这件事,等价于‘有一个机器人能一字不差地读完这本书并判断它是否符合语法’(是 DCFL)。”
想象有两个专家:
定理 2.57 就是在说:只要我们讨论的都是带避雷针的建筑,那么,建筑师能造出来的所有建筑,质检员都能认为是合格的;反之,质检员认为合格的所有建筑,也一定能被某个拥有合格蓝图的建筑师造出来。
📜 [原文3]
每个 DCFG 都有一个等价的 DPDA。
这个引理陈述了定理 2.57 的第一个证明方向:从 DCFG 到 DPDA 的转换是总是可能的。
引理 2.58 声明了一个单向的、无条件的转换关系:总能将任意一个 DCFG 转换成一个功能完全相同的 DPDA。这是证明 DCFG 和 DPDA 等价性的第一步。
此引理的存在是为了完成定理 2.57 的正向证明。它建立了一条从生成模型到识别模型的构造性路径。在实践中,这意味着如果我们有一个确定性的语法定义(DCFG),我们就有信心能实现一个确定性的解析器(DPDA)来处理它。这构成了 LR 解析等自底向上解析技术的基础。
这引理就像在说:“只要你有一本无歧义的乐高拼搭说明书(DCFG),我就能设计一个机器人(DPDA),这个机器人能看到一堆拼好的乐高积木后,判断出它是否是严格按照这本说明书拼出来的。”
想象一个厨师有一本绝不含糊的菜谱(DCFG),比如“宫保鸡丁:1. 切鸡丁;2. 炒鸡丁;3. 加调料...”。引理 2.58 就是在保证,我们一定可以训练一个美食评论家(DPDA),他品尝一道菜时,能通过一系列明确的步骤(比如先尝鸡丁的火候,再尝酱汁的味道...),准确判断出这道菜是不是严格按照那本“宫保鸡丁”菜谱做出来的。这个过程不需要等到菜的末尾有什么特殊标记,评论家在品尝的每一步都是确定性的。
📜 [原文4]
我们展示如何将 DCFG $G$ 转换为等价的 DPDA $P$。$P$ 使用 DFA $DK$ 进行操作,如下所示。它模拟 $DK$ 对从输入中读取的符号,直到 $DK$ 接受。如定理 2.52 的证明所示,$DK$ 的接受状态指示一个特定的带点规则,因为 $G$ 是确定性的,并且该规则识别出扩展到目前为止它已看到的输入的某个有效字符串的句柄。此外,由于 $G$ 是确定性的,这个句柄适用于每个有效扩展,特别是如果输入在 $L(G)$ 中,它将适用于 $P$ 的完整输入。因此 $P$ 可以使用这个句柄来识别其输入字符串的第一个规约步骤,即使此时它只读取了部分输入。
$P$ 如何识别第二个和后续的规约步骤?一个想法是直接在输入字符串上执行规约步骤,然后像我们上面那样通过 $DK$ 运行修改后的输入。但是输入既不能被修改也不能被重新读取,所以这个想法行不通。另一种方法是将输入复制到栈上并在那里执行规约步骤,但那样的话 $P$ 需要弹出整个栈才能通过 $DK$ 运行修改后的输入,因此修改后的输入将无法用于后续步骤。
这里的技巧是在栈上存储 $DK$ 的状态,而不是在栈上存储输入字符串。每次 $P$ 读取一个输入符号并模拟 $DK$ 中的一个移动时,它会通过将 $DK$ 的状态压入栈中来记录。当它使用规约规则 $T \rightarrow u$ 执行规约步骤时,它会从栈中弹出 $|u|$ 个状态,显示 $DK$ 在读取 $u$ 之前所处的状态。它将 $DK$ 重置为该状态,然后模拟它在输入 $T$ 上,并将结果状态压入栈中。然后 $P$ 继续读取和处理输入符号,就像以前一样。
当 $P$ 将开始变量压入栈中时,它已经找到了其输入到开始变量的归约,因此它进入接受状态。
这部分是构造性证明的核心思想,它描述了一个算法,用于将 DCFG $G$ 转化为 DPDA $P$。这个算法非常巧妙,是 LR 解析的理论雏形。
该证明思路描述了一个精巧的算法,用于将任何 DCFG $G$ 转换为等效的 DPDA $P$。其核心技巧是让 DPDA $P$ 的栈存储一个辅助性的 DFA ($DK$) 的状态序列,而不是输入符号本身。$P$ 通过“移入”操作(读取输入,压入新状态)和“规约”操作(根据 $DK$ 的信号,弹出若干状态,再压入一个代表规约结果的新状态),来模拟一个自底向上的解析过程。这个过程是确定性的,因为它完全由 DCFG 的确定性(体现在 $DK$ 上)来指导。
这个证明思路的存在,不仅是为了完成引理 2.58 的证明,更重要的是,它揭示了确定性解析的底层工作原理。它为现代编译器中广泛使用的 LR(0)、SLR、LALR 和 LR(1) 等解析算法提供了理论模型。它告诉我们,只要语法是确定性的,我们就可以构建一个状态机(DPDA),用一个栈来高效地、确定地解析它,而无需回溯。
想象你在玩一个复杂的解谜游戏,面前有一长串按顺序排列的线索(输入字符串)。
把解析过程想象成坐火车。
📜 [原文5]
每个识别带结束标记语言的 DPDA 都有一个等价的 DCFG。
这个引理是定理 2.57 的第二个、也是更复杂的一个证明方向。它要说明如何从一个识别带结束标记语言的 DPDA 出发,反向构造出一个等价的 DCFG。
引理 2.59 声明了一个单向的、带条件的转换关系:总能将任意一个识别带结束标记语言的 DPDA,转换成一个功能完全相同的 DCFG。这是证明 DPDA 和 DCFG 等价性的第二步,也是更难的一步。
此引理的存在是为了完成定理 2.57 的反向证明。它建立了一条从识别模型回到生成模型的构造性路径。这在理论上完成了等价性的闭环。它表明,任何能被确定性地、机械地解析的(带结束标记的)语言结构,也一定能用一套确定性的、无歧义的语法规则来描述。这再次加强了语法设计和解析器实现之间的理论联系。
此引理就像在说:“只要你有一个机器人(DPDA),它能准确地判断任何一本以‘全书完’(结束标记)结尾的书是否符合某种未知的语法,我就能通过观察这个机器人的所有行为,反向推导出一本无歧义的语法书(DCFG),这本语法书恰好能写出所有被机器人判定为‘好书’的书。”
想象你有一个非常挑剔的锁匠(DPDA),他只认一种带特殊标记(比如末端有个小铃铛 \dashv)的钥匙。他能通过一套固定的、确定性的步骤(转动、插入、感知内部结构),判断一把带铃铛的钥匙是不是“对的钥匙”。
引理 2.59 就是在保证,我们可以通过彻底研究这个锁匠检测钥匙的所有动作(状态如何变化、何时利用了钥匙的哪个凹槽),反向工程出一套制造这种“对的钥匙”的精确图纸(DCFG)。这套图纸会告诉我们,哪里该有个凹槽,哪里该有个凸起,最后必须装上那个小铃铛。用这套图纸造出来的所有钥匙,都能被那个挑剔的锁匠打开锁。铃铛(结束标记)之所以重要,是因为它给了锁匠一个明确的信号:“钥匙插到底了,现在做最终判断”,这个信号简化了我们反向推导图纸的难度。
📜 [原文6]
该证明是引理 2.27 第 121 页构造的修改,该引理描述了 PDA $P$ 到等价 CFG $G$ 的转换。
这里 $P$ 和 $G$ 是确定性的。在引理 2.27 的证明思路中,我们修改了 $P$,使其在接受时清空栈并进入一个特定的接受状态 $q_{\text{accept}}$。PDA 无法直接确定它是否在输入的末尾,因此 $P$ 利用其非确定性来猜测它处于这种情况。我们不想在构造 DPDA $P$ 时引入非确定性。相反,我们使用 $L(P)$ 是带结束标记的假设。我们修改 $P$,使其在读取结束标记 $\dashv$ 后进入其原始接受状态之一时,清空栈并进入 $q_{\text{accept}}$。
接下来,我们应用文法构造来获得 $G$。简单地将原始构造应用于 DPDA 会产生一个几乎确定性的文法,因为 CFG 的推导与 DPDA 的计算密切对应。该文法在一个微小但可修复的方面未能实现确定性。
原始构造引入了形式为 $A_{pq} \rightarrow A_{pr} A_{rq}$ 的规则,这些规则可能导致歧义。这些规则涵盖了 $A_{pq}$ 生成一个字符串的情况,该字符串将 $P$ 从状态 $p$ 带到状态 $q$,其栈在两端都为空,并且栈中途清空。替换对应于在该点划分计算。但是,如果栈多次清空,则可能进行多次划分。每个划分都会产生不同的分析树,因此所得文法是有歧义的。我们通过修改文法来解决这个问题,使其仅在栈中途清空的最后一点划分计算,从而消除这种歧义。例如,在有歧义的文法中也出现了类似但更简单的情况:
这等价于无歧义且确定性的文法:
接下来我们使用 DK 检验来证明 $G$ 是确定性的。为此,我们将分析 $P$ 如何在有效字符串上操作,通过扩展其输入字母表和转移函数来处理变量符号和终结符号。我们将所有符号 $A_{pq}$ 添加到 $P$ 的输入字母表,并通过定义 $\delta\left(p, A_{p q}, \boldsymbol{\varepsilon}\right)=(q, \boldsymbol{\varepsilon})$ 来扩展其转移函数 $\delta$。将所有其他涉及 $A_{pq}$ 的转移设置为 $\emptyset$。为了保持 $P$ 的确定性行为,如果 $P$ 从输入中读取 $A_{pq}$,则不允许 $\varepsilon$ 输入移动。
这部分概述了将 DPDA 转换为 DCFG 的证明策略。它不是一个全新的发明,而是对一个已知的通用构造(从任意 PDA 到 CFG)的精巧改造,使其适应确定性的约束。
该证明思路的核心是“改造并应用”。它首先改造给定的 DPDA,利用结束标记 \dashv 来实现确定性的“接受前清空栈”。然后,它应用一个已知的、通用的 PDA->CFG 构造算法。但它敏锐地发现这个算法会引入一类特定的歧义,并通过修改规则生成方式(强制在最后一次栈空点切分)来修复这个缺陷。最后,它计划使用 DK 检验来严格证明最终得到的文法 $G$ 确实是确定性的(DCFG)。
本段的目的是勾勒出一条从一个复杂的机器模型(DPDA)回归到一个同样有严格约束的语法模型(DCFG)的构造性路径。这条路径充满了陷阱(非确定性、歧义),本段清晰地指出了这些陷阱,并提出了解决方案。这展示了理论证明的精妙之处:不是简单地应用公式,而是在理解其背后原理的基础上,进行巧妙的修改和调整,以适应更严格的约束条件。
这个过程就像破解一个黑盒程序。
想象你在考古,发现了一台古代的自动织布机(DPDA)。这台机器能检验一块织好的布匹(带结束流苏 \dashv 的字符串)是否是“皇家贡品”。
📜 [原文7]
以下断言适用于 $L(G)$ 中任何字符串 $w$ 的推导,例如
如果 $P$ 读取包含变量 $A_{pq}$ 的 $v_i$,它会在读取 $A_{pq}$ 之前进入状态 $p$。
这个断言是在证明“我们构造出的文法 $G$ 是 DCFG”这个大目标过程中的一个关键辅助结论。它试图在文法 G 的推导过程和DPDA P 的计算过程之间建立一个精确的对应关系。
断言 2.60 建立了一座坚实的桥梁,它声明在文法 $G$ 的任何一步推导中间形式 $v_i$ 中,如果存在一个变量 $A_{pq}$,那么当模拟的 DPDA $P$ 的计算恰好进行到这个变量的位置时,$P$ 的内部状态不多不少正好就是 $p$。这个断言保证了我们构造的文法变量的下标与其在 DPDA 计算中所对应的状态是完美同步的。
这个断言是后续使用 DK 检验来证明文法 $G$ 的确定性的基石。DK 检验本身是分析文法结构的,但它的原理源于模拟解析过程。为了能将 DK 检验应用于我们从 DPDA 构造出的文法 $G$,我们必须首先证明 $G$ 的推导过程和 $P$ 的计算过程之间存在着这种可预测的、严格的对应关系。断言 2.60 就是这个对应关系的核心。有了它,我们才能在分析文法时,有把握地说“这里有一个变量 $A_{pq}$,所以对应的 DPDA 状态一定是 $p$”,从而利用 DPDA 的确定性来推导文法的确定性。
这就像给地图上的每个城市都用经纬度作了标记。
想象一个大型接力赛。
📜 [原文8]
证明采用关于 $i$(从 $A_{q_0, q_{\text{accept}}}$ 推导 $v_i$ 的步骤数)的归纳法。
基础情况:$i=0$。
在这种情况下,$v_i=A_{q_0, q_{\text{accept}}}$ 并且 $P$ 从状态 $q_0$ 开始,因此基础情况成立。
归纳步骤:假设对 $i$ 断言成立,并证明对 $i+1$ 成立。
首先考虑情况 $v_i=x A_{pq} y$ 并且 $A_{pq}$ 是在步骤 $v_i \Rightarrow v_{i+1}$ 中被替换的变量。归纳假设意味着 $P$ 在读取 $x$ 之后,读取符号 $A_{pq}$ 之前进入状态 $p$。根据 $G$ 的构造,替换规则可以是两种类型:
因此,根据使用哪种类型的规则,$v_{i+1}=x A_{pr} a A_{st} b y$ 或 $v_{i+1}=xy$。在第一种情况下,当 $P$ 读取 $v_{i+1}$ 中的 $A_{pr} a A_{st} b$ 时,我们知道它从状态 $p$ 开始,因为它刚刚完成读取 $x$。当 $P$ 读取 $v_{i+1}$ 中的 $A_{pr} a A_{st} b$ 时,由于替换规则的构造,它将按顺序进入状态 $r, s, t$ 和 $q$。因此,它在读取 $A_{pr}$ 之前进入状态 $p$,并在读取 $A_{st}$ 之前进入状态 $s$,从而确立了对这两个变量出现的断言。断言在 $y$ 部分的变量出现上成立,因为在 $P$ 读取 $b$ 之后,它进入状态 $q$,然后它读取字符串 $y$。在输入 $v_i$ 上,它也在读取 $y$ 之前进入 $q$,因此计算在 $v_i$ 和 $v_{i+1}$ 的 $y$ 部分上一致。显然,计算在 $x$ 部分上一致。因此,断言对 $v_{i+1}$ 成立。在第二种情况下,没有引入新变量,所以我们只需观察计算在 $v_i$ 和 $v_{i+1}$ 的 $x$ 和 $y$ 部分上一致。这证明了断言。
这部分是断言 2.60 的形式化证明,使用了标准的数学归纳法。
该证明通过严谨的数学归纳法,论证了断言 2.60 的正确性。它从最简单的基础情况 ($i=0$) 出发,然后假设在第 $i$ 步断言成立,通过分析一步推导可能带来的两种变化(变量被复杂结构替换或被 ε 替换),证明了在第 $i+1$ 步,所有变量(无论是新来的还是原有的)与其对应的 DPDA 运行时状态之间的同步关系依然保持。
本段的存在是为了给断言 2.60 提供严格的、无可辩驳的数学支持。在理论计算机科学中,一个“断言”或“引理”若无证明,就只是一个猜想。这个证明是后续所有依赖于该断言的论证(特别是断言 2.61)能够成立的逻辑基石。
这就像证明一个递归程序的正确性。
想象在玩多米诺骨牌。
📜 [原文9]
$G$ 通过 DK 检验。
这是证明 DPDA P 等价于 DCFG G 的核心步骤的最终宣言。它宣称,我们通过一系列精巧构造和修正得到的文法 $G$,最终是可以通过确定性的“年审”——DK 检验的。
断言 2.61 是一个结论性的声明,它宣称我们从 DPDA 精心构造出的文法 $G$,最终满足了作为确定性上下文无关文法 (DCFG) 的严格判定标准——DK 检验。证明这个断言是整个引理证明的终点线。
这个断言的存在,是为了给整个 DPDA -> DCFG 的构造过程一个最终的“合格认证”。前面的所有工作——改造 DPDA、应用通用构造、修复歧义、建立断言 2.60 的对应关系——都是在为证明这个最终断言铺路。它将前面所有铺垫联系在一起,得出了我们想要的最终结论:我们构造出的文法是确定性的。
这就像一个学生经过漫长的学习和准备后,最终宣称:“我能通过期末考试(DK 检验)!”
回到考古学家发现织布机的比喻。
📜 [原文10]
我们证明 $DK$ 的每个接受状态都满足 DK 检验要求。选择这些接受状态之一。它包含一个已完成规则 $R$。这个已完成规则可能有两种形式:
在这两种情况下,我们都需要证明接受状态不能包含
a. 另一个已完成规则,并且
b. 一个点后立即跟着终结符号的带点规则。
我们分别考虑这四种情况。在每种情况下,我们首先考虑一个字符串 $z$, $DK$ 在其上进入我们上面选择的接受状态。
情况 1a。这里 $R$ 是一个已完成的类型 1-2 规则。对于此接受状态中的任何规则,$z$ 必须以该规则中点之前的符号结尾,因为 $DK$ 在 $z$ 上进入该状态。因此,点之前的符号在所有此类规则中必须一致。这些符号在 $R$ 中是 $A_{pr} a A_{st} b$,因此任何其他类型 1-2 的已完成规则都必须在右侧具有完全相同的符号。由此可知,左侧的变量也必须一致,因此规则必须相同。
假设接受状态包含 $R$ 和某个类型 3 已完成 $\varepsilon$ 规则 $T$。从 $R$ 我们知道 $z$ 以 $A_{pr} a A_{st} b$ 结尾。此外,我们知道 $P$ 在 $z$ 的末尾弹出其栈,因为在 $R$ 中在该点发生了弹出,这是由于 $G$ 的构造。根据我们构建 $DK$ 的方式,状态中已完成的 $\varepsilon$ 规则必须派生自位于同一状态的带点规则,其中点不在开头,并且点紧接在某个变量之前。(在 $DK$ 的开始状态中可能出现异常,其中点可能出现在规则的开头,但此接受状态不能是开始状态,因为它包含一个已完成的类型 1-2 规则。)在 $G$ 中,这意味着 $T$ 派生自一个类型 1-2 的带点规则,其中点位于第二个变量之前。从 $G$ 的构造来看,在点之前发生了一个压入操作。这意味着 $P$ 在 $z$ 的末尾进行了一次压入移动,这与我们之前的陈述相矛盾。因此,已完成的 $\varepsilon$ 规则 $T$ 不可能存在。无论哪种情况,此接受状态中不能出现第二个已完成规则。
情况 2a。这里 $R$ 是一个已完成的 $\varepsilon$ 规则 $A_{pp} \rightarrow .$。我们证明没有其他已完成的 $\varepsilon$ 规则 $A_{qq} \rightarrow .$ 可以与 $R$ 共存。如果共存,前面的断言表明 $P$ 在读取 $z$ 后必须处于 $p$ 状态,并且在读取 $z$ 后也必须处于 $q$ 状态。因此 $p=q$,并且这两个已完成的 $\boldsymbol{\varepsilon}$ 规则是相同的。
情况 1b。这里 $R$ 是一个已完成的类型 1-2 规则。从情况 1a 我们知道 $P$ 在 $z$ 的末尾弹出其栈。假设接受状态也包含一个带点规则 $T$,其点后立即跟着终结符号。从 $T$ 我们知道 $P$ 在 $z$ 的末尾不弹出其栈。这个矛盾表明这种情况不可能发生。
情况 2b。这里 $R$ 是一个已完成的 $\varepsilon$ 规则。假设接受状态还包含一个带点规则 $T$,其点后立即跟着终结符号。因为 $T$ 是类型 1-2,变量符号紧接在点之前,因此 $z$ 以该变量符号结尾。此外,在 $P$ 读取 $z$ 后,它已准备好读取一个非 $\varepsilon$ 输入符号,因为终结符号紧接在点之后。如情况 1a 所示,已完成的 $\varepsilon$ 规则 $R$ 派生自类型 1-2 的带点规则 $S$,其中点紧接在第二个变量之前。(同样,此接受状态不能是 $DK$ 的开始状态,因为点不出现在 $T$ 的开头。)因此,某个符号 $\hat{a} \in \Sigma_{\varepsilon}$ 紧接在 $S$ 中的点之前,因此 $z$ 以 $\hat{a}$ 结尾。$\hat{a} \in \Sigma$ 或 $\hat{a}=\varepsilon$,但由于 $z$ 以变量符号结尾,$\hat{a} \notin \Sigma$,因此 $\hat{a}=\varepsilon$。因此,在 $P$ 读取 $z$ 但在它进行 $\varepsilon$ 输入移动以处理 $\hat{a}$ 之前,它已准备好读取一个 $\varepsilon$ 输入。我们上面还证明了 $P$ 在这一点已准备好读取一个非 $\varepsilon$ 输入符号。但是,DPDA 不允许在给定状态和栈的情况下同时进行 $\varepsilon$ 输入移动和读取非 $\varepsilon$ 输入符号的移动,因此上述情况是不可能的。因此,这种情况不可能发生。
这部分是断言 2.61 的核心证明,采用的是反证法和分类讨论。其目标是证明在 $DK$ 自动机的任何一个状态里,都不会出现冲突。证明的焦点集中在接受状态,因为根据 DK 检验的定义,冲突只可能发生在包含已完成规则(即准备规约)的状态中。
证明策略:
情况 1a: 复杂规约 vs. 任何其他规约 (排除 Reduce/Reduce 冲突)
情况 2a: ε-规约 vs. ε-规约 (排除 Reduce/Reduce 冲突)
情况 1b: 复杂规约 vs. 移入 (排除 Shift/Reduce 冲突)
情况 2b: ε-规约 vs. 移入 (排除 Shift/Reduce 冲突)
该证明通过对四种可能的冲突情况进行逐一排查,并利用反证法,成功证明了我们构造的文法 $G$ 不会产生任何冲突。每一种情况的排除都最终归结于利用了源 DPDA $P$ 的确定性:
由于文法 $G$ 是对 DPDA $P$ 行为的忠实编码,因此 $P$ 的确定性就直接传导给了 $G$,保证了 $G$ 能通过 DK 检验。
本段是整个引理 2.59 证明的收官之战。它的目的就是以一种滴水不漏的逻辑,完成对断言 2.61 的证明。通过这个详尽的分类讨论,它将抽象的“等价性”问题,转化为了对具体冲突场景的分析,并利用 DPDA 的基本定义来解决这些问题,展示了理论证明的严谨与力量。
这就像一个侦探在排除嫌疑。
想象一个十字路口的交通信号灯系统($DK$ 状态)。
📜 [原文11]
确定性上下文无关语言具有重要的实际意义。它们的成员资格和解析算法基于 DPDA,因此效率高,并且它们包含丰富的 CFL 类别,其中包括大多数编程语言。然而,DCFG 有时不便于表达特定的 DCFL。所有句柄都必须是强制的要求通常是设计直观 DCFG 的障碍。
幸运的是,一个更广泛的文法类别,称为 LR(k) 文法,为我们提供了两全其美的优点。它们与 DCFG 足够接近,可以直接转换为 DPDA。然而,它们对于许多应用来说表达能力足够强。
LR(k) 文法的算法引入了前瞻 (lookahead)。在 DCFG 中,所有句柄都是强制的。句柄仅取决于有效字符串中(包括句柄本身)的符号,而不取决于句柄后面的终结符号。在 LR(k) 文法中,句柄也可能取决于句柄后面的符号,但仅限于这些符号的前 $k$ 个。缩写 LR(k) 代表:从左到右的输入处理 (Left to right input processing)、最右推导(或等价地,最左归约)(Rightmost derivations),以及 $k$ 个前瞻符号 (k symbols of lookahead)。
为了使其精确,设 $h$ 是有效字符串 $v=xhy$ 的句柄。我们说 $h$ 由前瞻 $k$ 强制,如果 $h$ 是每个有效字符串 $x h \hat{y}$ 的唯一句柄,其中 $\hat{y} \in \Sigma^{*}$,并且 $y$ 和 $\hat{y}$ 在它们的前 $k$ 个符号上一致。(如果任一字符串的长度小于 $k$,则这些字符串必须一致到较短字符串的长度。)
这一部分将理论从纯粹的 DCFG 拓展到了一个在实践中更有用、更强大的概念——LR(k) 文法。
本段从 DCFG 的实践局限性出发,引入了更强大、更实用的 LR(k) 文法。其核心优势在于允许解析器在做决策时“向前看 $k$ 个符号”(前瞻)。这解决了 DCFG (即 LR(0)) 中常见的“移入/规约冲突”,使得语法规则可以写得更自然、更直观,同时又不牺牲语言的确定性。最后,本段给出了前瞻的精确定义,为后续讨论奠定了基础。
本段的目的是将理论与实践联系起来。在证明了 DCFG 与 DPDA 的理论等价性后,它指出了 DCFG 在实际应用中的不足,从而引出 LR(k) 文法这一在编译器设计领域至关重要的概念。它解释了为什么我们需要比 LR(0) 更强的文法,并定义了这种文法的核心特征——前瞻。这为接下来介绍 LR(1) 的 DK1 检验以及证明所有 LR(k) 文法都描述 DCFL 做了铺垫。
你在下棋。
📜 [原文12]
LR(k) 文法是一种上下文无关文法,其中每个有效字符串的句柄都由前瞻 $k$ 强制。
因此,DCFG 与 LR(0) 文法相同。我们可以证明对于每个 $k$,我们可以将 LR(k) 文法转换为 DPDA。我们已经证明 DPDA 等价于 LR(0) 文法。因此 LR(k) 文法对于所有 $k$ 在能力上是等价的,并且都准确描述了 DCFL。以下示例表明 LR(1) 文法比 DCFG 更方便指定某些语言。
这部分给出了 LR(k) 文法的正式定义,并立即阐明了它在计算能力谱系中的位置。
当解析器看到一串 a 之后,它需要决定是继续移入 a 还是规约。比如对于 aaY,Y 是句柄,但规约为 $X$ 还是继续移入?这需要前瞻。实际上,这个语言是正则的,但我们用它来思考。
本段正式定义了 LR(k) 文法,并迅速阐明了它的核心理论地位:尽管 LR(k) 文法本身随着 $k$ 的增大而变得越来越强大(能以无歧义方式描述的语法结构越来越多),但它们所能生成的语言集合的边界是固定的,这个边界就是 DCFL。它进一步指出了 LR(k) 相比于 DCFG (LR(0)) 的主要优势不在于生成新类型的语言,而在于能以更简洁、更自然的方式描述已有的 DCFL。
本段的目的是在引入 LR(k) 的概念后,迅速消除一个潜在的误解,即认为 LR(k) 开创了一个全新的、比 DCFL 更大的语言类别。通过清晰地论证所有 LR(k) 文法在能力上都等价于 DPDA,本段将 LR(k) 牢牢地定位在 DCFL 的框架内。这使得我们可以将 LR(k) 视为解决 DCFG 实用性问题的“高级工具”,而不是一个全新的理论分支。
这就像比较不同的编程语言。
想象你要写一本菜谱,来教人做各种菜。
📜 [原文13]
为避免繁琐的符号和技术细节,我们仅在 $k=1$ 的特殊情况下展示如何将 LR(k) 文法转换为 DPDA。一般情况下的转换方式基本相同。
本段是一个过渡说明,旨在简化后续的讨论。
本段是一个“范围说明”,它告诉读者,为了清晰和实用,接下来的技术细节将集中于将 LR(1) 文法转换为 DPDA 的过程,并指出这种方法可以推广到任意 $k$。
本段的目的是管理读者的预期,并使证明过程更易于理解。通过将问题简化为最重要和最常见的 LR(1) 情况,作者可以更清晰地展示构造的核心思想,而不会让读者淹没在繁琐的下标和复杂的集合定义中。这是一种常见的、有效的数学和计算机科学教学策略。
这就像在教人开车。老师会说:“为了简单起见,我们今天只学在晴天、白天、干燥路面上如何开车。学会了之后,晚上开车、雨天开车(对应 $k>1$ 的情况)的原理是相通的,只是需要多注意一些额外的细节。”
想象一位数学教授在讲授一个复杂的定理。他说:“这个定理在 n 维空间都成立。但为了让大家直观地理解,我们先在二维平面($k=1$ 的情况)上证明它。你们会看到,所有的核心思想都在二维情况下体现了。之后,推广到三维、四维乃至 n 维(一般情况 $k$),只是需要把所有的 $(x, y)$ 坐标换成 $(x_1, x_2, \dots, x_n)$ 而已,证明的逻辑是一样的。”
📜 [原文14]
首先,我们将介绍 DK 检验的变体,为 LR(1) 文法进行了修改。我们称之为带前瞻 1 的 DK 检验,或简称为 $DK_1$ 检验。和以前一样,我们将构造一个 NFA,这里称为 $K_1$,并将其转换为 DFA $DK_1$。 $K_1$ 的每个状态都有一个带点规则 $T \rightarrow u . v$ 和一个终结符号 $a$,称为前瞻符号,表示为 $T \rightarrow u . v \quad a$。此状态表示 $K_1$ 最近读取了字符串 $u$,如果 $v$ 在 $u$ 之后,$a$ 在 $v$ 之后,那么 $u v$ 将是句柄的一部分。
形式构造与之前大致相同。开始状态对每个涉及开始变量 $S_1$ 的规则和每个 $a \in \Sigma$ 都有一个到 $S_{1 \rightarrow .ua}$ 的 $\varepsilon$ 移动。移进转移将 $T \rightarrow u . x v a$ 转换为 $T \rightarrow ux . va$,输入为 $x$,其中 $x$ 是变量符号或终结符号。$\varepsilon$ 转移将 $T \rightarrow u . Cva \quad a$ 转换为 $C \rightarrow . r \quad b$,对于每个规则 $C \rightarrow r$,其中 $b$ 是可以从 $v$ 派生的任何终结符串的第一个符号。如果 $v$ 派生 $\varepsilon$,则添加 $b=a$。接受状态是所有 $B \rightarrow u \cdot a$,用于已完成规则 $B \rightarrow u.$ 和 $a \in \Sigma$。
设 $R_1$ 是一个已完成规则,前瞻符号为 $a_1$,设 $R_2$ 是一个带点规则,前瞻符号为 $a_2$。如果满足以下条件,则称 $R_1$ 和 $R_2$ 是一致的:
现在我们准备描述 $DK_1$ 检验。构造 DFA $DK_1$。检验规定每个接受状态不得包含任何两个一致的带点规则。
这部分描述了如何将用于 LR(0) 的 DK 检验升级为用于 LR(1) 的 $DK_1$ 检验。核心思想是在自动机的每个状态中,不仅记录句柄的解析进度,还附带记录了句柄后面应该跟什么符号。
如果 $a = b$,就发生了冲突。因为当下一个输入符号是 $a$ 时,解析器不知道是应该根据第一条规则用前瞻 $a$ 来规约,还是根据第二条规则移入符号 $a$。
如果 $a = b$,就发生了冲突。因为当下一个输入符号是 $a$ 时,解析器不知道是应该按 $A \rightarrow u$ 规约,还是按 $B \rightarrow v$ 规约。
$DK_1$ 检验是 DK 检验的升级版,它通过在自动机的每个状态中增加“前瞻符号”的信息,来判断一个文法是否为 LR(1) 文法。其核心在于,将 LR(0) 检验中不可调和的冲突,通过检查前瞻符号来化解。只有当规约所需的前瞻符号和移入所需的下一个输入符号相同时,或者两个不同规约所需的前瞻符号相同时,才会判定为冲突。这使得 $DK_1$ 检验比 DK 检验更“宽容”,能够接受更多更自然的文法。
本段的目的是为 LR(1) 文法提供一个形式化的、可操作的判定算法。有了 $DK_1$ 检验,我们就不再是凭感觉说一个文法“似乎是 LR(1) 的”,而是可以通过一个机械的、可由计算机执行的算法来精确地验证它。这是构建 LR(1) 解析器生成器(如 YACC, Bison 的理论基础)的第一步。
想象你在一个岔路口。
📜 [原文15]
$G$ 通过 $DK_1$ 检验当且仅当 $G$ 是 LR(1) 文法。
这是一个定义性的定理,它将一个抽象的数学定义(LR(1) 文法)与一个具体的、可执行的算法($DK_1$ 检验)等同起来。
定理 2.63 是 LR(1) 理论的基石。它声明,判断一个文法是否为 LR(1) 的抽象定义,可以完全等价地用一个具体的 $DK_1$ 检验算法来判定。这使得 LR(1) 文法的识别问题从一个理论问题转化为了一个工程问题。
本定理的目的是将 LR(1) 文法的理论定义与其实际应用连接起来。在它之前,LR(1) 只是一个数学概念;在它之后,LR(1) 成为了一个可以通过算法来验证的属性。这是所有 LR(1) 解析器生成工具(如 YACC, Bison)能够存在的理论前提。这些工具的核心功能之一,就是实现了 $DK_1$ 检验(或其变体),以判断用户输入的文法是否符合要求。
想象医生诊断一种罕见的过敏症。
📜 [原文16]
这个示例表明以下文法通过了 $DK_1$ 检验。回想一下,在示例 2.53 中,这个文法未能通过 DK 检验。因此,它是一个 LR(1) 文法但不是 DCFG 的例子。

图 2.65
通过 $DK_1$ 检验
本段通过一个具体的例子,生动地展示了 LR(1) 相对于 LR(0) (DCFG) 的优势。
本示例通过一个具体的算术表达式文法,成功地展示了 LR(1) 的威力。这个文法因为存在“移入/规约冲突”而无法被 LR(0) (DCFG) 解析,但通过引入一个符号的前瞻,这些冲突都得到了解决。这直观地证明了 LR(1) 文法的集合确实是真包含 DCFG 集合的,并且 LR(1) 在描述常见编程语言结构(如带优先级的运算符)方面更加自然和强大。
本示例的目的是提供一个具体的、有说服力的证据,来支撑“LR(1) 比 DCFG 更方便”这个论点。理论的阐述往往是抽象的,一个好的例子能让读者瞬间抓住核心差异。这个例子就是 LR(0) 和 LR(1) 分水岭上的一个界碑,它让读者亲眼看到“前瞻”是如何实实在在地解决问题的。
想象你在一条流水线上组装产品。
📜 [原文17]
一个带结束标记的语言可由 LR(1) 文法生成当且仅当它是一个 DCFL。
我们已经证明每个 DCFL 都有一个 LR(0) 文法,因为 LR(0) 文法与 DCFG 相同。这证明了定理的反向。剩下的是以下引理,它展示了如何将 LR(1) 文法转换为 DPDA。
这个定理是 定理 2.57 的一个平行版本,只是把 DCFG 替换成了更强大的 LR(1) 文法。
定理 2.66 最终确立了 LR(1) 文法 和 DCFL 之间的等价关系(在带结束标记的前提下)。它通过引用之前的结论(DCFL → LR(0))轻松地证明了其中一个方向,并将另一个方向的证明任务(LR(1) → DPDA)交给了下一个引理。这个定理是本章关于确定性语言理论的一个重要总结。
本定理的目的是将前面引入的 LR(1) 概念,正式地、最终地纳入到 DCFL 的理论框架中。它明确宣告,尽管 LR(1) 文法看起来比 DCFG 更强大,但它们并没有突破 DCFL 的边界。这为在实践中使用 LR(1) 文法来设计和解析所有 DCFL 提供了坚实的理论依据。
这就像在证明“所有可以用 Python 写的程序,都是图灵可计算的”。
想象两种不同级别的建筑蓝图。
📜 [原文18]
每个 LR(1) 文法都有一个等价的 DPDA。
这个引理是 定理 2.66 的最后一块拼图,也是 LR(1) 理论能够实际应用的关键。
引理 2.67 声明了一个从 LR(1) 文法到 DPDA 的构造性路径的存在。这保证了任何用 LR(1) 文法描述的语言,都可以被一个高效的、确定性的解析器所识别。
本引理的目的是为 定理 2.66 提供最后的支持,并从根本上阐明 LR(1) 解析器是如何工作的。它的证明思路(如下一段所述)实际上就是 LR(1) 解析算法的理论描述。
引理说:“只要你给我一本用‘高级语言’(LR(1))写的、无歧义的说明书,我就能造一个机器人(DPDA),这个机器人能严格按照这本说明书来验证产品。”
回到那个会“抬头看路”的司机(LR(1) 解析器)的比喻。
📜 [原文19]
我们构造 $P_1$,它是我们在引理 2.67 中介绍的 DPDA $P$ 的修改版本。 $P_1$ 读取其输入并模拟 $DK_1$,同时使用栈来跟踪 $DK_1$ 如果所有规约步骤都应用到目前为止的输入,它将处于什么状态。此外,$P_1$ 超前读取 1 个符号,并将此前瞻信息存储在其有限状态存储器中。每当 $DK_1$ 达到接受状态时,$P_1$ 都会查阅其前瞻以查看是否执行规约步骤,以及如果在此状态中出现多种可能性,执行哪个步骤。只能应用一个选项,因为文法是 LR(1)。
这部分描述了如何构造一个能识别 LR(1) 文法的 DPDA,即 LR(1) 解析算法的原理。
该证明思路清晰地描述了 LR(1) 解析器的核心算法。它通过对 LR(0) 解析器 (DPDA $P$) 进行两项关键升级:1) 模拟更强大的 $DK_1$ 自动机,2) 增加缓存一位的前瞻能力,从而构建出一个新的 DPDA $P_1$。这个 $P_1$ 在遇到决策点时,能够利用缓存的前瞻符号和 $DK_1$ 状态中包含的前瞻信息,来唯一地、确定地选择下一步动作(移入或规约)。因为文法的 LR(1) 性质保证了这种选择的唯一性,所以 $P_1$ 是一个合法的 DPDA。
本段的目的在于揭示理论如何转化为算法。它不仅是引理 2.67 的证明梗概,更是对所有 LR(1) 解析器工作原理的精辟总结。它告诉我们,LR(1) 文法的“前瞻”特性,不是一个虚无缥缈的数学性质,而是可以被一个确定性机器(DPDA)通过“多看一个符号”的机制来具体实现的。
这就像一个升级版的流水线工人(DPDA $P_1$)。
想象你在参加一个电视智力竞赛,需要快速回答问题。
📜 [原文20]
2.1 回想一下我们在示例 2.4 中给出的 CFG $G_4$。为方便起见,我们将其变量重命名为单个字母,如下所示。
给出每个字符串的分析树和推导。
a. a
b. a+a
c. a+a+a
d. ((a))
2.2 a. 使用语言 $A=\left\{\mathrm{a}^{m} \mathrm{~b}^{n} \mathrm{c}^{n} \mid m, n \geq 0\right\}$ 和 $B=\left\{\mathrm{a}^{n} \mathrm{~b}^{n} \mathrm{c}^{m} \mid m, n \geq 0\right\}$ 以及示例 2.36 来证明上下文无关语言类在交集下不封闭。
b. 使用 (a) 部分和德摩根定律 (定理 0.20) 来证明上下文无关语言类在补运算下不封闭。
${ }^{\text{A}}$ 2.3 对以下上下文无关文法 $G$ 的每个部分回答。
a. $G$ 的变量是什么?
i. 对或错:$T \stackrel{*}{\Rightarrow} T$。
b. $G$ 的终结符是什么?
j. 对或错:$XXX \xrightarrow{*}$ aba。
c. $G$ 的开始变量是哪个?
k. 对或错:$X \stackrel{*}{\Rightarrow}$ aba。
d. 给出 $L(G)$ 中的三个字符串。
l. 对或错:$T \stackrel{*}{\Rightarrow} XX$。
e. 给出不在 $L(G)$ 中的三个字符串。
m. 对或错:$T \stackrel{*}{\Rightarrow} XXX$。
f. 对或错:$T \Rightarrow$ aba。
n. 对或错:$S \stackrel{*}{\Rightarrow} \varepsilon$。
g. 对或错:$T \stackrel{*}{\Rightarrow}$ aba。
o. 用英语描述 $L(G)$。
h. 对或错:$T \Rightarrow T$。
2.4 给出生成以下语言的上下文无关文法。在所有部分中,字母表 $\Sigma$ 是 $\{0,1\}$。
${ }^{\text{A}}$ a. $\{w \mid w \text{ 包含至少三个 } 1 \text{s}\}$
b. $\{w \mid w \text{ 以相同符号开始和结束}\}$
c. $\{w \mid w \text{ 的长度为奇数}\}$
${ }^{\text{A}}$ d. $\{w \mid w \text{ 的长度为奇数且其中间符号为 } 0\}$
e. $\{w \mid w=w^{\mathcal{R}} \text{,即 } w \text{ 是回文}\}$
f. 空集
2.5 给出练习 2.4 中语言的下推自动机的非形式化描述和状态图。
2.6 给出生成以下语言的上下文无关文法。
${ }^{\text{A}}$ a. 字母表 $\{\mathrm{a}, \mathrm{b}\}$ 上 a 比 b 多的字符串集
b. 语言 $\left\{\mathrm{a}^{n} \mathrm{~b}^{n} \mid n \geq 0\right\}$ 的补集
${ }^{\text{A}}$ c. $\left\{w \# x \mid w^{\mathcal{R}} \text{ 是 } x \text{ 的子字符串,其中 } w, x \in\{0,1\}^{*}\right\}$
d. $\left\{x_{1} \# x_{2} \# \cdots \# x_{k} \mid k \geq 1\text{,每个 } x_{i} \in\{\mathrm{a}, \mathrm{b}\}^{*}\text{,并且对于某个 } i \text{ 和 } j\text{,} x_{i}=x_{j}^{\mathcal{R}}\right\}$
${ }^{\text{A}}$ 2.7 给出练习 2.6 中语言的 PDA 的非形式化英语描述。
${ }^{\text{A}}$ 2.8 证明字符串 the girl touches the boy with the flower 在第 103 页的文法 $G_2$ 中有两个不同的最左推导。用英语描述这个句子的两种不同含义。
2.9 给出生成语言
的上下文无关文法。您的文法是否有歧义?为什么或为什么不?
2.10 给出识别练习 2.9 中语言 $A$ 的下推自动机的非形式化描述。
2.11 使用定理 2.20 中给出的过程,将练习 2.1 中给出的 CFG $G_4$ 转换为等价的 PDA。
2.12 使用定理 2.20 中给出的过程,将练习 2.3 中给出的 CFG $G$ 转换为等价的 PDA。
2.13 令 $G=(V, \Sigma, R, S)$ 为以下文法。$V=\{S, T, U\};\Sigma=\{0, \#\}$;$R$ 是规则集:
a. 用英语描述 $L(G)$。
b. 证明 $L(G)$ 不是正则的。
2.14 使用定理 2.9 中给出的过程,将以下 CFG 转换为乔姆斯基范式中的等价 CFG。
2.15 给出反例,以证明以下构造未能证明上下文无关语言类在星运算下是闭合的。设 $A$ 是由 CFG $G=(V, \Sigma, R, S)$ 生成的 CFL。添加新规则 $S \rightarrow SS$,并将所得文法命名为 $G'$。这个文法应该生成 $A^*$。
2.16 证明上下文无关语言类在正则运算、并集、连接和星运算下是闭合的。
2.17 使用练习 2.16 的结果,给出另一个证明,证明每个正则语言都是上下文无关的,通过展示如何将正则表达式直接转换为等价的上下文无关文法。
这一部分是教材第二章的课后练习题,旨在巩固本章学习的核心概念,包括上下文无关文法 (CFG)、下推自动机 (PDA)、乔姆斯基范式 (CNF)、确定性与非确定性、歧义性以及闭包性质等。
练习部分是理论学习的延伸和巩固,覆盖了从基本概念理解、文法与自动机设计、算法应用到理论性质证明的方方面面。通过完成这些练习,学生可以将抽象的理论知识转化为解决具体问题的技能。
练习题的存在是为了让学生主动地与所学知识进行互动,通过实践来加深理解、发现盲点、并锻炼解决问题的能力。它们是连接“听懂了”和“会用了”之间的桥梁。
学习理论就像是学习一本武功秘笈。
学习本章理论就像是学习了如何读懂和设计电路图(CFG)以及如何构建和理解实际的电路(PDA)。练习题就像是:
[[END_OF_RESPONSE]]所有解释内容已全部输出完毕。