还没有笔记
选中页面文字后点击「高亮」按钮添加
📜 [原文1]
在第 1 章中,我们介绍了两种不同但等效的语言描述方法:有限自动机和正则表达式。我们展示了许多语言可以用这种方式描述,但某些简单的语言,例如 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$,则不能。
这段话是本章的引言,旨在承上启下。它首先回顾了前一章(第 1 章)的核心内容,即有限自动机(Finite Automata, FA)和正则表达式(Regular Expressions, RE)。这两个工具是计算理论中用于描述和识别一类被称为正则语言(Regular Languages)的强大工具。
“不同但等效”是一个关键点。有限自动机是一种计算模型,像一台简单的机器,通过状态转移来“读取”并判断一个字符串是否属于某个语言。而正则表达式是一种代数表达式,是一种描述性的文本模式,用来定义一个字符串集合。尽管它们的形式完全不同(一个是机器模型,一个是代数表达式),但它们所能描述的语言集合是完全相同的,这个集合就是正则语言。这就是“等效”的含义。这个等价性是计算理论中的一个基本且重要的结论。
接着,段落指出了有限自动机和正则表达式的局限性。虽然它们可以描述很多有用的语言(例如,电子邮件地址的格式、特定格式的日期等),但它们的能力是有限的。存在一些结构上看起来很简单,但有限自动机无法处理的语言。
这里给出的经典例子是语言 $L = \left\{0^{n} 1^{n} \mid n \geq 0\right\}$。这个语言包含的字符串是由任意数量(包括零)的 '0' 后面跟着相同数量的 '1' 组成的。例如,空字符串 $\varepsilon$ (当 $n=0$ 时)、"01" (当 $n=1$ 时)、"0011" (当 $n=2$ 时)、"000111" (当 $n=3$ 时) 等都属于这个语言。而 "011"、"001"、"10" 等则不属于。
为什么有限自动机无法识别这个语言呢?直观地理解,有限自动机的“记忆力”是有限的。它的记忆力体现在它的状态数量上。要判断一个字符串是否属于 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$,机器在读完所有的 '0' 之后,必须准确地记住 '0' 的数量 $n$,以便在接下来读取 '1' 时进行匹配。但是,$n$ 可以是任意大的非负整数。一个只有有限个状态的机器,无法存储一个可能无限增长的计数值。这就是有限自动机的根本局限性,这个局限性通常用泵引理(Pumping Lemma for Regular Languages)来形式化地证明。
因此,这段引言的目的是告诉我们,现有的工具(有限自动机和正则表达式)已经不够用了,我们需要一种更强大的方法来描述像 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$ 这样更复杂的语言。这为引入本章的主角——上下文无关文法——埋下了伏笔。
本段是第 2 章的开篇,通过回顾第 1 章的有限自动机和正则表达式,引出了它们的局限性。它以经典的非正则语言 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$ 为例,说明了需要更强大的描述工具来处理具有递归或依赖计数特性的语言,从而为本章引入上下文无关文法做了铺垫。
本段的目的是在知识体系上建立一座桥梁,连接已学的正则语言和将要学习的上下文无关语言。它通过指出旧工具的“能力天花板”,来论证学习新工具的必要性和动机,使得内容的展开显得自然且有逻辑。
你可以把有限自动机想象成一个只有短期记忆的人。他可以记住几个(有限个)关键信息,比如“我刚刚看到了一个'a'”或“我已经看到了奇数个'b'”。但如果你让他数一数一本书里总共有多少个'a',然后再告诉你这个数字,他就做不到了,因为书里的'a'可能非常多,超出了他的记忆极限。语言 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$ 就需要这种无限的计数能力,而有限自动机不具备。
想象一条单行道的隧道,汽车只能从一端进,另一端出。有限自动机就像一个站在隧道口的收费员,他只有一个小本子,上面只能记有限页的东西(状态)。如果规则是“进来多少辆红车,就必须紧跟着出去多少辆蓝车”,当红车源源不断地进来时($n$ 可以非常大),收费员的小本子很快就记满了,他无法确切知道到底进来了多少辆红车,也就无法在蓝车出去时进行核对。这就是有限自动机的困境。
📜 [原文2]
在本章中,我们将介绍上下文无关文法,这是一种更强大的语言描述方法。这类文法可以描述具有递归结构的某些特征,这使其在各种应用中都很有用。
这段话正式引入了本章的核心概念——上下文无关文法(Context-Free Grammar, CFG)。在上一段指出有限自动机的局限性之后,这里直接给出了解决方案。
“更强大的语言描述方法”是对上下文无关文法的一个定位。这里的“强大”是形式化的,意味着由上下文无关文法所能描述的语言集合(即上下文无关语言)是正则语言集合的一个超集。也就是说,所有正则语言都可以用上下文无关文法来描述,但上下文无关文法还能描述一些非正则语言,比如上一段提到的 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$。
接下来解释了上下文无关文法之所以“强大”的关键在于它能够描述“递归结构”。递归(Recursion)是指一个结构或规则的定义中包含了它自身的引用。在语言中,递归结构表现为“一个短语类型可以包含同类型的更小的短语”。例如,一个算术表达式可以由两个更小的算术表达式通过加号连接而成。 ( (3 + 4) * 5 ) 就是一个例子,外层的表达式包含了内层的 (3 + 4)。这种能够自我嵌套的特性,正是上下文无关文法所擅长描述的,也是有限自动机无法处理的。
最后,段落提到了上下文无关文法的实用性——“在各种应用中都很有用”。这表明上下文无关文法不仅仅是一个理论工具,它在实际中有着广泛的应用。这为后面详细介绍其应用场景(如人类语言学和编程语言编译)做了引子。
本段明确提出了本章要介绍的核心工具——上下文无关文法 (CFG)。它将 CFG 定位为一种比有限自动机更强大的语言描述方法,其强大之处在于能够捕捉语言中的递归结构。同时,它也强调了 CFG 的实用价值,预示了其在多个领域的广泛应用。
本段的核心目的是为读者建立对上下文无关文法的初步认知。它回答了三个基本问题:1. 我们要学什么?(上下文无关文法) 2. 它有什么特点?(更强大,能描述递归结构) 3. 学它有什么用?(应用广泛)。这为后续深入学习打下了概念基础。
如果说有限自动机是只有短期记忆的人,那么上下文无关文法就像一套俄罗斯套娃的制作说明书。说明书告诉你:“一个套娃可以由一个更小的套娃涂上油漆再装进一个外壳里组成”。通过反复应用这条规则,你可以制作出任意多层的套娃。这个“自我引用”的制作规则就是递归。上下文无关文法就是这样一套规则,它能生成具有嵌套或递归特性的语言。
想象一下你在用乐高积木搭建。有限自动机的规则集就像是“红砖旁边只能放蓝砖,蓝砖旁边只能放绿砖”这类简单的“邻接规则”。你只能搭出一条彩色的线。而上下文无关文法的规则集则像是“一个‘房子’可以由四面‘墙’和一个‘屋顶’组成”,而一面‘墙’又可以由很多‘砖块’组成,甚至一个‘大房子’的院子里可以建一个‘小房子’。这种分层、嵌套的组合能力,就是上下文无关文法描述递归结构能力的直观体现。
📜 [原文3]
上下文无关文法最初用于研究人类语言。理解名词、动词、介词等术语及其各自短语之间关系的一种方法,自然会导致一种递归,因为名词短语可能出现在动词短语中,反之亦然。上下文无关文法帮助我们组织和理解这些关系。
这段话介绍了上下文无关文法的第一个重要应用领域:自然语言处理(Natural Language Processing, NLP),特别是其在语言学中的起源。
“最初用于研究人类语言”点明了其历史背景。在计算机科学领域之外,语言学家(如诺姆·乔姆斯基)为了形式化地描述人类语言的语法结构,发展了这套理论。
接下来,段落解释了为什么上下文无关文法适合描述人类语言。人类语言的句子不是单词的随意堆砌,而是具有层次化的语法结构。句子由短语构成,短语又由更小的短语或单词构成。这里提到了几个基本的语法单位:“名词”、“动词”、“介词”以及它们构成的“短语”(如名词短语、动词短语)。
核心思想在于这些短语之间的“递归”关系。“名词短语可能出现在动词短语中”,例如,在动词短语“sees the boy”(看见那个男孩)中,“the boy”是一个名词短语。“反之亦然”指的是动词短语也可能出现在名词短语中,虽然这种情况在英语中不那么直接,但可以通过从句等形式实现,例如名词短语“the man who runs fast”(跑得快的男人)中,"who runs fast"起到了修饰作用,其核心是动词"runs"。更典型的例子是,一个名词短语可以包含另一个名词短语,如“the corner of the room”(房间的角落),其中“the room”是内嵌在更大的名词短语里的。
“上下文无关文法帮助我们组织和理解这些关系”是对其作用的总结。通过一套形式化的规则,语言学家可以精确地描述哪些句子是合法的,哪些是不合法的,并且能够揭示句子的内部结构。这种结构化的分析对于理解语言、机器翻译和人机对话等任务至关重要。
本段阐述了上下文无关文法在语言学中的起源和核心应用。它通过分析人类语言中短语之间普遍存在的递归和嵌套关系,说明了上下文无关文法是组织和理解这些复杂语法结构的有力工具。
本段的目的是展示上下文无关文法的第一个,也是最原始的应用场景——人类语言研究。这不仅丰富了我们对该工具的认识,也通过一个我们熟悉的领域(自己的母语或外语)帮助我们直观地理解抽象的“递归结构”概念。
想象一下你在分析一个复杂的句子,就像在画一棵语法树。你会先把句子分成主语(一个名词短语)和谓语(一个动词短语)。然后你再分别去看主语和谓语的构成,可能会发现它们里面又包含了更小的短语。这个过程不断地分解下去,直到你分析到单个的单词为止。上下文无关文法就是这套分解规则的集合。它告诉你什么样的组合是合法的,以及如何进行这种层次化的分解。
把一个句子想象成一个公司组织架构图。CEO是整个句子(SENTENCE)。他下面有几个副总裁,比如“名词短语部”和“动词短语部”。每个副总裁下面又有经理(比如“介词短语”、“形容词”),经理下面是普通员工(具体的单词 "the", "boy", "sees")。有时候,“名词短语部”的一个项目需要从“介词短语部”借调一个团队,而那个团队里可能又有一个来自“名词短语部”的顾问。这种部门间的嵌套和调用关系,就是上下文无关文法所描述的递归关系。
📜 [原文4]
上下文无关文法的一个重要应用是编程语言的规范和编译。编程语言的文法通常作为人们学习语言语法时的参考。编程语言的编译器和解释器设计者通常从获取语言的文法开始。大多数编译器和解释器都包含一个称为解析器的组件,它在生成编译代码或执行解释之前提取程序的意义。一旦有了上下文无关文法,许多方法都能促进解析器的构建。有些工具甚至可以根据文法自动生成解析器。
这段话介绍了上下文无关文法在计算机科学领域最核心的应用:编程语言。
“编程语言的规范和编译”点出了两个关键环节。
段落接着详细阐述了文法在编译过程中的作用。编译器或解释器的设计者首先需要得到该语言的上下文无关文法。这是设计的第一步。
编译器和解释器中有一个核心组件叫解析器(Parser)。解析器的任务是读取源代码字符串,并根据文法规则来分析其语法结构,判断代码是否符合语法。这个过程称为解析(Parsing)或语法分析。如果语法正确,解析器会生成一个数据结构来表示代码的层次化意义,这个结构通常是抽象语法树(Abstract Syntax Tree, AST),它捕捉了代码的运算顺序、依赖关系等。这个“提取程序的意义”的过程是后续生成可执行代码或进行解释执行的基础。
例如,对于代码 a = b + c d,解析器会根据文法中定义的运算符优先级,生成一棵树,表明应该先计算 c d,然后将其结果与 b 相加,最后再将总结果赋值给 a。没有文法和解析器,编译器就无法理解这段代码的真正意图。
最后一段强调了文法的工具价值。“一旦有了上下文无关文法”,构建解析器就变得有章可循。存在许多成熟的理论和算法(如 LL 解析、LR 解析)来指导如何手动编写一个解析器。更方便的是,存在一些“解析器生成器”(Parser Generator)工具,例如 YACC (Yet Another Compiler-Compiler) 或 Bison。你只需要把语言的上下文无关文法作为输入提供给这些工具,它们就能自动生成对应的解析器代码。这极大地简化了编译器和解释器的开发工作。
```yacc
%token NUMBER
%%
expr: expr '+' term { $$ = $1 + $3; }
| term { $$ = $1; }
;
term: term '*' factor { $$ = $1 * $3; }
| factor { $$ = $1; }
;
factor: '(' expr ')' { $$ = $2; }
| NUMBER { $$ = $1; }
;
%%
```
本段详细阐述了上下文无关文法在计算机科学中的核心应用:作为编程语言的规范和编译/ 解释过程的基础。它解释了文法如何被解析器用来分析代码结构、提取意义,并强调了文法对于自动化构建编译器工具(如解析器生成器)的重要性。
本段的目的是将上下文无关文法这个理论工具与一个非常具体、重要的实践领域——编程语言开发——联系起来。这不仅展示了其巨大的实用价值,也为学习计算机科学的学生提供了强烈的学习动机,因为这直接关系到他们日常使用的工具(编译器、解释器)是如何工作的。
上下文无关文法就像一本编程语言的“官方食谱”。解析器就是一位严格按照食谱做菜的厨师。源代码是顾客的订单。厨师(解析器)拿到订单(源代码),会逐行对照食谱(文法),检查订单写得是否规范。如果规范,他就知道这道菜(程序)的烹饪步骤(运算顺序和结构),然后开始备料、烹饪(生成机器代码)。如果订单不符合食谱(语法错误),厨师就会拒绝服务并指出哪里写错了。像 YACC 这样的工具,则是一个“食谱机器人”,你给它食谱,它就能变出一个严格的厨师来。
想象一下你正在组装一件宜家家具。那本只有图画和零件编号的说明书,就是这件家具的“文法”。它规定了A零件必须和B零件用C号螺丝连接,然后整体再插入D零件的孔中。你(解析器)看着一堆零件(源代码),严格按照说明书(文法)的步骤进行组装(解析)。如果你发现少了个零件,或者想把A零件强行接到Z零件上(说明书里没这么画),你就知道出错了(语法错误)。当你严格按照说明书组装完成后,你就得到了一个具有特定结构和功能的椅子(程序的语法树),而不是一堆无意义的木板和螺丝。
📜 [原文5]
与上下文无关文法相关的语言集合称为上下文无关语言。它们包括所有正则语言以及许多额外的语言。在本章中,我们给出上下文无关文法的形式化定义,并研究上下文无关语言的性质。我们还将介绍下推自动机,这是一类识别上下文无关语言的机器。下推自动机很有用,因为它们使我们能够更深入地了解上下文无关文法的力量。
这段话是整个引言的总结,并预告了本章即将展开的主要内容。
“与上下文无关文法相关的语言集合称为上下文无关语言 (Context-Free Languages, CFLs)。” 这句话给出了一个核心定义。就像由正则表达式描述的语言集合被称为正则语言一样,所有能够被某一个上下文无关文法生成的语言,其集合被称为上下文无关语言。这是一个新的、更庞大的语言类别。
“它们包括所有正则语言以及许多额外的语言。” 这句话阐明了上下文无关语言和正则语言之间的关系。正则语言是上下文无关语言的真子集(Proper Subset)。这意味着:
这在形式上确认了上下文无关文法是比有限自动机或正则表达式“更强大”的描述工具。
接下来是本章的内容路线图:
本段作为引言的结尾,明确定义了上下文无关语言 (CFL) 这一核心概念,并阐述了它与正则语言的层级关系(CFL 包含所有正则语言)。同时,它清晰地勾勒出本章的学习路线:学习上下文无关文法的形式化定义、研究 CFL 的性质,并引入与之等价的计算模型——下推自动机,以从机器识别的角度加深理解。
本段的目的是为读者提供一个清晰的“学习地图”。在进入具体的、可能充满细节和数学符号的内容之前,让读者对本章的整体结构、核心概念及其相互关系有一个宏观的把握。这有助于读者在学习过程中保持方向感,更好地组织和吸收新知识。
把学习计算理论的过程想象成升级你的工具箱。第 1 章你得到了锤子(正则表达式)和螺丝刀(有限自动机),它们很好用,但只能处理一些简单的钉子和螺丝(正则语言)。本章告诉你,有一个更复杂的任务(比如处理 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$),你需要新工具。于是,你将得到一本“蓝图”(上下文无关文法)和一台“3D 打印机”(下推自动机)。蓝图告诉你如何设计和生成复杂的零件(上下文无关语言),而 3D 打印机可以根据设计来实际制造或验证这些零件。它们的能力比锤子和螺丝刀更强。
想象一场法庭辩论。上下文无关文法就像是检察官,他要“构建”一个完整的犯罪故事(生成一个字符串),从动机到行为,一步步推导出结论。下推自动机则像是辩护律师,他面对一个给定的“指控”(输入字符串),需要用证据(栈操作)来一步步“验证”这个指控是否成立。虽然角色不同,但他们都围绕着同一套法律(语言规则)工作。本章就是要学习这套更高级的法律以及检察官和律师的工作方法。
📜 [原文6]
以下是上下文无关文法的一个示例,我们称之为 $G_{1}$。
文法由一系列替换规则组成,也称为产生式。每条规则都以文法中的一行出现,包含一个符号和一个由箭头分隔的字符串。该符号称为变量。该字符串由变量和称为终结符的其他符号组成。变量符号通常用大写字母表示。终结符类似于输入字母表,通常用小写字母、数字或特殊符号组成。其中一个变量被指定为开始变量。它通常出现在最顶层规则的左侧。例如,文法 $G_{1}$ 包含三条规则。$G_{1}$ 的变量是 $A$ 和 $B$,其中 $A$ 是开始变量。它的终结符是 0, 1 和 \#。
这一部分开始正式介绍上下文无关文法(CFG)的具体构成。它通过一个非常经典的例子 $G_1$ 来展开说明。
首先,给出了一个名为 $G_1$ 的文法,它包含三条规则:
然后,对文法的组成要素进行了详细拆解:
最后,对 $G_1$ 进行了总结:
这一段通过一个实例,清晰地定义了上下文无关文法的四个基本组成部分:变量、终结符、规则和开始变量。这是理解后续所有内容的基础。
```
S -> aSb
S -> ε
```
```
E -> E + T
E -> T
T -> T * F
T -> F
F -> (E)
F -> id
```
本段通过一个具体的例子 $G_1$,详细介绍了构成上下文无关文法 (CFG) 的四个基本组件:规则(或产生式)、变量(非终结符)、终结符和开始变量。它建立了后续讨论 CFG 工作方式所必需的基础词汇和概念。
此段的目的是从一个具体的、简单的实例入手,避免一开始就进行抽象的形式化定义,从而降低读者的认知门槛。通过“解剖麻雀”的方式,让读者对文法的组成部分有一个直观且准确的认识,为理解文法如何“工作”做好准备。
一个上下文无关文法就像一套菜谱。
想象你是一个玩具工厂的生产线控制器。
你的任务就是从最终产品的型号开始,按照装配指令不断地把组件代号替换成更小的组件或基本零件,直到最后只剩下基本零件的组合,一个完整的玩具车就生产出来了。
📜 [原文7]
你通过以下方式生成该语言的每个字符串来使用文法描述语言。
例如,文法 $G_{1}$ 生成字符串 000\#111。获取字符串的替换序列称为推导。文法 $G_{1}$ 中字符串 000\#111 的推导是
你也可以用分析树以图形方式表示相同的信息。分析树的一个示例如图 2.1 所示。

图 2.1
文法 $G_{1}$ 中 000\#111 的分析树
这一部分详细描述了如何使用一个文法来生成语言中的字符串,并引入了两个关键概念:推导和分析树。
首先,给出了一个三步走的算法流程,解释了“生成”一个字符串的过程:
接着,通过一个具体的例子来演示这个过程。目标是使用文法 $G_1$ 生成字符串 000#111。这个一步步替换的过程被称为推导 (Derivation)。
推导过程的符号是 $\Rightarrow$,读作“一步推导出”。
$A \Rightarrow 0A1$:
$0A1 \Rightarrow 00A11$:
$00A11 \Rightarrow 000A111$:
$000A111 \Rightarrow 000B111$:
$000B111 \Rightarrow 000\#111$:
这个序列完整地展示了从开始变量 $A$ 如何一步步生成最终字符串 000#111 的过程。
最后,引入了分析树(Parse Tree)或推导树(Derivation Tree)的概念。它是推导过程的图形化表示,能更直观地展示字符串的语法结构。
分析树清晰地展示了字符串的层次结构,例如,000#111 是由一个顶层的 $A$ 结构生成,这个 $A$ 分解为 0、一个子 $A$ 和 1,这个子 $A$ 又同样分解...直到最后通过 $B$ 产生 #。
$A \Rightarrow 0A1$ (应用 $A \rightarrow 0A1$ 一次)
$\Rightarrow 0B1$ (应用 $A \rightarrow B$)
$\Rightarrow 0\#1$ (应用 $B \rightarrow \#$)
$A \Rightarrow B$ (应用 $A \rightarrow B$)
$\Rightarrow \#$ (应用 $B \rightarrow \#$)
本段解释了如何使用上下文无关文法通过一个称为推导的逐步替换过程来生成语言中的字符串。它还引入了分析树作为推导过程的图形化表示,它能更清晰地揭示生成字符串的层次化语法结构。
本段的目的是将静态的文法规则和动态的字符串生成过程联系起来,让读者明白文法是如何“运作”的。通过推导和分析树这两个核心概念,为后续讨论语言的定义、文法的歧义性等问题奠定了操作基础。
推导就像是在执行一份菜谱的步骤。你从主菜名(开始变量)开始,然后一步步地执行指令(应用规则),把半成品(变量)替换成更小的半成品或者基本食材(终结符),直到最后得到一道完整的菜(最终字符串)。
分析树就像是这道菜的“解构图”。它展示了这道菜最终是由哪些基本食材,通过哪些半成品步骤组合而成的。比如,最终的“红烧肉”是由“炒好的糖色”和“焯过水的五花肉”组成,而“炒好的糖色”又是由“冰糖”和“油”加热而来。这个层次关系就是分析树。
想象一下你正在用 Photoshop 进行图像合成。
📜 [原文8]
以这种方式生成的所有字符串构成文法的语言。我们将文法 $G_{1}$ 的语言表示为 $L\left(G_{1}\right)$。对文法 $G_{1}$ 进行一些实验,我们发现 $L\left(G_{1}\right)$ 是 $\left\{0^{n} \# 1^{n} \mid n \geq 0\right\}$。任何可以通过某些上下文无关文法生成的语言都称为上下文无关语言 (CFL)。为方便起见,在呈现上下文无关文法时,我们将几个具有相同左侧变量的规则(例如 $A \rightarrow 0 A 1$ 和 $A \rightarrow B$)缩写为一行 $A \rightarrow 0 A 1 \mid B$,其中符号 " $\mid$ " 表示 "或"。
这段话正式定义了由文法生成的语言,并介绍了一些约定俗成的表示法。
```
E -> E + T
E -> T
```
```
E -> E + T | T
```
本段将文法的生成能力与语言的概念正式挂钩,定义了文法的语言 $L(G)$。通过分析 $G_1$ 的生成过程,得出其语言为 $\left\{0^{n} \# 1^{n} \mid n \geq 0\right\}$。在此基础上,给出了上下文无关语言 (CFL) 的一般定义。最后,介绍了一种常用的文法书写简化规则 |。
此段的目的是完成从“文法如何工作”到“文法定义了什么”的关键一步。它正式定义了上下文无关语言这一核心研究对象,并将前面所有的技术细节(推导、变量、终结符)汇集到一个有意义的集合——语言——上,为后续的理论分析(如语言的性质、分类)建立了基础。
文法 $G$ 是一台“字符串制造机”的设计蓝图。$L(G)$ 就是这台机器所有可能生产出来的、并且通过了质检(即不含任何半成品零件/变量)的“最终产品”的完整目录。上下文无关语言就是所有可以用这种“蓝图+机器”模式制造出来的产品的总类别。| 规则就像蓝图上的一个注释:“在第5步,你可以使用A号螺丝,也可以使用B号螺丝”。
想象一个分形图案的生成规则(例如科赫雪花)。
📜 [原文9]
以下是上下文无关文法的第二个示例,称为 $G_{2}$,它描述了英语的一个片段。
```
$\langle$ SENTENCE $\rangle \rightarrow\langle$ NOUN-PHRASE $\rangle\langle$ VERB-PHRASE $\rangle$
$\langle$ NOUN-PHRASE $\rangle \rightarrow\langle$ CMPLX-NOUN $\rangle \mid\langle$ CMPLX-NOUN $\rangle\langle$ PREP-PHRASE $\rangle$
$\langle$ VERB-PHRASE $\rangle \rightarrow\langle$ CMPLX-VERB $\rangle \mid\langle$ CMPLX-VERB $\rangle\langle$ PREP-PHRASE $\rangle$
$\langle$ PREP-PHRASE $\rangle \rightarrow\langle$ PREP $\rangle\langle$ CMPLX-NOUN $\rangle$
$\langle$ CMPLX-NOUN $\rangle \rightarrow\langle$ ARTICLE $\rangle\langle$ NOUN $\rangle$
$\langle$ CMPLX-VERB $\rangle \rightarrow\langle$ VERB $\rangle \mid\langle$ VERB $\rangle\langle$ NOUN-PHRASE $\rangle$
$\langle$ ARTICLE $\rangle \rightarrow \mathrm{a} \mid$ the
〈NOUN〉 → boy | girl | flower
|VERB → touches | likes | sees
$\langle$ PREP $\rangle \rightarrow$ with
```
文法 $G_{2}$ 有 10 个变量(括号内大写的语法术语);27 个终结符(标准英文字母加上一个空格字符);以及 18 条规则。$L\left(G_{2}\right)$ 中的字符串包括:
```
a boy sees
the boy sees a flower
a girl with a flower likes the boy
```
这些字符串中的每一个在文法 $G_{2}$ 中都有推导。以下是此列表中第一个字符串的推导。
这一部分通过一个更复杂、更实际的例子 $G_2$ 来进一步展示上下文无关文法的应用,这次是模拟自然语言——英语的语法。
首先,给出了文法 $G_2$ 的所有规则。
接下来,对 $G_2$ 的规模进行了统计:10 个变量,27 个终结符(26个字母+空格),18 条规则(通过计算 | 的数量可以得出)。
然后,列举了三个可以通过这个文法生成的句子,展示了该文法的生成能力:
最后,为了证明这些句子确实是语言 $L(G_2)$ 的成员,详细演示了第一个句子 a boy sees 的推导过程。这个推导从开始变量 <SENTENCE> 出发,一步步应用规则,将变量替换为更小的变量或终结符,最终得到了完全由终结符构成的目标句子。
这个推导过程清晰地展示了句子的语法结构是如何被文法规则逐步构建出来的:
整个过程就像一个语法分析的逆过程,从最高层的句子结构逐步填充细节,直至生成最终的单词序列。
<SENTENCE>
⇒ <NOUN-PHRASE><VERB-PHRASE>
⇒ <CMPLX-NOUN><VERB-PHRASE>
⇒ <ARTICLE><NOUN><VERB-PHRASE>
⇒ the <NOUN><VERB-PHRASE>
⇒ the girl <VERB-PHRASE>
⇒ the girl <CMPLX-VERB>
⇒ the girl <VERB><NOUN-PHRASE> (这次动词带宾语)
⇒ the girl likes <NOUN-PHRASE>
⇒ the girl likes <CMPLX-NOUN>
⇒ the girl likes <ARTICLE><NOUN>
⇒ the girl likes a <NOUN>
⇒ the girl likes a flower
<SENTENCE>
⇒ <NOUN-PHRASE><VERB-PHRASE>
⇒ <CMPLX-NOUN><PREP-PHRASE><VERB-PHRASE> (应用了 <NP> -> <CN><PP> 规则)
⇒ <ARTICLE><NOUN><PREP-PHRASE><VERB-PHRASE>
⇒ a boy <PREP-PHRASE><VERB-PHRASE>
⇒ a boy <PREP><CMPLX-NOUN><VERB-PHRASE> (应用了 <PP> -> <P><CN>)
⇒ a boy with <CMPLX-NOUN><VERB-PHRASE>
⇒ a boy with <ARTICLE><NOUN><VERB-PHRASE>
⇒ a boy with a flower <VERB-PHRASE>
... (后续过程类似)
本段通过一个描述部分英语语法的上下文无关文法 $G_2$ 作为实例,具体展示了 CFG 如何用于建模自然语言。它详细列出了文法的规则、组成成分,并给出了几个生成句子的例子,最后还完整演示了其中一个句子的推导过程,从而将抽象的文法规则与具体的句子生成实践联系起来。
本段的目的是提供一个比 $G_1$ 更大规模、更贴近实际应用的例子,以巩固读者对 CFG 的理解。通过使用读者熟悉的自然语言作为模型,可以更直观地理解文法规则如何捕捉层次化结构(句子-短语-单词),以及推导过程如何模拟句子的构建。
这个文法就像一个“句子自动生成器”的程序。程序从 "生成一个句子" 的指令开始,然后根据内部的语法规则库,随机(或按一定策略)选择规则来逐步构建。比如,它先决定句子是“主语+谓语”结构,然后决定主语是“冠词+名词”,再决定冠词是'a',名词是'boy',一步步往下走,最终拼凑出一个完整的、语法正确的句子。
想象你在玩一个语法版的“疯狂填词”游戏。你有一张卡片,上面写着 ______ (一个 <SENTENCE>)。游戏规则书(文法)说,你可以把它换成两张更小的卡片:______(一个 <NOUN-PHRASE>) 和 ______(一个 <VERB-PHRASE>)。你继续查规则书,发现 <NOUN-PHRASE> 卡片可以换成 <ARTICLE> 和 <NOUN> 两张卡片。最后,规则书告诉你 <ARTICLE> 可以直接填入单词 'a' 或 'the'。你不断地将这些代表“语法槽位”的卡片替换成更小的槽位,或者直接填入单词,直到所有卡片上都填满了单词,一个完整的句子就诞生了。这个填词的过程就是推导。
📜 [原文10]
让我们将上下文无关文法 (CFG) 的概念形式化。
上下文无关文法是一个四元组 ( $V, \Sigma, R, S$ ),其中
如果 $u, v$ 和 $w$ 是变量和终结符的字符串,并且 $A \rightarrow w$ 是文法的一条规则,我们说 $u A v$ 导出 $u w v$,记作 $u A v \Rightarrow u w v$。如果 $u=v$ 或存在一个序列 $u_{1}, u_{2}, \ldots, u_{k}$,其中 $k \geq 0$ 且
我们说 $u$ 推导 $v$,记作 $u \stackrel{*}{\Rightarrow} v$。
文法的语言是 $\left\{w \in \Sigma^{*} \mid S \stackrel{*}{\Rightarrow} w\right\}$。
这一部分从直观描述转向了严格的数学定义,将上下文无关文法及其相关概念形式化。
首先,定义 2.2 将上下文无关文法 (CFG) 定义为一个四元组 $(V, \Sigma, R, S)$。这是一种在数学和计算机科学中常见的定义方式,通过一个元组来精确地规定一个对象的构成要素。
接下来,形式化定义了推导过程:
这个 * 符号是克莱尼星号(Kleene Star)的推广,表示“零次或多次应用”。
最后,基于多步推导,给出了由文法 $G$ 生成的语言 $L(G)$ 的形式化定义:
这个定义精确、无歧义地界定了上下文无关文法是什么,以及它是如何工作的。
本节为上下文无关文法 (CFG) 提供了一个严格的、基于集合论的形式化定义。它将 CFG 定义为一个四元组 $(V, \Sigma, R, S)$,并精确定义了“一步推导” ($\Rightarrow$)、“多步推导” ($\stackrel{*}{\Rightarrow}$) 和文法生成的语言 $L(G)$。这套形式化体系是后续所有理论分析和证明的基石。
本段的目的是将之前通过例子建立的直观理解,提升到数学上严谨的层面。在科学和工程中,形式化是必不可少的一步,它消除了模糊性,使得概念可以被精确地操作和推理。这为后续证明文法的等价性、研究语言的性质、设计算法等工作铺平了道路。
形式化定义就像是为之前直观的“菜谱”模型编写了一份“ISO 国际标准”。
想象一下你在为乐高积木编写官方的设计规范。
📜 [原文11]
在文法 $G_{1}$ 中,$V=\{A, B\}$,$\Sigma=\{0,1, \#\}$,$S=A$,且 $R$ 是第 102 页上出现的三条规则的集合。在文法 $G_{2}$ 中,
$\Sigma=\{\mathrm{a}, \mathrm{b}, \mathrm{c}, \ldots, \mathrm{z}$, " " $\}$。符号 " " 是空格符号,不可见地放在每个单词(a, boy 等)之后,这样单词就不会连在一起。
我们通常只通过写下文法的规则来指定文法。我们可以将变量识别为出现在规则左侧的符号,并将终结符识别为其余的符号。按照惯例,开始变量是第一条规则左侧的变量。
这段话将前面给出的形式化定义应用到之前的两个例子 $G_1$ 和 $G_2$ 上,并阐述了在实际操作中描述一个文法的常用惯例。
首先,它精确地用四元组的形式“翻译”了文法 $G_1$:
这展示了如何将一个写出的文法映射到其形式化定义的四元组上。
接着,它对更复杂的文法 $G_2$ 也做了同样的分析:
然后,引出了一套非常重要的书写惯例。在实践中,每次都写出完整的四元组 $(V, \Sigma, R, S)$ 会非常繁琐。因此,大家约定俗成了一套简化表示法:
这套惯例大大简化了文法的书写,使得我们可以仅通过规则列表就能完整地、无歧义地定义一个文法。前面的 $G_1$ 和 $G_2$ 就是按照这个惯例给出的。
```
E -> E + T | T
T -> T * F | F
F -> (E) | id
```
```
// Grammar for simple assignments
// Start symbol is <assign>
<assign> -> <id> = <expr>
<expr> -> <id> + <id> | <id>
<id> -> a | b | c
```
本段将形式化定义与具体例子相结合,演示了如何将一个实际的文法表示为四元组。更重要的是,它阐明了一套广泛使用的书写惯例:仅通过书写规则,就可以隐式地、无歧义地定义整个上下文无关文法的四个组成部分 ($V, \Sigma, R, S$)。这为后续所有文法的呈现方式提供了背景说明。
本段的目的是在严谨的形式化定义和便捷的日常实践之间架起一座桥梁。它告诉读者,虽然我们有了精确的数学基础,但在交流和工作中,我们会使用一套更简洁的“速记法”。理解这套速记法是如何工作的,对于能够读懂和写出文法至关重要。
这就像在化学中,一个水分子的形式化定义是“由两个氢原子和一个氧原子通过共价键连接而成的化合物”。但在实践中,我们几乎总是直接写它的化学式 $H_2O$。这个化学式就是一种“惯例”,我们看到它就能自动推断出所有的组成信息(有哪些原子,分别有几个,是什么连接方式)。本段解释的就是上下文无关文法的“化学式”写法。
想象你在网上分享一个菜谱。
```json
{
"start_dish": "红烧肉",
"semi_finished_products": ["面团", "酱汁"],
"basic_ingredients": ["猪肉", "酱油", "糖"],
"steps": [
{"from": "红烧肉", "to": "焯水的猪肉 + 酱汁"},
...
]
}
```
红烧肉 -> 焯水的猪肉 + 酱汁
酱汁 -> 酱油 + 糖
...
📜 [原文12]
考虑文法 $G_{3}=(\{S\},\{\mathrm{a}, \mathrm{b}\}, R, S)$。规则集 $R$ 是
这个文法生成像 abab、aaabbb 和 aababb 这样的字符串。如果你将 a 视为左括号 "(",b 视为右括号 ")",你就能更容易地看出这种语言是什么。从这个角度看,$L\left(G_{3}\right)$ 是所有正确嵌套括号的字符串的语言。请注意,规则的右侧可以是空串 $\varepsilon$。
这个例子介绍了一个非常经典的上下文无关文法 $G_3$,它用来生成所有正确嵌套括号的字符串。
首先,明确了文法 $G_3$ 的形式化定义:
接下来分析这三条规则的作用:
通过这三条规则的组合,可以生成所有形式的合法括号串。
段落给出了几个 $G_3$ 生成的字符串例子:abab, aaabbb, aababb。
为了帮助理解,段落提出了一个绝佳的类比:将 'a' 想象成左括号 (, 将 'b' 想象成右括号 )。
在这个类比下,$L(G_3)$ 就是所有正确嵌套括号的语言。例如:
最后,再次强调了 ε 规则的重要性,它是递归能够终止的基础。没有它,推导将无限进行下去,无法生成任何有限长度的字符串。
这两种推导对应不同的分析树,一种是 (S S) S 结构,另一种是 S (S S) 结构。对于编程语言来说,这种歧义通常是需要避免的。
本例介绍了一个重要且经典的上下文无关文法 $G_3$,它精确地生成了所有正确嵌套括号的语言。通过类比括号,该例子清晰地解释了文法中递归的两种主要形式:嵌套 ($S \rightarrow \mathrm{a}S\mathrm{b}$) 和并列 ($S \rightarrow SS$),以及基本情况 ($S \rightarrow \varepsilon$) 如何共同作用来定义一个复杂的、具有递归结构的语言。
本段的目的是通过一个与 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$ 类似但结构更丰富的例子,来深化读者对 CFG 递归能力的理解。它展示了 CFG 不仅能处理简单的“匹配计数”(如 $G_1$),还能处理更复杂的结构,即嵌套和并列的任意组合,这在描述程序结构、数学表达式等方面非常常见。
想象你在写一个数学证明。
通过这些规则,你可以从公理出发,构建出任意复杂的证明体系。
想象你在吹泡泡。
$L(G_3)$ 就是你用这两种方式(包裹、并列)能吹出的所有泡泡组合的形态。
📜 [原文13]
考虑文法 $G_{4}=(V, \Sigma, R,\langle\mathrm{EXPR}\rangle)$。
$V$ 是 $\{\langle \text{EXPR} \rangle,\langle \text{TERM} \rangle,\langle \text{FACTOR} \rangle\}$,$\Sigma$ 是 $\{\mathrm{a},+, \times,(), \ \}$。规则是
字符串 $\mathrm{a}+\mathrm{a} \times \mathrm{a}$ 和 $(\mathrm{a}+\mathrm{a}) \times \mathrm{a}$ 可以用文法 $G_{4}$ 生成。分析树如下图所示。

图 2.5
字符串 $\mathrm{a}+\mathrm{a} \times \mathrm{a}$ 和 $(\mathrm{a}+\mathrm{a}) \times \mathrm{a}$ 的分析树
这个例子展示了上下文无关文法在描述编程语言中算术表达式的强大能力,特别是如何利用文法结构来强制实现运算符的优先级和结合性。
首先,给出了文法 $G_4$ 的形式化定义:
这个三层结构(表达式-项-因子)是编译器设计中用于处理带优先级的表达式的经典文法设计模式。
接着,展示了用这个文法生成的两个字符串 a+a×a 和 (a+a)×a 的分析树。
这个例子有力地证明了,一个精心设计的上下文无关文法不仅能定义哪些字符串是合法的,还能通过其推导结构(即分析树的形状)来赋予字符串唯一的、正确的语义解释。
⇒ <EXPR> + <TERM> (顶层是加法)
⇒ <TERM> + <TERM> (左边的 <EXPR> 简化为 <TERM>)
⇒ <FACTOR> + <TERM> (再简化)
⇒ a + <TERM> (最终简化为 a)
⇒ a + <TERM> × <FACTOR> (右边的 <TERM> 展开为乘法)
⇒ a + <FACTOR> × <FACTOR> (左边的 <TERM> 简化)
⇒ a + a × <FACTOR> (简化为 a)
⇒ a + a × a (简化为 a)
⇒ <TERM> (顶层结构是一个 TERM,因为最终是乘法)
⇒ <TERM> × <FACTOR>
⇒ <FACTOR> × <FACTOR>
⇒ ( <EXPR> ) × <FACTOR> (左边的 <FACTOR> 展开为括号表达式)
⇒ ( <EXPR> + <TERM> ) × <FACTOR> (括号内的 <EXPR> 展开为加法)
... (继续在括号内进行推导,最终得到 (a+a))
⇒ (a+a) × a (右边的 <FACTOR> 简化为 a)
本例通过文法 $G_4$ 展示了如何使用上下文无关文法来描述算术表达式,并巧妙地利用文法的层次结构(表达式-项-因子)来编码运算符的优先级(乘法高于加法)和结合性(左结合)。通过对比 a+a×a 和 (a+a)×a 的分析树,清晰地说明了该文法如何确保表达式被正确地、无歧义地解析,这在编译器设计中至关重要。
本段的目的是展示 CFG 在解决实际编程问题中的一个核心应用和高级技巧。它超越了简单的语言成员识别,进入到如何利用文法来表达“语义”的层面。通过这个例子,读者可以理解文法设计不仅仅是“能生成就行”,更是“要生成得有意义、无歧义”,这为理解编译器前端的工作原理提供了深刻的洞见。
这个文法就像一个严格的数学老师在教你运算顺序。
这个从因子到项再到表达式的逐级定义,就自然而然地规定了“先括号,再乘法,最后加法”的运算顺序。
想象一条装配线,有三个工位:因子工位、项工位、表达式工位。
这个流水线的顺序(因子 -> 项 -> 表达式)决定了组装的顺序,× 号螺丝总是在 + 号螺丝之前被使用。
📜 [原文14]
编译器将用编程语言编写的代码转换为另一种形式,通常是更适合执行的形式。为此,编译器在称为解析的过程中提取待编译代码的含义。这种含义的一种表示是代码的分析树,它位于编程语言的上下文无关文法中。我们将在定理 7.16 和问题 7.22 中讨论一种解析上下文无关语言的算法。
文法 $G_{4}$ 描述了与算术表达式相关的编程语言片段。请注意图 2.5 中的分析树如何“分组”操作。字符串 $\mathrm{a}+\mathrm{a} \times \mathrm{a}$ 的树将 $\times$ 运算符及其操作数(后两个 a)分组为 + 运算符的一个操作数。在字符串 $(\mathrm{a}+\mathrm{a}) \times \mathrm{a}$ 的树中,分组顺序颠倒。这些分组符合乘法先于加法的标准优先级,以及使用括号覆盖标准优先级。文法 $G_{4}$ 旨在捕获这些优先级关系。
这段话是对例子 2.4 的进一步阐述和总结,旨在强调文法、分析树与编译器工作原理之间的深刻联系。
第一段首先概括了编译器的核心功能:代码转换。然后聚焦于编译过程中的一个关键步骤——解析(Parsing)。
第二段则将这些通用概念应用回例子 $G_4$。
本段是对例子 2.4 的升华。它将文法 $G_4$ 的具体设计与编译器的通用工作原理联系起来,强调了上下文无关文法和其产生的分析树在“提取代码含义”这一核心任务中的关键作用。通过“分组”这一直观概念,它解释了文法结构如何被用来精确地编码和实现如运算符优先级等重要的编程语言规则。
本段的目的是为了让读者不仅仅停留在“这个文法能生成这些字符串”的表面理解上,而是深入到“为什么文法要这样设计”的层面。它揭示了文法设计背后的“语义动机”,即通过语法结构来约束和定义程序的意义。这对于理解语言设计和编译器原理至关重要。
分析树就像是句子的“骨架”。对于 a+a×a,文法 $G_4$ 给出的“骨架”是,+ 是主心骨(脊椎),一个 a 是一条胳膊,而 a×a 整体是另一条胳膊。a×a 这条胳膊本身又有自己的骨架,× 是其骨干。你要想理解整个骨架,必须先理解每条胳膊的构造。这种骨架的层次结构就决定了你“理解”这个句子的顺序。
想象一下你在阅读一份复杂的法律合同。
📜 [原文15]
正如 1.1 节(第 41 页)讨论的有限自动机设计一样,上下文无关文法的设计也需要创造力。事实上,上下文无关文法比有限自动机更难构建,因为我们更习惯于为特定任务编写机器程序,而不是用文法描述语言。当面临构建 CFG 的问题时,以下技术(单独或组合使用)会很有帮助。
这段话是设计上下文无关文法 (CFG) 这一主题的引言。它首先定性地描述了这项任务的特点和难点,并预告了接下来将要介绍一些有用的设计技巧。
这是在描述一个“过程”。
这是在描述一个“结构”。
本段作为“设计 CFG”部分的开场白,首先承认了设计上下文无关文法是一项富有挑战性的创造性活动,并从人类思维习惯的角度解释了其难度所在(过程式思维 vs. 描述式思维)。最后,它积极地预告了接下来将要介绍一系列实用的设计技巧,以帮助读者应对这一挑战。
本段的目的是进行心理建设和方法论引导。它通过“丑话说在前面”的方式,让读者对任务难度有合理的预期,避免因挫败感而气馁。同时,通过点明思维方式的差异,引导读者有意识地从“如何做”转换到“是什么”的视角来思考问题,为学习后续的具体技巧做好思维准备。
设计有限自动机就像是导演一场戏,你要安排每个演员(状态)在特定时间(接到输入)做什么动作(状态转移)。而设计上下文无关文法就像是编剧写剧本,你要定义角色的构成(“一个英雄角色由一个悲惨的过去和一个坚定的目标组成”),而不是指挥演员的每一个动作。我们通常更习惯当导演,而不是当编剧。
想象两种描述“楼梯”的方式。
设计文法要求我们用后一种思维方式来描述语言。
📜 [原文16]
首先,许多 CFL 是更简单 CFL 的并集。如果你必须为可以分解为更简单部分的 CFL 构建 CFG,那么这样做,然后为每个部分构建单独的文法。这些单独的文法可以很容易地合并为原始语言的文法,方法是组合它们的规则,然后添加新规则 $S \rightarrow S_{1}\left|S_{2}\right| \cdots \mid S_{k}$,其中变量 $S_{i}$ 是各个文法的开始变量。解决几个更简单的问题通常比解决一个复杂的问题更容易。
例如,为了获得语言 $\left\{0^{n} 1^{n} \mid n \geq 0\right\} \cup\left\{1^{n} 0^{n} \mid n \geq 0\right\}$ 的文法,首先构建语言 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$ 的文法
以及语言 $\left\{1^{n} 0^{n} \mid n \geq 0\right\}$ 的文法
然后添加规则 $S \rightarrow S_{1} \mid S_{2}$,得到文法
这里介绍了设计上下文无关文法 (CFG) 的第一个核心技术:分解与合并,专门用于处理由并集 (Union) 构成的语言。
核心思想: “分而治之” (Divide and Conquer)。
这个技巧的有效性基于上下文无关语言对于并集运算是封闭的这一性质。也就是说,如果 $L_1$ 和 $L_2$ 都是 CFL,那么它们的并集 $L_1 \cup L_2$ 也必然是 CFL。这个构造性证明本身就提供了设计文法的方法。
例子分析:
```
S -> S1 | S2
S1 -> 0S1 1 | ε
S2 -> 1S2 0 | ε
```
这个文法的工作方式是:从 $S$ 开始,你第一步必须选择走 $S_1$ 的路径还是 $S_2$ 的路径。一旦选定,比如选了 $S_1$,你就进入了 $L_1$ 的生成模式,之后的所有推导都遵循 $G_1$ 的规则,不可能再跳到 $S_2$ 的规则里去。这样就保证了生成的字符串要么完全符合 $L_1$ 的格式,要么完全符合 $L_2$ 的格式。
```
S -> S1 | S2
S1 -> aaS1 | ε
S2 -> bbS2 | b
```
```
S -> S1 | S2
S1 -> aS1 | ε
S2 -> bS2 | ε
```
本段介绍了一种强大而系统的 CFG 设计技术:对于由并集构成的语言,采用“分而治之”的策略。通过为每个子语言单独设计文法,然后用一个新的开始变量和选择规则将它们“粘合”在一起,可以将一个复杂问题简化为几个简单问题的组合。这个方法既直观又可靠。
本段的目的是为 CFG 设计提供第一个具体的、可操作的“设计模式”。这让读者从完全依赖“创造力”的困境中解脱出来,开始掌握一些结构化的设计方法。通过展示如何处理并集,它也从侧面引入了上下文无关语言的封闭性质这一重要理论概念。
这就像是做一个“套餐选择”菜单。
今日特选 (S):
想象你在为一个大型活动规划不同的入口。
📜 [原文17]
其次,如果一种语言恰好是正则的,那么构建它的 CFG 就很容易,如果你能先为该语言构建一个 DFA。你可以将任何 DFA 转换为等效的 CFG,如下所示。为 DFA 的每个状态 $q_{i}$ 创建一个变量 $R_{i}$。如果 $\delta\left(q_{i}, a\right)=q_{j}$ 是 DFA 中的一个转移,则将规则 $R_{i} \rightarrow a R_{j}$ 添加到 CFG 中。如果 $q_{i}$ 是 DFA 的接受状态,则添加规则 $R_{i} \rightarrow \varepsilon$。将 $R_{0}$ 作为文法的开始变量,其中 $q_{0}$ 是机器的开始状态。请自行验证,由此产生的 CFG 生成的语言与 DFA 识别的语言相同。
这里介绍了设计上下文无关文法 (CFG) 的第二个技术:从确定性有限自动机 (DFA) 进行转换。这个技术专门用于为正则语言构建 CFG。
背景知识: 我们已经知道,所有正则语言都是上下文无关语言。这意味着任何一个可以用 DFA(或 NFA,或正则表达式)描述的语言,都必然存在一个 CFG 可以生成它。本段就提供了一个将 DFA 自动转换为等效 CFG 的算法。
转换算法:
正确性验证 (直观):
$R_0 \Rightarrow a_1 R_{i_1} \Rightarrow a_1 a_2 R_{i_2} \Rightarrow \dots \Rightarrow a_1 a_2 \dots a_k R_{i_k}$。
这个技巧非常强大,因为它将一个可能需要创造力的设计问题,转换为了一个完全机械化的、算法性的转换过程。只要你能为正则语言画出 DFA,就能自动得到它的 CFG。
```
R0 -> bR0 | aR1
R1 -> aR1 | bR2
R2 -> aR2 | bR2 | ε // R2是接受状态
```
本段提出了一个将任何 DFA 转换为等效 CFG 的确定性算法。这个方法为所有正则语言提供了一个标准化的 CFG 构建流程,将设计问题转化为了一个机械的转换过程。其核心思想是让文法的变量模拟 DFA 的状态,让文法的规则模拟 DFA 的转移。
本段的目的是提供一个处理正则语言这一大类 CFL 的“银弹”。它不仅再次强调了“所有正则语言都是上下文无关的”,并且给出了一个构造性的证明。这使得设计师在面对一个已知是正则的语言时,有了一个非常可靠和简单的工具,而不必去重新进行创造性思考。
[直觉心-智模型]
这个转换过程就像是为自动售货机 (DFA) 编写一份“用户可读的说明书” (CFG)。
想象一个迷宫游戏 (DFA)。
📜 [原文18]
第三,某些上下文无关语言包含带有两个子串的字符串,这两个子串是“关联”的,这意味着针对此类语言的机器需要记住有关其中一个子串的无限量信息,以验证它是否与另一个子串正确对应。这种情况发生在语言 $\left\{0^{n} 1^{n} \mid n \geq 0\right\}$ 中,因为机器需要记住 0 的数量,以验证它是否等于 1 的数量。你可以通过使用 $R \rightarrow u R v$ 形式的规则来构建 CFG 以处理这种情况,该规则生成字符串,其中包含 $u$ 的部分对应于包含 $v$ 的部分。
这里介绍了设计上下文无关文法的第三个,也是最核心的一个技巧:使用递归规则处理关联子串。这个技巧是用来处理那些超越了正则语言能力的、需要“计数”或“匹配”的语言。
这个技巧是设计非正则 CFL 文法的基石,它直接利用了 CFG 最强大的武器——递归。
本段介绍了设计 CFG 的一个关键策略:使用形如 $R \rightarrow uRv$ 的递归规则来处理语言中“关联子串”的匹配问题。这种方法通过文法结构本身实现了隐式的计数或对称性检查,是生成非正则的上下文无关语言的核心技巧。
本段的目的是揭示上下文无关文法超越有限自动机的根本原因和实现方式。它为读者提供了一把“钥匙”,以解决所有需要无限“记忆”来匹配或计数的语言的设计问题。这是从正则语言思维迈向上下文无关语言思维的最重要一步。
这种递归规则就像一个“对称的生长指令”。想象你有一棵神奇的树 ($R$)。
想象你在用双手同时串珠子。
📜 [原文19]
最后,在更复杂的语言中,字符串可能包含递归地作为其他(或相同)结构的一部分出现的某些结构。这种情况发生在例子 2.4 中生成算术表达式的文法中。每当出现符号 a 时,一个完整的括号表达式可能会递归地出现。为了实现这种效果,将生成结构的变量符号放置在规则中对应于该结构可能递归出现的位置。
这里介绍了设计上下文无关文法的第四个技巧:结构替换式递归。这个技巧用于处理那些一个语法结构可以出现在另一个(或自身)语法结构内部的语言,这是构建复杂层次结构的关键。
这个技巧是构建具有任意深度嵌套能力的文法的关键,广泛应用于编程语言、数据格式(如 JSON)等领域。
```
<value> -> <object> | <string> | <number>
<object> -> { <members> }
<members> -> <pair> | <pair>, <members>
<pair> -> <string> : <value> // <-- 关键递归点
```
```
<stmt> -> <if-stmt> | <assign-stmt> | ...
<if-stmt> -> if (<expr>) <stmt> else <stmt> // <-- 关键递归点
<assign-stmt> -> <id> = <expr>
```
本段介绍了 CFG 设计的第四个技巧:通过在规则中将代表原子结构的选项和代表完整递归结构的选项并列,来实现复杂的、任意深度的结构嵌套。这个技巧的核心是“把代表整体的变量,放到一个代表局部成分的规则里”,如 $G_4$ 中 FACTOR -> (EXPR) | a 所做的那样。这是构建如编程语言等复杂层次结构语言文法的基石。
本段的目的是介绍一种比“对称匹配”更强大的递归模式,即“结构替换”。这使得读者能够理解和设计那些具有复杂嵌套特性的语言的文法,比如编程语言中的表达式、语句块,或数据格式中的嵌套对象。这进一步拓宽了读者可以解决的问题范围。
这就像是“梦想成真”的规则。
想象一下你的电脑文件系统。
📜 [原文20]
有时,一个文法可以用几种不同的方式生成同一个字符串。这样的字符串将有几个不同的分析树,因此有几个不同的含义。这对于某些应用(例如编程语言)可能是不希望的,因为程序应该具有唯一的解释。
如果一个文法以几种不同的方式生成同一个字符串,我们说该字符串在该文法中是二义性地推导的。如果一个文法二义性地生成了某个字符串,我们说该文法是二义性的。
例如,考虑文法 $G_{5}$:
这个文法二义性地生成了字符串 $\mathrm{a}+\mathrm{a} \times \mathrm{a}$。下图显示了两个不同的分析树。

图 2.6
文法 $G_{5}$ 中字符串 $\mathrm{a}+\mathrm{a} \times \mathrm{a}$ 的两个分析树

图 2.6
文法 $G_{5}$ 中字符串 $\mathrm{a}+\mathrm{a} \times \mathrm{a}$ 的两个分析树
这一部分引入了上下文无关文法中的一个核心问题——二义性 (Ambiguity)。
首先,对二义性进行了直观的定义和解释:
接着,给出了两个形式化的术语:
然后,通过一个具体的文法 $G_5$ 来演示二义性。
因为文法 $G_5$ 允许为同一个字符串 a+a×a 生成这两棵结构完全不同(即含义完全不同)的分析树,所以 $G_5$ 是一个二义性文法。
本段引入了上下文无关文法的一个关键问题——二义性。它定义了当一个文法可以为同一个字符串生成多棵不同的分析树(从而产生多种含义)时,该文法就是二义性的。通过文法 $G_5$ 和字符串 a+a×a 的例子,直观地展示了二义性是如何产生的(缺乏优先级和结合性规定),以及它为什么在编程语言等需要确定性解释的应用中是不可接受的。
本段的目的是让读者认识到文法设计中的一个重要陷阱和挑战。它说明了设计一个能生成目标语言的 CFG 只是第一步,保证这个 CFG 是无二义性的(如果可能的话)同样重要,尤其是在编译器设计等实际应用中。这为后续讨论如何消除二义性(如 $G_4$ 的设计)和固有二义性语言等更深层次的话题埋下伏笔。
一个二义性文法就像是一份有歧义的法律条文。
想象一句有歧义的英文句子:“I saw a man on a hill with a telescope.”
一个二义性文法就如同英语语法本身,允许对同一句话产生多种不同的结构理解。
📜 [原文21]
这个文法没有捕捉到通常的优先级关系,因此它可能先分组 + 运算符,后分组 × 运算符,反之亦然。相比之下,文法 $G_{4}$ 生成完全相同的语言,但每个生成的字符串都有唯一的分析树。因此 $G_{4}$ 是无二义性的,而 $G_{5}$ 是二义性的。
文法 $G_{2}$(第 103 页)是二义性文法的另一个例子。句子 the girl touches the boy with the flower 有两种不同的推导。在练习 2.8 中,你将被要求给出两个分析树,并观察它们与阅读该句子的两种不同方式的对应关系。
这段话是对二义性例子的补充说明和总结,并引出了另一个关于自然语言文法的二义性例子。
第一段,对文法 $G_4$ 和 $G_5$ 进行了直接的对比和定性。
第二段,将二义性的概念扩展回自然语言的例子。
E ⇒ E+T ⇒ E+T+T ⇒ T+T+T ⇒ a+T+T ⇒ a+a+T ⇒ a+a+a
这唯一对应于 (a+a)+a 的分析树结构。通过非对称的规则 E -> E+T,我们消除了结合性的二义性。
<SENTENCE> ⇒ <NP> <VP>
... ⇒ the girl <VP>
⇒ the girl <CMPLX-VERB>
⇒ the girl <VERB> <NP> (动词带宾语)
⇒ the girl touches <NP>
⇒ the girl touches <CMPLX-NOUN> <PREP-PHRASE> (宾语名词短语自身带修饰)
... ⇒ the girl touches the boy with the flower
这棵树中,<PREP-PHRASE> 是 <NP> (the boy) 的子节点。
<SENTENCE> ⇒ <NP> <VP>
... ⇒ the girl <VP>
⇒ the girl <VERB-PHRASE> <PREP-PHRASE> (整个动词短语被修饰)
⇒ the girl <CMPLX-VERB> <PREP-PHRASE>
... ⇒ the girl touches the boy with the flower
这棵树中,<PREP-PHRASE> 和 <CMPLX-VERB> 是兄弟节点,都属于顶层的 <VERB-PHRASE>。
本段通过对比文法 $G_4$ 和 $G_5$,强调了一个语言可以有二义性和无二义性两种不同的文法,并指出无二义性对于编程语言等应用的重要性。同时,它以自然语言中介词短语修饰不明确的经典例子,进一步说明了二义性是一个普遍存在的问题,并将其与不同的分析树结构和语义解释直接关联起来。
本段的目的是巩固读者对二义性概念的理解,并将其从数学表达式的领域扩展到更熟悉的自然语言领域。通过对比“好文法”($G_4$)和“坏文法”($G_5$),它强化了“设计一个无二义性的文法”是文法工程中的一个核心目标。通过自然语言的例子,它让抽象的“多棵分析树”概念与我们日常生活中就能体验到的“一句话有多种理解”现象联系起来,使得概念更易于吸收。
[直觉心-智模型]
这就像是比较两份建筑蓝图(文法)。
两份蓝图描述的都是“一个有客厅、厨房、餐厅的房子”(同一个语言),但 $G_4$ 是更好的设计,因为它不会产生误解。
想象一下你在给一个机器人下达指令。
$G_2$ 这样的文法就类似于第一种有歧义的自然语言指令。
📜 [原文22]
现在我们形式化二义性的概念。当我们说一个文法二义性地生成一个字符串时,我们指的是该字符串有两个或多个不同的分析树,而不是两个不同的推导。两个推导可能仅仅因为替换变量的顺序不同而不同,但它们的整体结构是相同的。为了专注于结构,我们定义一种以固定顺序替换变量的推导类型。如果每一步都替换最左边的剩余变量,那么文法 $G$ 中字符串 $w$ 的推导就是最左推导。定义 2.2(第 104 页)之前的推导就是最左推导。
定义 2.7
如果字符串 $w$ 具有两个或多个不同的最左推导,则称其在上下文无关文法 $G$ 中二义性地推导。如果文法 $G$ 二义性地生成了某个字符串,则称该文法是二义性的。
这一部分为二义性提供了一个更严格、更便于操作的形式化定义。
首先,它澄清了一个关键点:二义性的本质在于分析树的不同,而不是推导序列的不同。
为了消除这种因推导顺序不同而产生的“假性”多样性,从而能专注于真正的结构性二义性,这里引入了一个标准化的推导方法:最左推导 (Leftmost Derivation)。
基于最左推导,定义 2.7 给出了二义性的形式化定义:
这个定义比“多棵分析树”的定义更便于在数学证明或算法中进行判断。
最后,文本回顾了一下,指出在前面(定义 2.2 之前)为 a boy sees 做的推导,实际上就是一个最左推导的例子。
本段为二义性提供了更严格的形式化定义。它首先澄清二义性的本质在于分析树的结构差异,而非推导步骤的顺序差异。为了标准化比较,引入了最左推导的概念,即每一步都替换最左边的变量。最终,将二义性定义为:一个文法是二义性的,如果它能为某个字符串提供两个或以上不同的最左推导。这个定义将抽象的分析树比较问题转化为了具体的推导序列比较问题。
本段的目的是将对二义性的直观理解转化为一个在数学上和算法上都易于处理的精确工具。在计算机科学中,能够形式化地、无歧义地定义一个概念是进行自动分析、证明和算法设计的前提。通过最左推导,为判断二义性提供了一个坚实可靠的操作性标准。
这就像在规范如何阅读一本书。
想象你在做一道多步骤的数学题。
📜 [原文23]
有时,当我们有一个二义性文法时,我们可以找到一个生成相同语言的无二义性文法。然而,有些上下文无关语言只能由二义性文法生成。这类语言被称为固有二义性语言。问题 2.41 要求你证明语言 $\left\{\mathrm{a}^{i} \mathrm{~b}^{j} \mathrm{c}^{k} \mid i=j \text{ 或 } j=k\right\}$ 是固有二义性的。
这段话讨论了二义性问题的两种可能结果,并引入了一个新的重要概念:固有二义性语言。
本段区分了两种二义性:可消除的二义性和不可消除的二义性。前者是文法设计不佳的结果,可以通过重写文法来解决;后者则源于语言本身的内在结构性冲突,这类语言被称为固有二义性语言。并给出了一个经典的固有二义性语言例子,$\left\{\mathrm{a}^{i} \mathrm{~b}^{j} \mathrm{c}^{k} \mid i=j \text{ 或 } j=k\right\}$,其二义性源于满足两种匹配条件的字符串(如 $a^n b^n c^n$)的存在。
本段的目的是为了揭示二义性问题的全部图景,告诉读者并非所有的二义性问题都有解决方案。这引入了计算理论中一个深刻的限制性结论,即存在一些上下文无关语言,它们在本质上就是“模糊”的,无法用任何一个 CFG 进行唯一解析。这对于理解 CFL 的能力边界和解析理论的局限性至关重要。
这就像是给照片分类。
想象一个人的身份。
📜 [原文24]
在使用上下文无关文法时,通常将它们简化形式会很方便。最简单且最有用的形式之一被称为乔姆斯基范式。乔姆斯基范式在给出处理上下文无关文法的算法时很有用,正如我们在第 4 章和第 7 章中所做的那样。
上下文无关文法是乔姆斯基范式,如果每条规则都采用以下形式
其中 $a$ 是任何终结符,$A, B$ 和 $C$ 是任何变量——但 $B$ 和 $C$ 不能是开始变量。此外,我们允许规则 $S \rightarrow \varepsilon$,其中 $S$ 是开始变量。
这一部分引入了一个重要的文法“标准化”形式——乔姆斯基范式 (Chomsky Normal Form, CNF)。
首先,引言部分解释了为什么需要这种范式:
接着,定义 2.8 给出了乔姆斯基范式的严格定义。一个 CFG 要成为 CNF,它的所有规则(除了一个特殊情况)必须满足以下两种形式之一:
特殊情况:
总结 CNF 的特点:
这种高度规范化的形式,使得分析树的结构也变得非常规整:每个变量节点要么有两个变量子节点,要么有一个终结符子节点。这对于动态规划类的解析算法(如 CYK)来说是完美的基础。
```
S -> AB
A -> CD | a
B -> b
C -> c
D -> d
```
本段引入了乔姆斯基范式 (CNF),一种对上下文无关文法进行标准化的重要形式。它定义了 CNF 文法的规则必须满足的两种严格形式($A \rightarrow BC$ 或 $A \rightarrow a$),以及处理空串的特殊规则 ($S \rightarrow \varepsilon$)。并阐明了使用 CNF 的主要动机:它极大地简化了处理 CFG 的算法的设计和分析。
本段的目的是介绍一种“规范化”工具。在数学和计算机科学中,将研究对象转换到一个统一的、简单的“标准型”或“范式”是一种常用且强大的技术。这可以去除无关的复杂性,使我们能集中于核心问题。CNF 就是 CFG 的一个标准型,它的引入是为了后续的算法和理论推导服务的。
乔姆斯基范式就像是把所有食谱都统一成一种极简风格。
想象一下你在用最基础的 1x1 和 1x2 的乐高积木来复制一个复杂的模型。
📜 [原文25]
任何上下文无关语言都由乔姆斯基范式中的上下文无关文法生成。
证明思路 我们可以将任何文法 $G$ 转换为乔姆斯基范式。这种转换有几个阶段,其中违反条件的规则被替换为等效的令人满意的规则。首先,我们添加一个新的开始变量。然后,我们消除所有 $A \rightarrow \varepsilon$ 形式的$\varepsilon$ 规则。我们还消除所有 $A \rightarrow B$ 形式的单元规则。在这两种情况下,我们都会修补文法,以确保它仍然生成相同的语言。最后,我们将剩余的规则转换为正确的形式。
这一部分提出了一个核心定理并概述了其证明思路。
定理 2.9:
证明思路 (Proof Idea):
定理的证明是构造性的。它不只是说“存在”一个 CNF 文法,而是给出了一个具体的算法,可以将任何给定的 CFG 转换为等效的 CNF。这个算法分为几个清晰的阶段,每个阶段旨在消除一种不符合 CNF 要求的规则。
通过这四个阶段的处理,任何一个 CFG 都可以被系统地、算法化地转换为一个生成相同语言的 CNF 文法。
```
S0 -> S
S -> SS | ε
```
(后续步骤会进一步处理 $S \rightarrow \varepsilon$)
本段提出了一个根本性的定理(任何 CFL 都有一个 CNF 文法),并给出了其构造性证明的详细步骤概览。这个证明本身就是一个强大的算法,可以将任意 CFG 转换为等价的乔姆斯基范式。转换过程分为四个主要阶段:添加新开始变量、消除 $\varepsilon$-规则、消除单元规则,以及将剩余规则分解为 CNF 形式。
本段的目的是证明乔姆斯基范式的普适性和强大性。通过提供一个具体的转换算法,它不仅在理论上确立了 CNF 文法的表达能力没有损失,也在实践上为后续所有依赖 CNF 的算法提供了一个必要的前置工具。它告诉我们,我们可以“随心所欲”地设计一个方便的文法,然后总有办法把它“打磨”成标准、规整的 CNF 形式。
这个转换过程就像是“代码重构”。
这就像是把一篇长篇散文(任意 CFG)改编成一部格律严格的五言绝句诗集 (CNF)。
📜 [原文26]
证明 首先,我们添加一个新的开始变量 $S_{0}$ 和规则 $S_{0} \rightarrow S$,其中 $S$ 是原始的开始变量。此更改保证开始变量不会出现在规则的右侧。
其次,我们处理所有$\varepsilon$ 规则。我们删除$\varepsilon$ 规则 $A \rightarrow \varepsilon$,其中 $A$ 不是开始变量。然后对于规则右侧出现的每个 $A$,我们添加一条删除该出现的新规则。换句话说,如果 $R \rightarrow u A v$ 是一条规则,其中 $u$ 和 $v$ 是变量和终结符的字符串,我们添加规则 $R \rightarrow u v$。我们对每个 $A$ 的出现都这样做,因此规则 $R \rightarrow u A v A w$ 会导致我们添加 $R \rightarrow u v A w, R \rightarrow u A v w$ 和 $R \rightarrow u v w$。如果存在规则 $R \rightarrow A$,我们添加 $R \rightarrow \varepsilon$,除非我们之前已经删除了规则 $R \rightarrow \varepsilon$。我们重复这些步骤,直到消除所有不涉及开始变量的$\varepsilon$ 规则。
第三,我们处理所有单元规则。我们删除单元规则 $A \rightarrow B$。然后,每当出现规则 $B \rightarrow u$ 时,我们添加规则 $A \rightarrow u$,除非这是之前删除的单元规则。与之前一样,$u$ 是变量和终结符的字符串。我们重复这些步骤,直到消除所有单元规则。
最后,我们将所有剩余的规则转换为正确的形式。我们将每条规则 $A \rightarrow u_{1} u_{2} \cdots u_{k}$(其中 $k \geq 3$ 且每个 $u_{i}$ 是变量或终结符符号)替换为规则 $A \rightarrow u_{1} A_{1}, A_{1} \rightarrow u_{2} A_{2}, A_{2} \rightarrow u_{3} A_{3} \ldots$ 和 $A_{k-2} \rightarrow u_{k-1} u_{k}$。$A_{i}$ 是新变量。我们将上述规则中的任何终结符 $u_{i}$ 替换为新变量 $U_{i}$,并添加规则 $U_{i} \rightarrow u_{i}$。
这一部分是定理 2.9 的正式证明,它详细描述了将任意 CFG 转换为 CNF 的四步算法。
第一步:处理开始变量 (START)
第二步:消除 $\varepsilon$-规则 (TERM)
第三步:消除单元规则 (UNIT)
第四步:清理剩余规则 (CLEANUP)
这个四步过程保证了任何 CFG 都可以被机械地转换为等效的 CNF 文法。
已有的任何变量(包括之前步骤中新引入的)相冲突。
本段详细描述了将任何一个上下文无关文法 (CFG) 转换为等效的乔姆斯基范式 (CNF) 的四步构造性证明算法。
这个算法是证明定理 2.9 的核心,展示了 CNF 的普适性。
本段的目的是提供定理 2.9 的一个严谨、详细的构造性证明。它不仅仅是理论阐述,更是一个可以被计算机程序实现的具体算法。这为计算机科学家和编译器设计者提供了一个强大的工具,使他们可以将任何复杂的 CFG “预处理”成一种简单、标准的形式,从而极大地简化后续解析算法(如 CYK 算法)的设计与实现。
这套算法就像一个“文法精炼厂”。
想象一下你在整理一堆乱七八糟的电线。
📜 [原文27]
令 $G_{6}$ 为以下 CFG,并使用刚刚给出的转换过程将其转换为乔姆斯基范式。所呈现的文法系列说明了转换中的步骤。粗体显示的规则是刚添加的。灰色显示的规则是刚删除的。
3a. 删除单元规则 $S \rightarrow S$(左侧显示)和 $S_{0} \rightarrow S$(右侧显示)。
3b. 删除单元规则 $A \rightarrow B$ 和 $A \rightarrow S$。
这个例子通过一个完整的、循序渐进的过程,演示了如何将一个给定的文法 $G_6$ 转换为乔姆斯基范式 (CNF)。每一步都严格遵循了前面证明中描述的算法。
初始文法 $G_6$:
```
S -> ASA | aB
A -> B | S
B -> b | ε
```
第 1 步: 添加新开始变量 (START)
```
S0 -> S
S -> ASA | aB
A -> B | S
B -> b | ε
```
第 2 步: 消除 $\varepsilon$-规则 (TERM)
这是一个迭代的过程。
第 3 步: 消除单元规则 (UNIT)
这也是一个迭代过程。
$S_0 \rightarrow ASA | aB | a | SA | AS$
```
S0 -> ASA | aB | a | SA | AS
S -> ASA | aB | a | SA | AS
A -> B | S
B -> b
```
第 4 步: 清理剩余规则 (CLEANUP)
本例是一个将 CFG 转换为 CNF 的完整、手把手的教程。它严格遵循了定理 2.9 证明中提出的四步算法(START, TERM, UNIT, CLEANUP),并通过粗体和灰色标记清晰地展示了每一步中规则的增加和删除。通过这个具体的例子,读者可以直观地看到一个复杂的文法是如何被一步步地分解、替换、重组,最终“精炼”成一个结构高度统一的乔姆斯基范式文法的。
本段的目的是为了具象化抽象的转换算法。理论证明给出了“怎么做”,而这个例子展示了“这样做出来的结果是什么样”。通过一个足够复杂(但又可以手动追踪)的文法 $G_6$,它让读者能够亲手或在头脑中演练整个转换过程,从而深刻理解每个步骤的具体操作和目的,巩固对 CNF 转换过程的掌握。
这就像是在解一个复杂的代数方程。
这就像是为一篇长文章(原始文法)生成一份“核心要点摘要” (CNF),但要求摘要遵循非常严格的格式。
[[END_OF_RESPONSE]]所有解释内容已全部输出完毕。