try ai
科普
编辑
分享
反馈
  • 上下文无关文法

上下文无关文法

SciencePedia玻尔百科
核心要点
  • 上下文无关文法 (CFG) 使用一套递归规则来定义语言,这些规则从简单的终结符构建出复杂的嵌套结构。
  • 它们是计算机科学中解析编程语言的基础,并应用于计算生物学中为 RNA 折叠模式建模。
  • 对 CFG 的研究揭示了深刻的计算局限性,因为一些关键问题是不可判定的,例如判断两个文法是否生成相同的语言。

引言

有限的一套简单规则如何能产生我们在语言、计算机代码乃至自然界中所见的看似无限的复杂性?这个根本性问题位于计算机科学和语言学的核心。编程语言的精确语法、句子中的嵌套从句、生物分子的复杂折叠,都具有一个共同特征:一个由更小组件构建的层级结构。描述、解析和推理这些结构需要一个既强大又易于理解的形式化框架。上下文无关文法 (CFG) 正好提供了这样一个框架。

本文将探索上下文无关文法的世界,从其基本原理到其深远影响。在第一章“原理与机制”中,我们将解构 CFG 的组成部分,探索递归的魔力如何让有限的规则生成无限的语言。我们还将深入研究这些文法的理论性质,发现哪些关于它们的问题我们可以通过算法回答,以及我们在何处触及计算的深刻极限。随后,“应用与跨学科联系”一章将理论与实践联系起来,揭示 CFG 如何成为现代编译器的支柱、确保数字安全的工具,以及在计算生物学中理解生命密码的一种出人意料的有效模型。

{'WHEEL_ASSEMBLY': {'CHASSIS': {'CAR': {'CHASSIS': {'WHEEL_ASSEMBLY': {'GREETING': {'PHRASE': {'MESSAGE': {'GREETING': {'GREETING': {'MESSAGE': {'GREETING': {'CONVO': {'SIGN_OFF': {'CAR': {'GREETING': {'GREETING': {'MESSAGE': {'MESSAGE': {'MESSAGE': '} 开始,并反复应用产生式规则——用规则的右侧替换变量——我们可以推导出所有可能有效的消息,例如 "hi how r u ttyl"。\n\n### 递归的魔力:用有限规则构建无限世界\n\n这个工具箱看起来很简单,也许过于简单了。区区几条规则如何能描述一种语言潜在的无限复杂性,比如 Python 中所有有效的程序或所有有效的数学表达式?秘密武器就是​**​递归​**​:一条规则引用其自身。\n\n考虑回文语言——即正读反读都一样的字符串。一个用于生成必须以 \'1\' 开头和结尾的二进制回文的简单文法,可以用一个优美的递归思想来构建。我们需要一个主规则来定义整体结构,和一个辅助规则来定义回文的“内部”:\nS \to 1 \mid 1P1\n\n\nP \to 0P0 \mid 1P1 \mid \epsilon\n\n规则\n\n规则 \n\n规则S \to 1P1表示一个有效字符串可以是‘1‘,后跟∗任何∗内部回文表示一个有效字符串可以是 `1`,后跟*任何*内部回文表示一个有效字符串可以是‘1‘,后跟∗任何∗内部回文P,再后跟另一个‘1‘。规则,再后跟另一个 `1`。规则 ,再后跟另一个‘1‘。规则P \to 0P0对内部部分也做同样的处理,确保了完美的对称性。这就像建造一个俄罗斯套娃。每一层都在外部添加一对匹配的符号,从而保证了回文的性质。这种“记住”开头符号并在结尾与之匹配的能力,使得CFG比正则表达式等更简单的形式化方法更强大,后者以其有限的记忆能力而闻名。\n\n同样的递归魔力使我们能够处理计算机科学中无处不在的嵌套结构。像‘()‘、‘[()]‘和‘()[]‘这样的平衡括号和方括号的规则,可以被一个CFG优雅地捕获:\n 对内部部分也做同样的处理,确保了完美的对称性。这就像建造一个俄罗斯套娃。每一层都在外部添加一对匹配的符号,从而保证了回文的性质。这种“记住”开头符号并在结尾与之匹配的能力,使得 CFG 比正则表达式等更简单的形式化方法更强大,后者以其有限的记忆能力而闻名。\n\n同样的递归魔力使我们能够处理计算机科学中无处不在的嵌套结构。像 `()`、`[()]` 和 `()[]` 这样的平衡括号和方括号的规则,可以被一个 CFG 优雅地捕获:\n对内部部分也做同样的处理,确保了完美的对称性。这就像建造一个俄罗斯套娃。每一层都在外部添加一对匹配的符号,从而保证了回文的性质。这种“记住”开头符号并在结尾与之匹配的能力,使得CFG比正则表达式等更简单的形式化方法更强大,后者以其有限的记忆能力而闻名。\n\n同样的递归魔力使我们能够处理计算机科学中无处不在的嵌套结构。像‘()‘、‘[()]‘和‘()[]‘这样的平衡括号和方括号的规则,可以被一个CFG优雅地捕获:\nS \to (S) \mid [S] \mid SS \mid \epsilon\n\n在这里我们看到了两种递归在起作用。规则\n\n在这里我们看到了两种递归在起作用。规则 \n\n在这里我们看到了两种递归在起作用。规则S \to (S)和和和S \to [S]处理​∗∗​嵌套​∗∗​,就像俄罗斯套娃。规则处理​**​嵌套​**​,就像俄罗斯套娃。规则处理​∗∗​嵌套​∗∗​,就像俄罗斯套娃。规则S \to SS处理​**​串接​**​或序列,允许我们将两个有效的结构并排放置,比如 `()` 后面跟着 `[]`。这个单一、紧凑的文法定义了无数数据格式和编程语言构造的语法。\n\n### 文法代数:组合与转换语言\n\n一旦我们将文法视为形式化对象,我们就可以开始以真正数学化的方式来玩转它们。我们可以对它们进行“代数”运算,从现有的文法和语言创建新的文法和语言。这揭示了一种深刻而优美的结构。\n\n假设我们有一个文法G_1生成一种语言生成一种语言生成一种语言L_1(比如,偶数长度的回文‘abba‘),另一个文法(比如,偶数长度的回文 `abba`),另一个文法 (比如,偶数长度的回文‘abba‘),另一个文法G_2生成一种语言生成一种语言生成一种语言L_2(比如,形如(比如,形如 (比如,形如c^m d^{2m}的字符串)。如果我们想创建一种语言,其中来自的字符串)。如果我们想创建一种语言,其中来自的字符串)。如果我们想创建一种语言,其中来自L_1的字符串总是后跟一个来自的字符串总是后跟一个来自的字符串总是后跟一个来自L_2的字符串,该怎么办?这就是语言的​∗∗​串接​∗∗​, 的字符串,该怎么办?这就是语言的​**​串接​**​,的字符串,该怎么办?这就是语言的​∗∗​串接​∗∗​,L_1 L_2。构造方法非常简单:我们只需创建一个新的起始符号。构造方法非常简单:我们只需创建一个新的起始符号 。构造方法非常简单:我们只需创建一个新的起始符号S和一条规则和一条规则和一条规则S \to S_1 S_2,其中,其中 ,其中S_1和和和S_2是原始文法的起始符号。这就像把两条流水线连接在一起。\n\n一个更引人注目的转换是​∗∗​反转​∗∗​。给定一种语言是原始文法的起始符号。这就像把两条流水线连接在一起。\n\n一个更引人注目的转换是​**​反转​**​。给定一种语言是原始文法的起始符号。这就像把两条流水线连接在一起。\n\n一个更引人注目的转换是​∗∗​反转​∗∗​。给定一种语言L的文法,我们能为的文法,我们能为的文法,我们能为L^R(所有反转字符串的语言)创建一个文法吗?答案是肯定的,而且方法惊人地简单:对于原始文法中的每条产生式规则(所有反转字符串的语言)创建一个文法吗?答案是肯定的,而且方法惊人地简单:对于原始文法中的每条产生式规则 (所有反转字符串的语言)创建一个文法吗?答案是肯定的,而且方法惊人地简单:对于原始文法中的每条产生式规则A \to \alpha,我们只需通过反转右侧符号序列来创建一条新规则,我们只需通过反转右侧符号序列来创建一条新规则 ,我们只需通过反转右侧符号序列来创建一条新规则A \to \alpha^R。例如,规则。例如,规则 。例如,规则S \to A1B变成变成变成S \to B1A。这种对规则的简单、机械的翻转,完美地反映了文法所能产生的每个字符串的反转。这展示了生成规则与生成结构之间美妙的对偶性。\n\n### 知识的局限:我们能问和不能问文法什么\n\n文法不仅仅是生成工具;它们也是研究的对象。我们可以提出*关于*它们的问题。这个文法能为我们的机器人产生任何有效的命令序列吗?它能产生一个恰好五步长的命令吗?这个为我们编译器设计的新的、优化过的文法,其行为是否与旧文法完全相同?\n\n事实证明,其中一些问题有清晰的、算法性的答案,而另一些则将我们带入不可判定性的深渊——即不存在通用算法可以解决的问题。\n\n#### 可判定的问题(好消息)\n\n值得庆幸的是,一些最实际的问题是可判定的。例如,​**​空性问题​**​:由文法 G生成的语言是否为空?它到底能否生成∗任何∗字符串?我们可以通过找到所有“能产生产生式”的变量来解决这个问题。如果一个变量可以通过一系列规则应用,最终导出一个只由终结符组成的字符串,那么它就是能产生产生式的。我们可以迭代地找到这些变量:首先,找到所有有直接指向终结符的规则的变量(例如, 生成的语言是否为空?它到底能否生成*任何*字符串?我们可以通过找到所有“能产生产生式”的变量来解决这个问题。如果一个变量可以通过一系列规则应用,最终导出一个只由终结符组成的字符串,那么它就是能产生产生式的。我们可以迭代地找到这些变量:首先,找到所有有直接指向终结符的规则的变量(例如,生成的语言是否为空?它到底能否生成∗任何∗字符串?我们可以通过找到所有“能产生产生式”的变量来解决这个问题。如果一个变量可以通过一系列规则应用,最终导出一个只由终结符组成的字符串,那么它就是能产生产生式的。我们可以迭代地找到这些变量:首先,找到所有有直接指向终结符的规则的变量(例如,A \to \text{'grab'})。然后,找到所有有规则指向终结符和已知的能产生产生式变量的混合体的变量。我们重复这个过程,直到找不到新的能产生产生式的变量为止。如果我们的起始符号)。然后,找到所有有规则指向终结符和已知的能产生产生式变量的混合体的变量。我们重复这个过程,直到找不到新的能产生产生式的变量为止。如果我们的起始符号 )。然后,找到所有有规则指向终结符和已知的能产生产生式变量的混合体的变量。我们重复这个过程,直到找不到新的能产生产生式的变量为止。如果我们的起始符号S进入了这个集合,那么语言就是非空的。否则,它就是一个空洞的承诺,陷入了永远无法触及坚实地面的非能产生产生式规则的循环中。\n\n我们甚至可以回答更具体的问题。 进入了这个集合,那么语言就是非空的。否则,它就是一个空洞的承诺,陷入了永远无法触及坚实地面的非能产生产生式规则的循环中。\n\n我们甚至可以回答更具体的问题。进入了这个集合,那么语言就是非空的。否则,它就是一个空洞的承诺,陷入了永远无法触及坚实地面的非能产生产生式规则的循环中。\n\n我们甚至可以回答更具体的问题。L(G)是否包含任何长度恰好为5的字符串?有两种优美的方法来判定这个问题。直接的方法是列出所有可能的长度为5的字符串(这是一个有限的数目!),然后使用标准算法测试每一个字符串是否在该语言中。一种更深刻的方法涉及一个巧妙的交集运算。所有长度为5的字符串的集合是一个简单的正则语言。一个基本性质是,上下文无关语言和正则语言的交集总是上下文无关的。我们可以为这个交集构造一个新的文法是否包含任何长度恰好为 5 的字符串?有两种优美的方法来判定这个问题。直接的方法是列出所有可能的长度为 5 的字符串(这是一个有限的数目!),然后使用标准算法测试每一个字符串是否在该语言中。一种更深刻的方法涉及一个巧妙的交集运算。所有长度为 5 的字符串的集合是一个简单的正则语言。一个基本性质是,上下文无关语言和正则语言的交集总是上下文无关的。我们可以为这个交集构造一个新的文法是否包含任何长度恰好为5的字符串?有两种优美的方法来判定这个问题。直接的方法是列出所有可能的长度为5的字符串(这是一个有限的数目!),然后使用标准算法测试每一个字符串是否在该语言中。一种更深刻的方法涉及一个巧妙的交集运算。所有长度为5的字符串的集合是一个简单的正则语言。一个基本性质是,上下文无关语言和正则语言的交集总是上下文无关的。我们可以为这个交集构造一个新的文法G'。原来的问题现在转化为:“。原来的问题现在转化为:“。原来的问题现在转化为:“G'的语言是否为空?”——一个我们已经知道如何回答的问题!\n\n#### 不可判定的问题(深渊)\n\n然而,可判定性的光芒有其边界。当我们提出更一般性的问题时,我们便会碰壁。考虑​**​等价性问题​**​:给定两个文法G_1和和和G_2,它们是否生成完全相同的语言(,它们是否生成完全相同的语言 (,它们是否生成完全相同的语言(L(G_1) = L(G_2))?对于任何管理不同版本编程语言规范的人来说,这似乎是一个至关重要的问题。然而,它是​∗∗​不可判定的​∗∗​。没有算法能够接受任意两个CFG并确定地告诉你它们是否等价。可能的推导和结构是如此无限多样,以至于没有单一的程序能适用于所有情况。\n\n与此相关的是​∗∗​全域性问题​∗∗​:给定的文法)?对于任何管理不同版本编程语言规范的人来说,这似乎是一个至关重要的问题。然而,它是​**​不可判定的​**​。没有算法能够接受任意两个 CFG 并确定地告诉你它们是否等价。可能的推导和结构是如此无限多样,以至于没有单一的程序能适用于所有情况。\n\n与此相关的是​**​全域性问题​**​:给定的文法 )?对于任何管理不同版本编程语言规范的人来说,这似乎是一个至关重要的问题。然而,它是​∗∗​不可判定的​∗∗​。没有算法能够接受任意两个CFG并确定地告诉你它们是否等价。可能的推导和结构是如此无限多样,以至于没有单一的程序能适用于所有情况。\n\n与此相关的是​∗∗​全域性问题​∗∗​:给定的文法G是否能生成其字母表是否能生成其字母表是否能生成其字母表\Sigma^ 上的所有可能字符串?这也是不可判定的。如果它是可判定的,我们就可以通过构造一个“差集”语言并询问它是否是全域的来解决等价性问题。一个问题的不可判定性意味着另一个问题的不可判定性。这不是我们才智的失败;这是对通过计算所能知晓的范围的根本限制,这一发现与物理学中的任何发现同样深刻。\n\n### 解释的问题:二义性的危险\n\n我们还必须考虑最后一个微妙的属性。想象一下这个英文句子:“I saw a man on a hill with a telescope.”(我看到一个在山上的男人,他拿着望远镜/我用望远镜看到一个在山上的男人。)谁拿着望远镜?我,还是山上的男人?这个句子结构是模棱两可的。在自然语言中,这很常见。但在编程语言中,这是一场灾难。如果一个编译器发现有两种不同的有效方式来解析表达式 `3 * 4 + 5`,这究竟是意味着 `(3 * 4) + 5` 还是 `3 * (4 + 5)`?结果完全不同。\n\n如果一个文法允许单个字符串有多个不同的推导或“解析树”,那么这个文法就是​**​二义性​**​的。有时,我们可以将一个二义性文法重写成一个无二义性的文法。但有时,问题出在语言本身。如果生成一个语言的*每一个*可能的上下文无关文法都是二义性的,那么这个语言就是​**​固有二义性​**​的。\n\n考虑这样一个语言,它是两个集合的并集:L_1 = \{a^n b^n c^m d^m \mid n,m \ge 0\}和和和L_2 = \{a^n b^m c^m d^n \mid n,m \ge 0\}。这两个语言交集中的字符串,如。这两个语言交集中的字符串,如 。这两个语言交集中的字符串,如a^k b^k c^k d^k,有两种“自然”的分组方式。,有两种“自然”的分组方式。,有两种“自然”的分组方式。L_1的文法会将其视为的文法会将其视为的文法会将其视为(a^k b^k)(c^k d^k),而,而 ,而L_2的文法会将其视为的文法会将其视为的文法会将其视为a^k(b^k c^k)d^k。任何用于组合语言。任何用于组合语言 。任何用于组合语言L_1 \cup L_2的文法都必须能够生成这些交集字符串,但它继承了这种双重性格。它无法只选择一种解释而不失去正确生成语言其他部分的能力。这种语言本身核心的冲突,迫使任何试图描述它的文法都具有二义性。\n\n从简单的规则中涌现出无限、复杂的世界。我们可以用它们来构建、组合和推理,但它们也持有从根本上超出我们算法掌握能力的秘密。这种创造力与深刻局限性的融合,使得对形式文法的研究成为一场持续的发现之旅。', 'applications': "## 应用与跨学科联系\n\n既然我们已经熟悉了上下文无关文法 (CFG) 的机制——它们的规则、推导、解析树——我们很自然会问:“它们有什么用?” 这是一个合理的问题。这些文法仅仅是一种巧妙的形式游戏,是数学家和逻辑学家的好奇心产物吗?你可能会欣喜地发现,答案是响亮的“不”。生成规则这个简单的想法,原来是一把钥匙,为从我们数字世界的蓝图到生命密码本身等各种各样得惊人的领域,解锁了深刻的见解。我们将开始一段从实践到深邃的旅程,去看看这些抽象的字符串配方如何为我们提供一种新的语言来描述——并掌控——我们的世界。\n\n### 计算机的语言\n\n上下文无关文法最直接、最具体的应用,也许是你每天都在与之互动的东西:编程语言的结构。当程序员写下一行代码,如 `(x + y) * z`,计算机是如何理解它的?它看到的不是数字和变量;它看到的是一串扁平的字符。机器必须弄清楚 `x + y` 是一个独立的单元,被括号包围,并且其结果随后与 `z` 相乘。这个任务,称为解析,是任何编译器或解释器的核心。\n\n事实证明,有效算术表达式的规则可以完美地由一个 CFG 描述。一个简单的文法可以规定,一个表达式E可以是一个标识符‘id‘,或者是两个由‘+‘或‘∗‘连接的表达式,或者是一个被括号‘(E)‘包裹的表达式。这些递归规则, 可以是一个标识符 `id`,或者是两个由 `+` 或 `*` 连接的表达式,或者是一个被括号 `(E)` 包裹的表达式。这些递归规则,可以是一个标识符‘id‘,或者是两个由‘+‘或‘∗‘连接的表达式,或者是一个被括号‘(E)‘包裹的表达式。这些递归规则,E \to E+E \mid EE \mid (E) \mid \text{id},优美地捕捉了语言的嵌套结构,使得计算机能够构建一个解析树,该树反映了我们自己对表达式层次结构的直观理解。这个原理不仅限于算术;它是几乎所有现代编程语言语法赖以建立的基础。文法是宪法,而解析器是判断程序在语法上是否合法的法官。\n\n但CFG在数字领域的力量不仅限于理解有效的程序;它还有助于确保程序的安全。想象你正在设计一个安全的网络协议。所有有效消息的集合可以用一个CFG来描述,我们称之为,优美地捕捉了语言的嵌套结构,使得计算机能够构建一个解析树,该树反映了我们自己对表达式层次结构的直观理解。这个原理不仅限于算术;它是几乎所有现代编程语言语法赖以建立的基础。文法是宪法,而解析器是判断程序在语法上是否合法的法官。\n\n但 CFG 在数字领域的力量不仅限于理解有效的程序;它还有助于确保程序的安全。想象你正在设计一个安全的网络协议。所有有效消息的集合可以用一个 CFG 来描述,我们称之为 ,优美地捕捉了语言的嵌套结构,使得计算机能够构建一个解析树,该树反映了我们自己对表达式层次结构的直观理解。这个原理不仅限于算术;它是几乎所有现代编程语言语法赖以建立的基础。文法是宪法,而解析器是判断程序在语法上是否合法的法官。\n\n但CFG在数字领域的力量不仅限于理解有效的程序;它还有助于确保程序的安全。想象你正在设计一个安全的网络协议。所有有效消息的集合可以用一个CFG来描述,我们称之为G_{proto}。现在,假设存在某些恶意模式——也许是可能触发漏洞的命令序列——这些模式可以用一个更简单的正则语言。现在,假设存在某些恶意模式——也许是可能触发漏洞的命令序列——这些模式可以用一个更简单的正则语言 。现在,假设存在某些恶意模式——也许是可能触发漏洞的命令序列——这些模式可以用一个更简单的正则语言R_{ban}来描述。我们如何能确定没有任何有效消息同时也是恶意的?也就是说,我们如何检查这两种语言的交集来描述。我们如何能确定没有任何有效消息同时也是恶意的?也就是说,我们如何检查这两种语言的交集来描述。我们如何能确定没有任何有效消息同时也是恶意的?也就是说,我们如何检查这两种语言的交集L(G_{proto}) \cap R_{ban}是否为空?\n\n在这里,一个卓越的理论结果为我们提供了帮助。虽然两个上下文无关语言的交集不总是上下文无关的(我们稍后会回到这个关键点!),但一个上下文无关语言和一个正则语言的交集∗总是∗上下文无关的。这意味着我们可以系统地构造一个新的CFG, 是否为空?\n\n在这里,一个卓越的理论结果为我们提供了帮助。虽然两个上下文无关语言的交集不总是上下文无关的(我们稍后会回到这个关键点!),但一个上下文无关语言和一个正则语言的交集*总是*上下文无关的。这意味着我们可以系统地构造一个新的 CFG,是否为空?\n\n在这里,一个卓越的理论结果为我们提供了帮助。虽然两个上下文无关语言的交集不总是上下文无关的(我们稍后会回到这个关键点!),但一个上下文无关语言和一个正则语言的交集∗总是∗上下文无关的。这意味着我们可以系统地构造一个新的CFG,G_{int},它恰好生成那些既是有效消息又是被禁止模式的字符串集合。一旦我们有了这个文法,我们就可以使用一个已知的算法来判定它是否能生成任何字符串。如果,它恰好生成那些既是有效消息又是被禁止模式的字符串集合。一旦我们有了这个文法,我们就可以使用一个已知的算法来判定它是否能生成任何字符串。如果 ,它恰好生成那些既是有效消息又是被禁止模式的字符串集合。一旦我们有了这个文法,我们就可以使用一个已知的算法来判定它是否能生成任何字符串。如果L(G_{int})为空,那么我们的协议对于那种特定威胁就是安全的。这是一个抽象理论为复杂系统提供具体安全保障的优美范例。\n\n### 生命的文法\n\nCFG 在描述嵌套结构方面的优雅是如此基本,以至于令人惊讶的是,大自然本身似乎也发现了它。让我们从硅转向碳,从编译器转向生命的机器。我们细胞中最重要的分子之一是核糖核酸,即 RNA。一个 RNA 分子是一条单链核苷酸,但它并非像一根意大利面条那样漂浮。它会自我折叠,某些核苷酸会形成配对(A 与 U,G 与 C,以及一种特殊的“摆动”配对 G 与 U)。这些配对创造出一个复杂的三维形状,即其二级结构,这对它的功能至关重要。\n\n如果我们观察一个典型的 RNA 分子(没有复杂“假结”的分子)中的这些配对图,我们会看到一个熟悉的模式:嵌套结构。一对碱基可能包围一个环,该环本身又包含另一个更小的配对区域,该区域又包围另一个环,依此类推。这正是 CFG 擅长描述的那种结构!我们可以定义文法规则,其中代表 RNA 链片段的非终结符可以变成一个未配对的碱基后跟片段的其余部分,或者它可以变成一对匹配的碱基(如 'A' 和 'U')包围着一个代表内部片段的新非终结符。由这样的文法生成的语言是所有能够正确折叠成特定目标形状的核苷酸序列的集合。形式文法已成为计算生物学中预测和分析这些基本分子结构的不可或缺的工具。\n\n然而,就像任何强大的工具一样,理解其局限性同样重要。如果生物结构不是嵌套的,而是线性的、重复的,比如螺旋形病毒的组装过程呢?像烟草花叶病毒这样的病毒通过将相同的蛋白质亚基一个接一个地添加到 RNA 链上来构建其外壳。我们当然可以用一个简单的文法,如S \to S\,p \mid p来模拟亚基的顺序添加,其中来模拟亚基的顺序添加,其中来模拟亚基的顺序添加,其中p 代表一个蛋白质亚基。这个文法可以生成任意长度的螺旋。但它无法强制执行一个关键的全局约束:即组装必须在到达 RNA 模板末端时停止。最终病毒颗粒的长度取决于一个外部信息——RNA的长度——这是一种上下文信息,而上下文无关文法根本无法“看到”它。这教会了我们科学建模中一个至关重要的教训:一个模型的解释力,既取决于它能做什么,也取决于它不能做什么。\n\n### 计算的版图:什么是可能的,什么是不可能的\n\n这种对 CFG 能做什么和不能做什么的探索,将我们引向了计算机科学中一些最深刻的问题:计算的根本极限是什么?上下文无关文法为我们观察这片版图提供了一个迷人的窗口。\n\n首先,它们揭示了不同描述模式之间美妙的统一性。我们一直将文法视为一种用于产生语言的*生成性*工具。但还有一个互补的视角:一种识别工具,一台消耗一个字符串并判断它是否属于一种语言的抽象机器。对于正则语言,这台机器是有限自动机。对于上下文无关语言,它是​**​下推自动机 (PDA)​**​——本质上是一个配备了栈内存的有限自动机。一个深刻的定理指出,对于任何 CFG,都存在一个等价的 PDA,反之亦然。它们不是不同的东西,而是对同一底层结构的两种视角。这种等价性非常有用。例如,如果我们想知道一个 PDA 是否接受任何字符串,我们可以将其转换为等价的 CFG,然后运行一个直接的算法来查看该文法是否能生成任何东西。这种在不同形式化方法之间切换的能力是理论计算机科学的基石。\n\n但这个充满可解问题的整洁世界是有边界的,而 CFG 帮助我们找到了它。我们看到,我们可以判定一个 CFG 的语言与一个*正则*语言的交集是否非空。如果我们尝试将其与另一个*上下文无关*语言相交会发生什么?这个问题似乎只复杂了一点点,但答案却有天壤之别。它是​**​不可判定的​**​。不存在任何算法可以接受任意两个 CFG,G_1和和和G_2,并确定它们的语言是否共享哪怕一个字符串。这个惊人的结果通常通过证明如果你∗能∗解决这个问题,你也能解决臭名昭著的波斯特对应问题(PCP)——一个已知的不可判定的铺砖谜题——来证明。证明过程巧妙地从一个PCP实例构造出两个文法,其中一个共享的字符串直接对应于该谜题的一个解。单个共享字符串的存在成为PCP解的见证。由于不存在可以在所有情况下找到PCP解的算法,因此也不存在可以在所有情况下找到那个共享字符串的算法。\n\n这不是一个孤立的奇特现象。一大类看似合理的关于CFG的问题,实际上都是不可判定的。我们能写一个算法来确定一个给定的CFG是否能生成其字母表上的所有字符串吗(全域性问题)?不能。我们能判断一个看似复杂的CFG是否只是一个伪装的正则语言吗?不能。我们甚至能判断两个不同的文法,也许由两个不同的程序员编写,是否实际上生成完全相同的语言吗?同样,不能。这个问题,,并确定它们的语言是否共享哪怕一个字符串。这个惊人的结果通常通过证明如果你*能*解决这个问题,你也能解决臭名昭著的波斯特对应问题 (PCP)——一个已知的不可判定的铺砖谜题——来证明。证明过程巧妙地从一个 PCP 实例构造出两个文法,其中一个共享的字符串直接对应于该谜题的一个解。单个共享字符串的存在成为 PCP 解的见证。由于不存在可以在所有情况下找到 PCP 解的算法,因此也不存在可以在所有情况下找到那个共享字符串的算法。\n\n这不是一个孤立的奇特现象。一大类看似合理的关于 CFG 的问题,实际上都是不可判定的。我们能写一个算法来确定一个给定的 CFG 是否能生成其字母表上的所有字符串吗(全域性问题)?不能。我们能判断一个看似复杂的 CFG 是否只是一个伪装的正则语言吗?不能。我们甚至能判断两个不同的文法,也许由两个不同的程序员编写,是否实际上生成完全相同的语言吗?同样,不能。这个问题,,并确定它们的语言是否共享哪怕一个字符串。这个惊人的结果通常通过证明如果你∗能∗解决这个问题,你也能解决臭名昭著的波斯特对应问题(PCP)——一个已知的不可判定的铺砖谜题——来证明。证明过程巧妙地从一个PCP实例构造出两个文法,其中一个共享的字符串直接对应于该谜题的一个解。单个共享字符串的存在成为PCP解的见证。由于不存在可以在所有情况下找到PCP解的算法,因此也不存在可以在所有情况下找到那个共享字符串的算法。\n\n这不是一个孤立的奇特现象。一大类看似合理的关于CFG的问题,实际上都是不可判定的。我们能写一个算法来确定一个给定的CFG是否能生成其字母表上的所有字符串吗(全域性问题)?不能。我们能判断一个看似复杂的CFG是否只是一个伪装的正则语言吗?不能。我们甚至能判断两个不同的文法,也许由两个不同的程序员编写,是否实际上生成完全相同的语言吗?同样,不能。这个问题,EQ_{CFG},是形式语言理论中最著名的不可判定问题之一。CFG 的世界充满了这些计算黑洞——那些永远无法写出通用的、能终止的算法来解决的问题。\n\n### 在谱系中的位置\n\n那么,这给我们留下了什么?我们已经看到 CFG 足够强大,可以描述编程语言和生物结构,但它们也有限制,无论是在它们能捕捉的模式上,还是在我们能用算法回答的关于它们的问题上。谜题的最后一块是看它们在宏伟的体系中处于什么位置。通过一种称为对角化的优美证明技巧,我们可以构造一个语言 L_{diag},它被证明不是上下文无关的。该语言由所有二进制字符串,它被证明不是上下文无关的。该语言由所有二进制字符串 ,它被证明不是上下文无关的。该语言由所有二进制字符串w组成,这些字符串∗不∗在组成,这些字符串*不*在组成,这些字符串∗不∗在w本身编码的文法的语言中。根据其定义,没有 CFG 能生成这个语言,因为它会在编码该文法本身的字符串处导致逻辑矛盾。然而,这个语言*可以*被一种更强大的机器(线性有界自动机)识别,从而将其置于上下文有关语言的类别中。\n\n这证明了上下文无关语言的世界是一个更大、更复杂的语言宇宙的严格子集。我们从简单的规则开始,通过遵循它们的逻辑结论,我们不仅构建了编译器和模拟了分子,而且还描绘了通过计算所能知晓的范围的边界。穿越上下文无关文法世界的旅程,有力地证明了对简单、形式化系统的研究如何能引向对结构、复杂性以及逻辑本身根本极限的更深刻理解。", '#text': '`。\n\n通过从S = \text{'}, '#text': "的开头还是其他地方,你总能用'hi'来替换它。这是一个强大的简化假设。\n\n4. ​**​起始符号 ($S$)​**​:这是主蓝图,是我们开始生成语言中任何有效字符串时所用的变量。对于我们的短消息,起始符号是"}, '#text': '出现在'}, '#text': ')的规则始终是相同的,无论其[周围](/sciencepedia/feynman/keyword/entourages)是什么。无论 '}, '#text': '蓝图,定义了高层结构。术语​**​上下文无关​**​意味着替换一个变量(例如'}, '#text': '这样的规则就像我们的主'}}}, '#text': '\to'}, '#text': ",你可以使用 'hi'、*或* 'hey'、*或* 'hello'。” 像 "}, '#text': "\to \text{'hi'} \mid \text{'hey'} \mid \text{'hello'}这样的规则意味着“要创建一个"}, '#text': '是变量。它们不会出现在最终的消息中,但指导着消息的构建过程。它们也称为非终结符。\n\n3. ​**​产生式规则 ($P$)​**​:这些是指令。它们告诉我们如何用其他变量和终结符的组合来替换一个变量。像'}, '#text': ', 和 '}, '#text': ', '}, '#text': ",最后在上面盖上车身。\n\n这正是[上下文无关文法](/sciencepedia/feynman/keyword/context_free_grammars) (CFG) 背后的思想。它是一种用于生成语言中字符串的形式化配方,是一套用简单部件构建复杂结构的蓝图。\n\n### 文法工具箱:变量、终结符和规则\n\n每个[上下文无关文法](/sciencepedia/feynman/keyword/context_free_grammars)的核心由四个关键部分定义,一个四元组,通常写作 $G = (V, T, P, S)$。让我们用一个生成短消息的简单文法来揭开它的神秘面纱。\n\n1. ​**​终结符 ($T$)​**​:这些是我们语言中最终的、不可再分的符号,也就是“乐高积木”。在我们的短消息文法中,它们是我们能看到的实际词语和短语,如 'hi', 'sup', 和 'bye'。它们之所以被称为“终结符”,是因为一旦我们得到一个只包含这些符号的字符串,推导过程就终止了。\n\n2. ​**​变量 ($V$)​**​:这些是“蓝图”或“子组件”。它们代表抽象的结构概念。在我们的例子中,"}, '#text': ',然后加上四个 '}, '#text': '则告诉你,首先搭建一个'}, '#text': '告诉你如何搭建一个底盘。主说明书'}, '#text': '的手册告诉你如何组合一个轮子和一根轮轴。另一本'}, '#text': '## 原理与机制\n\n想象你有一盒乐高积木。你有红砖、蓝砖、轮子和窗户。这些是你的基础、不可分割的部件。现在,再想象你还有一套说明书。一本标记为 `'}