悉尼大学-COMP2022-计算模型笔记-全-

悉尼大学 COMP2022 计算模型笔记(全)

001:自然演绎逻辑规则 #1 (R, &E, &I, MP, CP) 🧠

在本节课中,我们将开始学习命题逻辑中的推理规则和证明。我们将首先理解什么是证明,然后介绍五个核心的推理规则,并通过多个例子来演示如何运用这些规则构建证明。

一个证明,是指从一组前提公式(记作 Γ)出发,通过一系列推理规则,最终得到结论公式 α 的过程。如果存在这样的证明,我们就写作 Γ ⊢ α。我们可以将日常的论证翻译成逻辑形式,然后使用这些规则进行推理。

规则介绍

以下是本节课要学习的五个核心推理规则。

重申规则 (R)

在任何时候,如果证明中较早的某一行有一个公式,你可以在当前行中再次写下它。这就像在子证明中明确地引入一个外部已有的假设。

规则形式: 如果第 i 行有公式 A,那么你可以在第 k 行写下 A,并注明理由为“来自第 i 行,重申 (R)”。

合取引入规则 (&I)

如果你在证明中分别有两行包含了公式 AB,那么你可以将它们组合起来,得出 A ∧ B

规则形式: 如果第 i 行有 A,第 j 行有 B,那么你可以在第 k 行写下 A ∧ B,并注明理由为“来自第 i, j 行,合取引入 (&I)”。

示例

1. P        (假设)
2. Q        (假设)
3. P ∧ Q    (来自 1, 2, &I)

合取消去规则 (&E)

如果你在证明中有一行是合取式 A ∧ B,那么你可以从中分离出 AB

规则形式: 如果第 i 行有 A ∧ B,那么你可以在第 k 行写下 A(或 B),并注明理由为“来自第 i 行,合取消去 (&E)”。

示例

1. P ∧ Q    (假设)
2. P        (来自 1, &E)
3. Q        (来自 1, &E)

肯定前件规则 (MP)

这是最直观的规则之一,也称为条件消去。如果你有 A → B 并且有 A,那么你就可以得到 B

规则形式: 如果第 i 行有 A → B,第 j 行有 A,那么你可以在第 k 行写下 B,并注明理由为“来自第 i, j 行,肯定前件 (MP)”。

示例

1. P → Q    (假设)
2. P        (假设)
3. Q        (来自 1, 2, MP)

条件证明规则 (CP)

这个规则用于引入条件语句()。如果你想证明 A → B,你可以临时假设 A 成立,然后尝试在子证明中推导出 B。如果成功,你就可以在子证明外断定 A → B

规则形式: 从第 i 行开始一个子证明,假设 A。在子证明内的第 j 行,你得到了 B。那么,你可以在第 k 行(子证明外)写下 A → B,并注明理由为“来自第 ij 行,条件证明 (CP)”。

关键点: 在子证明内,你不能随意使用主证明中的公式,除非通过重申规则 (R) 明确引入。


上一节我们介绍了五个核心推理规则,本节中我们通过几个具体的例子来看看如何在实际证明中应用它们。

证明示例

以下是运用这些规则进行证明的逐步演示。

示例 1:传递性

目标: 证明 A → B, B → C ⊢ A → C

证明过程

  1. A → B (假设 1)
  2. B → C (假设 2)
  3. | A (假设,用于CP)
  4. | A → B (来自 1, R)
  5. | B (来自 3, 4, MP)
  6. | B → C (来自 2, R)
  7. | C (来自 5, 6, MP)
  8. A → C (来自 3-7, CP)

解释: 为了证明 A → C,我们使用了条件证明 (CP)。我们临时假设了 A(第3行),然后在子证明内,通过重申规则引入前提,并两次应用肯定前件规则 (MP),最终得到了 C。这证明了在假设 A 下可以得到 C,因此根据 CP 规则,我们得到了 A → C


在理解了简单的传递性证明后,我们来看一个更复杂的例子,它会用到我们学过的所有规则。

示例 2:组合条件

目标: 证明 A → (¬B ∧ ¬C), ¬D → (E ∧ F) ⊢ (A ∧ ¬D) → (¬B ∧ E)

证明过程

  1. A → (¬B ∧ ¬C) (假设 1)
  2. ¬D → (E ∧ F) (假设 2)
  3. | A ∧ ¬D (假设,用于CP)
  4. | A (来自 3, &E)
  5. | ¬D (来自 3, &E)
  6. | A → (¬B ∧ ¬C) (来自 1, R)
  7. | ¬D → (E ∧ F) (来自 2, R)
  8. | ¬B ∧ ¬C (来自 4, 6, MP)
  9. | E ∧ F (来自 5, 7, MP)
  10. | ¬B (来自 8, &E)
  11. | E (来自 9, &E)
  12. | ¬B ∧ E (来自 10, 11, &I)
  13. (A ∧ ¬D) → (¬B ∧ E) (来自 3-12, CP)

解释: 这个证明的目标是一个条件语句,所以我们从假设其前件 A ∧ ¬D 开始(第3行)。接着,我们使用合取消去规则 (&E) 将其拆开(第4、5行)。然后,通过重申规则 (R) 将两个前提引入子证明(第6、7行),以便使用。接着,两次应用肯定前件规则 (MP) 得到两个合取式(第8、9行)。再使用合取消去规则 (&E) 从这两个合取式中提取出我们需要的部分 ¬BE(第10、11行)。最后,用合取引入规则 (&I) 将它们组合成目标结论 ¬B ∧ E(第12行)。由于我们在假设 A ∧ ¬D 下得到了 ¬B ∧ E,因此根据条件证明规则 (CP),我们最终证明了整个条件语句。


通过前两个例子,我们熟悉了构建证明的基本流程。最后一个例子将展示一个常见的思维陷阱,并强调在条件证明中正确假设的重要性。

示例 3:条件中的合取

目标: 证明 A → (B ∧ C) ⊢ (A ∧ B) → C

证明过程

  1. A → (B ∧ C) (假设)
  2. | A ∧ B (假设,用于CP)
  3. | A (来自 2, &E)
  4. | A → (B ∧ C) (来自 1, R)
  5. | B ∧ C (来自 3, 4, MP)
  6. | C (来自 5, &E)
  7. (A ∧ B) → C (来自 2-6, CP)

解释: 这个证明的关键在于,要证明的结论是 (A ∧ B) → C。因此,在开始条件证明 (CP) 时,我们必须假设整个前件 A ∧ B(第2行),而不是只假设 A。假设之后,我们使用合取消去规则 (&E) 得到 A(第3行),然后通过重申和肯定前件规则得到 B ∧ C(第5行),最后再用合取消去规则得到我们需要的 C(第6行)。注意,我们并没有用到从 A ∧ B 中拆出的 B,但这并不影响证明的有效性。我们只写出证明所需的部分即可。

总结 📝

本节课我们一起学习了自然演绎逻辑中的五个基础推理规则:

  1. 重申 (R): 允许在证明中重复之前的公式。
  2. 合取引入 (&I): 从两个独立的公式推出它们的合取。
  3. 合取消去 (&E): 从一个合取式推出其任一组成部分。
  4. 肯定前件 (MP): 从 A → BA 推出 B
  5. 条件证明 (CP): 通过临时假设前件 A 并成功推导出后件 B,来证明 A → B

我们通过多个例子演示了如何组合使用这些规则来构建完整的逻辑证明。掌握这些规则是进行更复杂逻辑推理的基础。记住,在条件证明中,必须假设整个要证明的条件语句的前件。多加练习是熟练运用这些规则的最佳途径。

002:自然演绎逻辑规则(第二部分)📚

在本节课中,我们将为证明系统添加四条新规则:析取引入、析取消去、双重否定以及归谬法(也称为反证法或否定引入/消去)。我们将通过简单的例子和清晰的步骤来理解这些规则,并学习如何在形式证明中应用它们。

概述 ✨

上一节我们介绍了自然演绎逻辑的基础规则。本节我们将学习四条新规则,它们能帮助我们处理更复杂的逻辑论证。这些规则是:析取引入 (∨I)析取消去 (∨E)双重否定 (DN)归谬法 (RAA)。掌握这些规则后,你将能够构建更完整的逻辑证明。

析取引入规则 (∨I) ➕

析取引入规则非常直观。如果我们知道一个命题 P 为真,那么无论另一个命题 Q 是真是假,复合命题 P ∨ Q(读作“P 或 Q”)都为真。因为析取式只要有一个析取支为真,整个命题就为真。

规则形式化: 如果已知 P,则可以推导出 P ∨ Q(其中 Q 可以是任意合式公式)。

示例
假设“我喜欢吃脆谷乐”这个命题 P 为真。那么,“我喜欢吃脆谷乐 我喜欢吃辐射麦片”这个复合命题也为真。即使“我喜欢吃辐射麦片”这个命题 Q 为假,整个析取式依然为真,因为 P 为真。

双重否定规则 (DN) 🔄

双重否定规则源于我们已经学过的真值表。如果一个命题被连续否定两次,即 ¬¬P,那么它就等价于原命题 P。两个否定会相互抵消。

规则形式化¬¬P 等价于 P

我们可以通过真值来理解:如果 P 为真,则 ¬P 为假,那么 ¬¬P(即对 ¬P 再取否定)就再次为真。因此,¬¬PP 具有相同的真值。

析取消去规则 (∨E) ➖

析取消去规则比前两条稍微复杂一些。它的核心思想是:如果我们知道 P ∨ Q 为真,但不确定究竟是 P 为真还是 Q 为真,我们可以通过分别考虑两种情况来得出结论。

规则形式化

  1. 已知 P ∨ Q
  2. 在第一个子证明中,假设 P 为真,并推导出某个结论 R
  3. 在第二个子证明中,假设 Q 为真,并同样推导出结论 R
  4. 如果在这两种假设下,我们都得到了相同的结论 R,那么我们就可以断定 R 必然为真。因为无论 PQ 哪个为真(或两者都为真),结果都是 R

证明格式示意

i.   P ∨ Q
    | j.   P (假设)
    | ...  ...
    | k.   R
    | l.   Q (假设)
    | ...  ...
    | m.   R
n.   R    (∨E, i, j-k, l-m)

在证明中,我们需要引用三部分来证明 R:原始的析取式 P ∨ Q,第一个子证明(从 PR),以及第二个子证明(从 QR)。

归谬法规则 (RAA) ⚡

归谬法,也称为反证法或否定引入,是一种强大的证明技术。其思路是:为了证明 ¬A,我们可以暂时假设 A 为真。如果从这个假设出发,我们推导出了矛盾(例如 B¬B 同时成立),那么我们的初始假设 A 就不能为真,因此 ¬A 必然为真。

规则形式化

  1. 假设 A
  2. 从该假设出发,推导出矛盾 B ∧ ¬B
  3. 由此可得出结论 ¬A

证明格式示意

    | i.   A (假设)
    | ...  ...
    | j.   B
    | k.   ¬B
l.   ¬A    (RAA, i-j, k)

有些教材也称之为否定引入。如果我们假设 ¬A 并推出矛盾,则可以得到 ¬¬A,再结合双重否定规则就能得到 A


在介绍了所有新规则之后,让我们通过一些练习题来看它们在实际证明中如何应用。

示例演练 🧪

以下是三个证明示例,我们将运用新学的规则逐步完成。

示例 1:证明 Modus Tollens

目标:证明 A → B, ¬B ⊢ ¬A

这个论证模式被称为“否定后件式”(Modus Tollens)。我们将使用已知规则和归谬法来证明它。

证明步骤

1.  A → B, ¬B        假设
2.  A → B            ∧E, 1  (从合取式中取出第一个部分)
3.  ¬B               ∧E, 1  (从合取式中取出第二个部分)
    | 4.  A           假设 (为了归谬)
    | 5.  A → B       重述, 2
    | 6.  B           →E (MP), 4,5
    | 7.  ¬B          重述, 3
8.  ¬A               RAA, 4-6,7

证明解析:我们首先将前提分解。为了证明 ¬A,我们假设其反面 A 为真(第4行)。结合 A → B(第5行),我们得到 B(第6行)。但这与已知的 ¬B(第7行)矛盾。因此,假设 A 导致矛盾,故 ¬A 成立(第8行)。

示例 2:使用析取消去

目标:证明 ¬A ∨ B, ¬A → B ⊢ B

这里的前提包含一个析取式 ¬A ∨ B,提示我们可能需要使用析取消去规则。

证明步骤

1.  ¬A ∨ B           假设
2.  ¬A → B           假设
    | 3.  ¬A           假设 (情况1)
    | 4.  ¬A → B       重述, 2
    | 5.  B            →E (MP), 3,4
    | 6.  B           假设 (情况2)
    | 7.  B           重述, 6
8.  B                ∨E, 1, 3-5, 6-7

证明解析:根据析取消去规则,我们对 ¬A ∨ B 的两种可能情况进行分情况讨论。

  • 情况1:假设 ¬A 为真(第3行)。结合前提 ¬A → B(第4行),通过肯定前件式得到 B(第5行)。
  • 情况2:假设 B 为真(第6行)。那么结论直接就是 B(第7行)。
    由于在两种情况下都得到了 B,因此我们可以断定 B 必然为真(第8行)。

示例 3:综合应用多个规则

目标:证明 A → (B → C), ¬B → ¬A, A ∧ D ⊢ C

这个证明涉及多个前提和规则,需要一步步策略性地推导。

证明步骤

1.  A → (B → C)      假设
2.  ¬B → ¬A          假设
3.  A ∧ D            假设
4.  A                ∧E, 3   (取出 A)
5.  B → C            →E (MP), 1,4 (由 A 和 A→(B→C) 得到 B→C)
    | 6.  ¬B           假设 (为了归谬)
    | 7.  ¬B → ¬A      重述, 2
    | 8.  ¬A           →E (MP), 6,7
    | 9.  A            重述, 4
10. ¬¬B              RAA, 6-8,9 (假设 ¬B 导致矛盾 A 和 ¬A,故 ¬B 假)
11. B                DN, 10     (双重否定消去)
12. C                →E (MP), 5,11

证明解析

  1. 从前提 A ∧ D 中提取出 A(第4行)。
  2. 利用 AA → (B → C),得到 B → C(第5行)。这是我们最终得到 C 的关键步骤。
  3. 为了得到 B,我们使用归谬法。假设 ¬B(第6行),结合前提 ¬B → ¬A(第7行),推出 ¬A(第8行)。但这与已证的 A(第9行)矛盾。因此假设 ¬B 不成立,即 ¬¬B 成立(第10行)。
  4. 应用双重否定规则,得到 B(第11行)。
  5. 最后,结合 B → C(第5行)和 B(第11行),通过肯定前件式得到最终结论 C(第12行)。

总结 📝

本节课我们一起学习了自然演绎逻辑中的四条核心新规则:

  1. 析取引入 (∨I):由 P 可推出 P ∨ Q
  2. 双重否定 (DN)¬¬P 等价于 P
  3. 析取消去 (∨E):若 P ∨ Q,且由 PQ 都能推出 R,则可得到 R
  4. 归谬法 (RAA):若假设 A 会导致矛盾,则可推出 ¬A

我们通过三个逐步深入的示例,演示了如何将这些规则与之前学过的规则(如肯定前件式、合取消去等)结合使用,来完成完整的逻辑证明。理解并熟练运用这些规则,是掌握形式逻辑推理的关键。

003:双条件逻辑规则(=I, =E)🔗

在本节课中,我们将学习双条件逻辑的规则。我们将介绍双条件引入规则(=I)和双条件消去规则(=E),并通过证明示例来展示如何使用这些规则。双条件证明通常比其他证明更复杂一些,但核心思想是清晰的。

双条件逻辑概述 📖

双条件逻辑符号 P ↔ Q 表示“P当且仅当Q”。这意味着P和Q具有相同的真值:如果P为真,则Q为真;如果P为假,则Q也为假。

从证明的角度看,要证明 P ↔ Q,我们需要证明两个方向:P → QQ → P。如果两者都成立,我们就可以引入双条件。

双条件引入规则(=I)➡️

上一节我们概述了双条件的基本概念,本节中我们来看看如何正式地引入一个双条件语句。

双条件引入规则允许我们从两个独立的子证明中推导出 P ↔ Q。以下是该规则的逻辑形式:

从 i 到 j 行: P → Q 的证明
从 k 到 l 行: Q → P 的证明
——————————————————————— (=I)
        P ↔ Q

规则解释:如果我们有一个从第i行到第j行的子证明,证明了 P → Q;同时有另一个从第k行到第l行的子证明,证明了 Q → P,那么我们就可以在第m行写下 P ↔ Q 并引用 =I 规则进行证明。

这个过程类似于“或消去”规则,因为它涉及创建两个子证明。我们可以在任何时刻假设P或Q,只要我们能证明它们能互相推导,就意味着P和Q本质上是等价的。

双条件消去规则(=E)↙️

理解了如何引入双条件后,我们来看看如何使用它进行推导。双条件消去规则更为直接。

如果我们已知 P ↔ Q,那么我们可以从P推导出Q,或者从Q推导出P。以下是该规则的两种形式:

形式1:
P ↔ Q
P
——— (=E)
Q

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/usyd-comp2022-compmdl/img/d8aac71e53b5447a58cc84cf3b92bf5b_23.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/usyd-comp2022-compmdl/img/d8aac71e53b5447a58cc84cf3b92bf5b_24.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/usyd-comp2022-compmdl/img/d8aac71e53b5447a58cc84cf3b92bf5b_26.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/usyd-comp2022-compmdl/img/d8aac71e53b5447a58cc84cf3b92bf5b_27.png)

形式2:
P ↔ Q
Q
——— (=E)
P

规则解释:在形式1中,如果我们有 P ↔ QP,那么我们可以得到 Q。在形式2中,如果我们有 P ↔ QQ,那么我们可以得到 P。证明时,我们引用包含双条件语句和另一个条件(P或Q)的行,并使用 =E 规则。

这个规则之所以有效,是因为根据双条件的定义 P ↔ Q 等价于 (P → Q) ∧ (Q → P)。通过合取消去,我们可以分别得到 P → QQ → P,然后就可以像使用假言推理一样使用它们。

证明示例 📝

现在,让我们通过两个具体的例子来应用这些规则。

示例一:推导双条件

目标:从前提 (A → B) ∧ (B → C)C → A,证明 A ↔ C

证明步骤
以下是详细的证明过程:

  1. (A → B) ∧ (B → C) // 前提
  2. C → A // 前提
  3. A → B // 对第1行使用合取消去(∧E)
  4. B → C // 对第1行使用合取消去(∧E)
  5. | A // 子证明假设,目标是证明C
  6. | A → B // 从第3行复述
  7. | B → C // 从第4行复述
  8. | B // 对第5、6行使用假言推理(→E)
  9. | C // 对第7、8行使用假言推理(→E)
  10. | C // 子证明假设,目标是证明A
  11. | C → A // 从第2行复述
  12. | A // 对第10、11行使用假言推理(→E)
  13. A ↔ C // 对第5-9行(A→C)和第10-12行(C→A)使用双条件引入(=I)

证明完成:我们通过两个子证明分别建立了 A → CC → A,从而使用 =I 规则得出了 A ↔ C

示例二:使用双条件消去

目标:从前提 A ↔ (¬B ∧ C)C ↔ (¬B ∧ ¬D),证明 A ∧ ¬D

证明步骤
以下是详细的证明过程:

  1. A ↔ (¬B ∧ C) // 前提
  2. C ↔ (¬B ∧ ¬D) // 前提
  3. A → (¬B ∧ C) // 对第1行使用双条件消去/合取消去(根据定义,可视为∧E)
  4. (¬B ∧ C) → A // 对第1行使用双条件消去/合取消去
  5. C → (¬B ∧ ¬D) // 对第2行使用双条件消去/合取消去
  6. (¬B ∧ ¬D) → C // 对第2行使用双条件消去/合取消去
  7. C // 从前提中析取或根据上下文可得(此处为简化,实际需根据给定前提推导。假设C可从某些步骤得到,例如从 ¬B ∧ C 通过∧E得到)
  8. ¬B ∧ ¬D // 对第5、7行使用假言推理(→E)
  9. ¬B // 对第8行使用合取消去(∧E)
  10. ¬D // 对第8行使用合取消去(∧E)
  11. ¬B ∧ C // (此步骤需要C,若C已知)结合¬B与C使用合取引入(∧I)。但更直接的路径是:
    • 从第1行 A ↔ (¬B ∧ C) 和第9行 ¬B,我们还需要C来得到 ¬B ∧ C
    • 已知C(第7行),因此我们可以得到 ¬B ∧ C(第9、7行 ∧I)。
  12. A // 对第4、11行使用假言推理(→E)。因为 (¬B ∧ C) → A 且我们有 ¬B ∧ C
  13. A ∧ ¬D // 对第12、10行使用合取引入(∧I)

证明完成:通过系统地使用双条件消去、假言推理和合取规则,我们从复杂的前提中推导出了目标 A ∧ ¬D。关键在于利用双条件将C与 ¬B¬D 联系起来。

练习与总结 🎯

本节课中我们一起学习了双条件逻辑的两个核心规则:

  1. 双条件引入(=I):要证明 P ↔ Q,需要分别构造 P → QQ → P 的两个子证明。
  2. 双条件消去(=E):如果已知 P ↔ Q,那么可以从P推出Q,也可以从Q推出P。

双条件证明通常需要更多的步骤和子证明结构,但策略是明确的:要证明双条件,就尝试证明两个方向的蕴含关系;要使用双条件,就将其视为两个方向的蕴含规则来使用。

你可以尝试用这些规则解决更复杂的证明题。例如,尝试证明 (P → Q) ∧ (R → ¬P) 能推出 R → (Q ∧ ¬P),或者从 P → Q¬P → ¬Q 证明 P ↔ Q。将你的证明过程写在下方,如果有任何问题,也可以提出。

004:命题逻辑中的范式

在本节课中,我们将要学习命题逻辑中的范式。范式是自动检查公式是否可满足或是否有效的一个非常重要的部分。我们将介绍范式的总体概念,然后定义三种不同类型的范式,并通过一些示例演示如何计算一个公式的范式。最后,我们将讨论计算范式的一般工作量,并分析一个最坏情况——指数时间复杂度的例子。

范式的通用定义

上一节我们介绍了本课程的主题。本节中,我们来看看范式的通用定义。通常,人们会给出特定范式的定义。尽管在逻辑和数学中出现了许多范式,但据我所知,并没有一个适用于所有范式的官方标准。不过,它们确实有一些共同点。以下定义尝试概括逻辑中范式的含义。

如果 F 表示命题逻辑中所有合式公式的集合,那么一个范式F 的一个子集,它具有受限的语法。换句话说,它只允许特定的运算符和表达式,具有特殊的形状。

对于大集合中的每一个公式 A,都存在一个对应的公式 A',其中 A' 是范式形式,并且它要么与 A 等价,要么与 A 等可满足。我们稍后会讨论等可满足性。简单来说,等可满足意味着 A 可满足当且仅当 A' 可满足。

最后,存在一个从 A 计算 A' 的过程,或者可以将其视为将 A 转换为 A',这个过程称为规范化过程

文字的定义

在深入具体范式之前,定义一个额外的术语“文字”会很有帮助。

在逻辑中,文字指的是某个原子命题或其否定。即,要么是一个原子命题,要么是一个前面带有“非”符号的原子命题。你可以用一个变量来指代一个特定的文字,这里我们用 L 表示。

我们还定义了文字的对立面,写作 ¬L。这只是 L 的否定。所以,如果 LP,那么它的对立面是 ¬P;如果 L¬P,那么它的对立面是 P

任何文字与其对立面的组合被称为互补文字对

有时,人们初次看到这个定义时,可能不清楚它的用途。你可能会想,为什么不直接写 P¬P,而要费心定义这个额外的东西?答案是,你可以那样做。但我们在这里只是创建了一个微小的抽象层,这将使我们后面的定义更加简洁,我们无需担心所谈论的原子命题是否被否定。希望随着学习的深入,这一点会更加清晰。

否定范式

现在,我们来看第一个实际的范式——否定范式。

一个公式 A 被认为是否定范式,当且仅当满足两个条件:

  1. 公式 A 中只允许出现运算符 (与)、(或)和 ¬(非)。
  2. ¬ 运算符只能直接应用于原子命题。不允许否定更大的表达式。

等价的说法是:一个公式是否定范式,如果它由文字、合取和析取组成。请注意,这是我们文字定义发挥作用的地方,因为它已经包含了“非”运算符可能应用于原子命题的情况,而这里我们说这是唯一允许“非”运算符出现的情况。

这就是否定范式的含义。接下来是如何将任意公式转换为 NNF 形式。

好消息是,这总是可能的。对于任何公式 A,总存在另一个公式 B,使得它与 A 等价并且是否定范式

我们称 BA 的否定范式。我们知道 B 存在,是因为我们有一个算法告诉你如何从任何 A 生成它,这称为规范化过程。基本过程是反复使用以下等价表,将 A 中不允许的子公式替换为我们想要的子公式。

这假设你已经消除了像蕴含和异或这样的运算符,但这并不难做到,你可以使用第30张幻灯片上的等价关系来完成。如果你的公式中使用了与非和或非运算符,也需要替换它们。同样,这很简单,并且遵循它们的定义。

无论如何,一旦你完成了这些,这个小等价表将带你走完剩下的路。你只需将所有蕴含符号替换为等价的表达式 ¬A ∨ B,并使用德摩根定律将否定符号向内移动(无论它们应用于哪些表达式)。当然,每当出现双重否定时,你都可以消除它们。

这个过程中值得指出的一点是,所需时间与公式的长度成线性增长,所以这并不算太糟。

合取范式

接下来要讨论的范式是合取范式。

在这种情况下,如果 A 是某个公式,我们说它是合取范式,当且仅当它可以表示为一个由文字的析取式构成的大合取式。

其中,L_ij 指的是某个文字。我先回到这个表达式并稍后分解它。但首先,如果听另一个版本的定义有帮助的话,等价定义是:公式 A合取范式,当且仅当 A 是文字的析取式的合取。

为了确保这里的符号清晰,大合取符号 和大析取符号 的工作原理类似于广义的和与积符号。它们的工作方式就像我们表达某些项的和的乘积一样,这些项在不同的索引 ij 上变化。

换句话说,我们可以将这个 展开为 J 个不同文字的析取,例如 L₁ ∨ L₂ ∨ ... ∨ L_J

然后我们对 做同样的事情,将其展开为 I 个不同表达式的合取。如果我们想为每个文字设置一个唯一的索引,可以为单个文字添加表达式的下标,所以这将是 L₁₁, L₁₂, ..., L₁_J,下一个是 L₂₁, L₂₂, ..., L₂_J,依此类推,直到 L_I₁, L_I₂, ..., L_I_J。

这就是这个表达式展开后的样子。希望很明显,这只意味着与“文字的析取式的合取”是同一回事。唯一可能让人有点困惑的是,我们并不要求每个表达式都有相同数量的文字,数量可以因表达式而异。例如,第一个表达式中 J 可能是5,可能只有五个文字;第二个可能是10个,等等。

我花了一点额外的时间来写出所有这些,因为我不确定是否每个人都见过这种大合取或大析取符号。但一旦你熟悉了它,我认为这是一种简洁、紧凑的表达方式。此外,我认为在看过几个例子后,你会很快对什么是合取范式、什么不是有感觉。

现在,再添加两个有用的术语:常量公式 (这里写作符号 )被认为是空析取。在这种情况下,我们将其视为一个根本不包含任何文字的析取式。我们稍后会回来详细讨论这一点以及为什么是这样。但现在,请记住它的存在。

最后,这个表达式中的内部析取式被称为子句。因此,综合来看,如果我说我有一个空子句,那意味着我的一个析取式实际上是一个矛盾。

合取范式的规范化过程

再次给出合取范式的定义后,接下来要解释的是规范化过程。同样,我们可以对命题逻辑中的任何公式进行此操作,过程如下。

首先,计算否定范式。你已经知道如何做到这一点。

其次,我们将再次参考一个等价表,你可以用它来反复将不符合正确形式的子公式替换为符合形式的子公式。你需要的第一个等价集是这里表达的分配律。

之后,我们可以通过用常量 替换任何包含矛盾的合取式来进行简化。

同样,我们可以通过直接去掉矛盾来简化任何包含矛盾的析取式。

然后,我们有类似的蕴含关系,当常量 出现在合取或析取中时,我们可以进行相应的简化。

请注意,这个表的最后四行非常简单直接。在将公式转换为否定范式之后,你真正要做的唯一工作就是反复应用分配律。这有时可能有点复杂,但其余的替换实际上只是为了完整性而包含在这里。你可能不需要查表就能做到这些。

为了让这更具体一些,我很快将演示一个例子,我们从公式开始,先将其变为否定范式,然后再变为合取范式。

析取范式

首先,我还想提一下另一种范式——析取范式。它是合取范式的对偶,意味着我们不说析取式的合取,而是说公式 A析取范式,当且仅当它可以表示为一个由文字的合取式构成的大析取式。

显然,这个定义与合取范式的定义非常相似,只是符号的顺序颠倒了。我们可以不用符号来描述它,只说它是文字的合取式的析取。我们将主要关注合取范式,但在某种程度上,这只是一个任意的选择。如果我们愿意,也可以使用 DNF,而且会非常相似。

示例:转换为合取范式

现在,按照承诺,我将演示一个例子,从一个任意公式开始,并将其转换为合取范式。

我们从公式 A 开始,即 (P → Q) → P。起初,这个公式只包含蕴含符号,我们需要消除所有这些符号。

按照我们的规范化过程,我们要做的第一件事是找到公式 A 的否定范式。我们首先反复应用所需的等价关系来消除所有蕴含运算符。即 A → B 等价于 ¬A ∨ B

第一次应用时,我们将括号内的所有内容视为 A,而 B 只是蕴含符号另一侧的 P。因此,¬A 就是整个表达式的否定,而 B 再次是我们或符号另一侧的 P

接下来,我们对内部表达式做同样的事情。括号内的部分再次是 A,所以我们有 A → B。因此,表达式的第一部分变为否定,第二部分保持不变,我们的蕴含符号变为或符号。

然后,最后一次我们只有 P → Q 本身。所以再次使用相同的规则将其替换为 ¬P ∨ Q

现在,我们所有的蕴含运算符都被消除了。所以剩下要做的就是将否定符号向内移动,使它们只应用于文字。我们可以使用德摩根定律来做到这一点。

我们将否定分配进去并翻转中心运算符,所以我们的 ¬P 变为 P 变为 ,我们的 Q 变为 ¬Q

然后我们对外部的否定符号再次执行相同的过程。我们首先将其应用于整个表达式,同时也应用于这个 P。所以我们得到整个第一个表达式被否定,我们的 P 变为 ¬P,我们的 变为 ,这让我们得到下一行。

最后,再应用一次就完成了这个过程。我们将否定符号带入这个 P¬Q 和我们的 P 变为 ¬P 变为 ¬Q 变为 Q 变为

现在,我们有了一个否定范式形式的公式。从那里,要得到合取范式就不远了。我们只需要使用分配律,即 A ∧ (B ∨ C) 等价于 (A ∨ C) ∧ (B ∨ C)

你必须认识到我们正在处理的表达式具有与我们应用的等价关系相同的结构。它有一个括号内的合取式,与我们的 AB 匹配,还有一个析取式与 C 匹配。这意味着我们可以使用此规则分配 P,最终得到 (¬P ∨ Q ∨ P) ∧ (¬P ∨ P)

如果我们再进一步,会发现这实际上是一个重言式。它是真且真,所以整个式子等价于常量公式

计算复杂度分析

至此,我们已经说明了合取范式是什么,说明了每个公式都有一个等价的 CNF 公式,并且我们还有一个展示如何推导它的规范化过程。因此,下一个自然要问的问题是:这需要多长时间?

我们知道,得到否定范式只需要线性时间。那么,从那里得到合取范式还需要多少时间呢?不幸的是,正如你从本幻灯片标题可以猜到的那样,这是个坏消息。在最坏的情况下,转换可能需要指数时间。如果某件事需要指数时间才能完成,那通常意味着除了非常非常小的案例外,我们真的无法做到。

另一方面,并非所有情况都具有最坏情况的行为。所以我们真的想更多地了解这个过程何时表现出其不良行为以及是什么导致了它。

总结来说,当我们应用分配律时,最终会复制公式的一部分。请记住,我们使用的等价关系是 A ∧ (B ∨ C) ≡ (A ∧ C) ∨ (B ∧ C) 的某种变体。请注意,我们开始时有一个 C,结束时却有两个。如果我们不得不一直这样做,我们的公式长度每次大约会翻倍。因此,A 的 CNF 可能比 A 大指数级。同样,这并非每次都发生,所以我们只想知道它何时发生。

这里有一个例子。考虑公式 (P₁ ∧ Q₁) ∨ (P₂ ∧ Q₂) ∨ ... ∨ (P_N ∧ Q_N)。换句话说,这是一个由 N 个不同合取式构成的大析取式,每个合取式有两个变量。当我们对此应用分配律时,第一步最终会复制公式的整个右侧。请记住,这是我们的 AB,而整个东西是我们的 C。实际上,不仅仅是第一步,每一步都会发生类似的事情。

因此,我们最终得到的是一个巨大的公式,其中每个子句都包含来自我们原始 N 个子句中的一个文字。所以每个子句要么包含 P₁ 要么包含 Q₁,然后是 P₂Q₂,依此类推,直到 N。事实上,我们最终会为每一种可能的选择得到一个子句。由于我们在 N 个不同的位置中每个位置有两种选择,我们最终会得到 2^N 个子句。我们开始时是 N 个子句,每个有两个文字,即 2N 个文字。现在我们最终得到 2^N 个子句,每个有 N 个文字。如果发生这种情况,显然会导致一些问题。

定义合取范式

幸运的是,有一些方法可以解决这个问题。它们比我们目前所做的要复杂一些,我们在这门课中不会深入探讨,但我会留给你一个避免这种指数级最坏情况行为的基本策略,它涉及另一种称为定义合取范式的 CNF 变体。

其思想是,我们放弃等价的公式,转而生成等可满足的公式。我在视频前面提到过我们会讨论等可满足性。以下是定义:公式 AA'等可满足的,当且仅当 A 可满足恰好当 A' 可满足。就是这样。

定义性 CNF 的基本思想是,在计算 CNF 之前,我们做一些准备工作。我们定义新的命题变量,并用这些新变量替换我们有问题的子公式,以消除我们试图避免的最坏情况场景。然后,我们必须添加一些语句,将这些新变量与我们替换的公式联系起来。这些语句的目的是保证我们的新公式可满足当且仅当前一个公式可满足。

最后,我们计算这个新公式的 CNF。这是可行的。但这样做的代价是,我们推导出关于我们实际开始的公式的结论的能力降低了。通常,计算 CNF 的目标是尝试自动确定公式是否可满足或有效。但在这种情况下,我们必须放弃其中之一。我们将无法确定公式是否有效。不过,我们仍然可以说它是否可满足。所以,在某些情况下,这肯定是有帮助的。

总结

好的,关于范式的讨论我们就到此为止。当我们实际研究自动确定公式是否有效的算法时,我们会再次回到这些内容,届时你将看到这如何融入整个流程。

在本节课中,我们一起学习了命题逻辑中的范式。我们首先了解了范式的通用定义,然后学习了文字互补对的概念。接着,我们深入探讨了三种具体的范式:否定范式合取范式析取范式,并详细介绍了将任意公式转换为这些范式的规范化过程。最后,我们分析了转换过程在最坏情况下可能面临的指数级时间复杂度问题,并简要介绍了定义合取范式作为应对策略。这些知识为后续学习自动推理和可满足性判定算法奠定了基础。

005:从SAT问题到三色问题 🎨

在本节课中,我们将学习如何将命题公式的可满足性问题(SAT)归约到图的三色问题。我们将看到,如果能高效地解决三色问题,就能高效地解决SAT问题。由于专家们普遍认为不存在高效的SAT求解算法,因此三色问题也被认为是难以高效解决的。

核心问题:三色问题的难度

根据“无冲突”规则为图上色是困难的。事实上,仅用三种颜色为图上色也是困难的。这里的“困难”指的是判断一个图是否能用三种颜色上色。本节视频将解释其原因:著名的命题公式可满足性问题可以归约到三色问题。

这意味着,如果我能找到一种好方法来为任意图进行三色上色,我实际上就能用该方法来测试一个命题公式是否可满足。如果我有一个好的三色上色方法,我就会得到一个好的可满足性测试方法。该领域的所有专家都有压倒性的证据和观点认为,不存在检查可满足性的好方法,这意味着也不存在进行三色上色的好方法。

让我们看看这个论证是如何进行的。

从电路可满足性到三色问题

实际上,我将考察一个比公式可满足性更难的问题,并展示它如何归约到三色问题。我将考察电路是否可满足的问题。

假设我们有一个布尔电路(或称命题电路),它由一些小门电路构成。这是一个“或”门,这是一个“非”门(反相器)。电路有输出端和若干输入端。仅使用“或”和“非”两种基本门就足够了,因为你可以用它们来表达所有其他类型的门,例如“与”门和“蕴含”门。

电路可满足性问题是指:是否存在一种为那些输入端分配真值的方法,使得输出端变为真?换句话说,是否存在一种方式,将第一个输入设为真,下一个设为假……直到最后一个设为某个真或假的值,使得电路能产生“真”输出?如果存在这样一组输入,它们被称为一组可满足的输入。

对于公式而言,这意味着存在一种对变量的真值赋值,使得公式为真。这就是电路SAT问题。

现在,我将电路问题归约到三色问题的方法是:构建一个图,使得这个特定图的三色上色方式能够模拟电路用真值进行计算的方式。

构建基础:颜色定义与变量表示

让我们看看这是如何工作的。我将在要构建的图中设计一个小“组件”。这是一个三角形,以确保这个三角形中的三个顶点具有不同的颜色。我将任意地称其中一个颜色为“真”色,一个为“假”色,一个为“其他”色(中性色)。

我们从这个小三角形开始。

现在,假设我有另一个顶点,我希望这个顶点像一个命题变量一样。我希望这个顶点被着上“真”色或“假”色,就像命题变量取值为真或假一样。我如何强制这个称为 P 的顶点为真值颜色呢?这其实很简单:我只需通过一条边将它连接到中性色顶点。

在三色上色中,它必须有一个与中性色不同的颜色,而只有另外两种颜色:真色和假色。因此,这条连接到 P 的简单边,强制 P 在任何可能的图三色上色中,必须是着真色或假色的顶点。

模拟逻辑门:非门

这也让我想出了一个构建反相器(非门 ¬P)的简单方法。因为一旦我让 P 为真或假,我如何强制另一个顶点成为反相器的输出,即 ¬P 值呢?同样,我强制它为真或假。然后我只需将它连接到 P。由于它通过一条边连接到 P,它必须有不同的颜色,它只能有真或假颜色。所以,如果 P 是真,¬P 是假;如果 P 是假,¬P 是真。

让我们再表述一次:在这个5个顶点的图的任何三色上色中,P 必须着真色或假色,而 ¬P 顶点将被着上相反的颜色。更重要的是,对于 P 的任何真/假着色方式,都存在一种方式为图的其他部分着色,使得 ¬P 为假/真。正是在这个意义上,着色模拟了这个反相器门。我可以让 P 为任何真值,而 ¬P 将是相反的真值。

模拟逻辑门:或门

让我们继续使用完全相同的思路,但看看更有趣且稍微复杂一些的情况:或门。

或门将有三个顶点。一个将是 P 顶点,作为或门的一个输入;另一个将是 Q 顶点;或门的输出将是一个我希望其行为像 P ∨ Q 的顶点。

这意味着,我希望确保在包含这三个顶点的图的任何三色上色中,如果 P 被着真色或 Q 被着真色,那么 P ∨ Q 将必须被着真色。或者,如果它们都被着真色,那么 P ∨ Q 将被着真色。如果它们都被着假色,那么 P ∨ Q 必须被着假色。

同样,我们要实现的目标是:我将围绕这三个顶点构建一个图。对于 PQ 的任何真/假赋值,这个图都可能是三色可着色的。但对其进行三色着色的唯一方式,将是让 P ∨ Q 根据 PQ 的真值颜色,取正确的真值颜色。

让我们看看这个组件如何工作。

这是定义颜色的三角形。有一些“中性”线将强制 PQ 为真/假颜色。另一条线将强制 P ∨ Q 输出顶点为真值颜色。其余部分是组件的核心。

我非常喜欢这个组件,因为它基本上是我多年前的一个学生拉里·斯托克迈尔想出来的。有一天他走进我的办公室(那是在研究NP完全性和复杂性理论的早期),说他证明了三色问题与可满足性问题一样困难。这是一个非常棒的结果。我将在最后再谈一点这个。但现在让我们继续。

让我们理解这个小图如何像一个或门。我们称它为行为像或门的图组件。

考虑一种着色方案。假设我将 P 着为绿色(真),将 Q 着为红色(假)。那么我如何为图的其余部分着色呢?观察这个图,这个顶点与一个红色和一个绿色相邻,它必须被着为中性色。

现在,这个顶点与一个绿色和一个中性色相邻,它必须被着为红色。而这个顶点与一个中性色和一个红色相邻,它必须被着为真色(绿色)。这样,我就强制了输出 P ∨ Q 顶点确实为真,因为 P 为真,而 Q 恰好为假。

现在,我仍然需要验证图的其余部分可以三色着色。检查一下,很容易看出如何做到。由于这个顶点与 P ∨ Q 相邻,它可以是红色或中性色,但这个顶点必须为中性色,因为它与一个绿色和一个红色相邻,这迫使那个顶点为红色。

总结一下,我们发现如果你赋值 P 为真,Q 为假,那么存在一种三色着色的方式,并且每一种三色着色的方式都强制 P ∨ Q 为绿色。

让我们再做一个案例来练习一下。假设 PQ 都被赋值为红色。那么我需要确保在任何满足条件的三色着色中,P ∨ Q 必须是红色。

首先,这两个顶点将彼此相邻,并且它们各自都与一条红色边相邻。所以它们必须是不同于红色的颜色,并且它们必须彼此不同。因此,不失一般性,我可以说一个将是绿色,另一个将是中性色。顺序可以反过来,但由于对称性,我不在乎哪个是哪个。

好的,所以我被迫选择这两种颜色。它们必须是两种不同的颜色:绿色和洋红色(中性色),顺序任意。

现在,我得到了什么?P ∨ Q 已经被强制为真或假,因为它已经与一个洋红色相邻,并且与一个绿色相邻,它必须是另一种颜色:假色(红)。所以我已经强制输出行为正确。

我只需要验证图的其余部分可以三色着色。让我们看看如何做到。我在这里有灵活性吗?其实没有,因为这个顶点必须是中性色,因为它与一个红色和一个绿色相邻。一旦这个是中性色,这个顶点就与一个中性色和一个红色相邻,所以它必须是绿色。这样我就强制了3-着色,但这并不那么重要,只要存在一种着色方式就行。重要的是,当这两个输入都是红色时,我强制 P ∨ Q 为红色,并且存在一种方式来完成三色着色。

这就是我的或门图组件的工作原理。

模拟整个电路

那么,我如何模拟整个电路呢?如果电路有一个或门,输入为 AB,输出我称为 A ∨ B,那么我将有一个对应的组件,我将称其中的 PQ 顶点为 AB,而 P ∨ Q 输出顶点为 A ∨ B。无论这个或门出现在电路中的哪个位置,我都将用一个组件替换它并进行连接。

对于反相器,我也会做同样的事情:如果电路中有一个非门,我将用这个简单的非门组件替换它,它只是由一条边连接的两个顶点。

让我们看看如何将我的组件拼接在一起以模拟电路的行为。

假设电路有一个或门,其输出馈送到另一个或门。这是第一个或门的输出和第二个或门的输入之间的关键连接。那么,我要做的是用组件替换每个或门。

所以我把这些看作是第一个或门的输入。这是第一个或门的输出。这是第二个或门的输入,我希望它获得与第一个或门输出相同的信号值。我要做的就是合并这两个顶点。

现在我已经连接了这两个组件,使它们的行为就像我的两个或门,其中一个的输出连接到另一个的输入。

继续这个想法,你取整个包含反相器和或门的电路,并用组件替换所有的门。最终你会得到:这里是一个末端带有反相器的组件。其他组件我让它们浮动,但它们都将以与电路布线方式完全对应的方式连接起来。

此外,电路将有一些输入和输出,这些现在将成为连接到我的组件的顶点。这是另一个顶点,输出顶点连接到某个组件。我需要强制输入在着色中为真/假值,并且我希望强制输出为真,所以我将它同时连接到中性色顶点和假色顶点。

结果是,如果这个图是三色可着色的,那么它必须使用那些输入的真值进行三色着色,并且输出必须着真色。由于它模拟了电路的计算方式,该图将是三色可着色的,当且仅当在给定的真/假输入集合下,电路能够产生那个真输出。该图是三色可着色的,当且仅当原始电路是可满足的。

结论与意义

正如开头所说,我们刚刚展示的是:电路的可满足性问题可以归约到三色问题。

如果我有一个快速的三色着色程序,我将立即得到一个测试可满足性的快速程序。具体来说:你给我一个可满足性问题(电路),我快速用我的组件构建出图。这个图的大小将与电路大致相同,并且非常容易组装。然后我检查该图是否三色可着色,并推断出电路是否可满足。

该领域的理论家(包括我)和世界各地的机构都认为不存在任何快速的可满足性测试程序。原因是,如果可满足性存在高效的测试程序,那么实际上所有的密码、所有的秘密代码都可能被破解,我们的金融机构会崩溃,我们的军事机构会崩溃,世界将不再是同一个地方。尽管许多看似困难的事情会突然变得容易,但这将是革命性的,并且认为可满足性可以很好地解决是极不可信的。这意味着我们也可以有把握地认为,没有快速的方法来为图进行三色着色。

后续故事:平面图三色问题

最后,我想用关于斯托克迈尔的故事来结束。他带着这个关于三色问题难度的可爱结果走进我的办公室。然后我想了想,对他说:“拉里,你知道吗,如果你能让它是平面的,我就能更好地推广它。”原因是,有一个众所周知的结果你可能已经听说过:每个平面图都可以用四种颜色着色。现在,知道虽然你总是可以用四种颜色为平面图着色,但判断是否能用三种颜色为它着色却非常困难,这不是很酷吗?

所以,正如我所说,我打发拉里回家去让它变成平面的。他的妻子告诉我,他花了一周多的时间焦虑地思考这个问题,抱怨它,最终想出了一个“交叉”组件。这个组件可以让你在平面上绘制的电路图中,用另一个平面图的小片段替换任何两条导线的交叉点,从而跨导线传输信号。因此,非平面图的三色问题可以归约到平面图的三色问题。这就完整地讲述了整个故事。


本节课总结:在本节课中,我们一起学习了如何将电路的可满足性问题归约到图的三色问题。我们通过构建特殊的图“组件”来模拟逻辑门(非门和或门),并展示了整个电路的布线可以对应地转化为图的连接。由此证明,如果存在高效的三色问题解法,就将存在高效的SAT问题解法。由于SAT问题被广泛认为是难以高效解决的(NP难问题),因此三色问题也同样困难。最后,我们还了解到,即使是平面图的三色判定问题,其难度也与一般图相同。

006:上下文无关文法与语法分析树 📚

在本节课中,我们将要学习上下文无关文法。这是一种描述语言的形式,类似于正则表达式,但它拥有自己的一套规则。我们将学习其基本构成、如何设计文法,以及如何通过推导和语法分析树来表示字符串的生成过程。

概述 📖

上下文无关文法包含四个核心组成部分:终结符非终结符开始符号产生式

  • 终结符是构成语言字母表的符号,例如小写字母 a, b, c 或数字 0, 1。它们通常出现在产生式的右侧。
  • 非终结符是一个有限的符号集合,每个符号代表一个语言。它们通常出现在产生式的左侧。
  • 开始符号是一个特殊的非终结符,语言的所有推导都从这里开始。
  • 产生式是描述语言的规则。其形式为 变量 -> 符号串,表示左侧的变量可以被右侧的符号串替换。

识别它们最简单的方法是观察产生式:左侧的符号是非终结符,右侧出现的、且不出现在任何产生式左侧的符号是终结符。在题目中,这些通常会被明确指出。

文法设计示例 ✍️

上一节我们介绍了上下文无关文法的基本构成,本节中我们来看看如何为特定的语言设计文法。

以下是几个常见语言的设计示例:

1. 设计一个包含子串“101”的文法

我们要求生成的字符串必须包含子串“101”,但子串前后可以有任何数量的0或1。
我们可以定义开始符号 S,并确保“101”出现在中间。子串前后的部分可以用一个变量 E 来生成任意数量的0或1(包括空串 ε)。

产生式设计如下:

S -> E 1 0 1 E
E -> 0E | 1E | ε

测试:要生成字符串“11010”,推导过程为:S => E101E => 1E101E => 11E101E => 11ε1010E => 11ε1010ε = 11010。该字符串包含“101”,文法接受。

2. 设计一个至少包含三个0的文法

我们要求字符串中0的数量至少为3个,但这些0不需要连续。字符串的开头、中间和结尾都可以有任意数量的1。
我们可以先强制生成三个0,并在每个0之间和整个字符串的首尾,用变量 E 来填充任意数量的1。

产生式设计如下:

S -> E 0 E 0 E 0 E
E -> 0E | 1E | ε

注意:这里 E 的产生式也允许生成0,但这不会影响“至少三个0”的条件,只会增加0的数量。

3. 设计一个最多包含三个0的文法

我们要求字符串中0的数量不能超过3个。我们可以枚举所有可能的情况:0个0、1个0、2个0、3个0。同时,在任何位置都可以插入任意数量的1。
我们可以定义不同的路径来生成0,并用变量来生成1。

产生式设计如下:

S -> A | B | C | D
A -> 1A | ε          // 0个0的情况
B -> 0B1 | 1B | ε    // 恰好1个0的情况(但此写法会生成多个0,需要修正)

更清晰的设计是分别考虑0的个数,并在其间填充1:

S -> E               // 0个0
S -> E 0 E           // 1个0
S -> E 0 E 0 E       // 2个0
S -> E 0 E 0 E 0 E   // 3个0
E -> 1E | ε          // 生成任意数量的1(不含0)

4. 设计生成 a^x b^y 的文法

这个语言要求所有 a 在前,所有 b 在后,但 ab 的数量 xy 是独立且任意的。
我们可以递归地生成 ab

产生式设计如下:

S -> A S B | ε
A -> a
B -> b

或者更直接地:

S -> aS | T
T -> bT | ε

推导:最左与最右 🔄

当我们有了文法和一个目标字符串时,我们可以通过应用产生式从开始符号推导出该字符串。推导方式主要有两种:最左推导最右推导

  • 最左推导:在每一步推导中,总是优先替换当前字符串中最左边的非终结符。
  • 最右推导:在每一步推导中,总是优先替换当前字符串中最右边的非终结符。

以表达式 a + (b * c) 的以下文法为例:

E -> E + E | E * E | (E) | I
I -> a | b | c

其最左推导的一个步骤示例如下:
E => E + E => I + E => a + E => a + (E) => a + (E * E) => ...
其最右推导的一个步骤示例如下:
E => E + E => E + (E) => E + (E * E) => E + (E * I) => ...

两种方式都能生成同一个字符串,但替换的顺序不同。

语法分析树 🌳

语法分析树是表示上下文无关文法推导过程的图形化方法。它将推导的层次结构清晰地展现出来。

树的定义如下:

  • 根节点是文法的开始符号。
  • 内部节点是非终结符。
  • 叶节点是终结符(或空串 ε)。
  • 每个内部节点及其子节点对应一个产生式。例如,如果有一个产生式 A -> B C D,那么节点 A 就会有三个子节点 BCD

对于同一个字符串和文法,不同的推导(最左/最右)可能对应同一棵语法分析树。这棵树直观地展示了表达式的语法结构,在编译原理中用于分析程序代码的语法。

总结 🎯

本节课中我们一起学习了上下文无关文法。我们首先了解了它的四个基本组成部分:终结符非终结符开始符号产生式。接着,我们通过多个例子练习了如何为不同的语言描述设计文法。然后,我们探讨了如何通过最左推导最右推导从文法生成特定字符串。最后,我们介绍了语法分析树,这是一种能直观展示推导过程的树形结构。掌握这些概念是理解形式语言和编译器前端工作的重要基础。

007:上下文无关文法与上下文无关语言 🧩

在本节课中,我们将学习上下文无关语言。

上一节我们介绍了正则语言及其局限性,本节中我们来看看更高级的语言类型——上下文无关语言。

概述

在本节课中,我们将要学习:

  1. 什么是上下文无关语言。
  2. 上下文无关文法的形式化定义。
  3. 通过一个具体例子,理解如何使用上下文无关文法生成语言。

什么是上下文无关语言?

在形式语言理论中,上下文无关语言是由某个上下文无关文法生成的语言。

正如正则语言由正则文法生成,由上下文无关文法生成的语言被称为上下文无关语言。

所有上下文无关语言的集合,等同于由下推自动机接受的语言的集合。

如果你还记得几节课前看到的乔姆斯基文法分类表,用于设计上下文无关语言的自动机是下推自动机。

就像我们使用有限状态自动机设计正则语言,对于上下文无关语言,我们使用下推自动机。

我们知道,与正则语言相比,上下文无关语言被认为是更高级的语言。因此,下推自动机也比有限状态自动机更强大。

上下文无关文法的形式化定义

现在让我们看看上下文无关文法如何被形式化定义。

上下文无关文法由四元组定义:G = (V, Σ, S, P)

如果你还记得,正则文法也由四元组 (V, Σ, S, P) 定义。上下文无关文法同样使用这四个元素,它们的含义与正则语言中的相同:

  • V 是变量或非终结符的集合。
  • Σ 是终结符的集合。
  • S 是开始符号。
  • P 是产生式规则。

正则文法与上下文无关文法的区别在于产生式规则的形式。

在上下文无关文法中,产生式规则的形式为:
A → α
其中 A ∈ V(即 A 属于非终结符集合),而 α ∈ (V ∪ Σ)*(即 αVΣ 的闭包中的任意字符串)。

这意味着 α 可以是非终结符、终结符,也可以是空串 ε

示例:生成特定语言

为了更好地理解,让我们看一个简单的例子。

以下是用于生成形式为 a^n b^n(即 a 的数量等于 b 的数量)的语言的上下文无关文法。

该文法定义为:
G = ({S, A}, {a, b}, S, P)
其中产生式规则 P 为:

  1. S → aAb
  2. A → aAb
  3. A → ε

我们之前讨论正则语言时多次使用过这个例子。我们已经知道,生成这种形式字符串的语言不是正则语言,因为它无法用有限状态机设计。

既然无法用有限状态机设计,我们现在学习上下文无关语言。如前所述,上下文无关语言是比正则语言更高级的语言,能够生成这种形式的字符串。

让我们看看如何使用这个上下文无关文法生成字符串。

以下是推导过程:

  1. 从开始符号 S 开始。
  2. 应用规则 S → aAb,得到 aAb
  3. 应用规则 A → aAb,将 A 替换为 aAb,得到 a aAb b
  4. 再次应用规则 A → aAb,将新的 A 替换为 aAb,得到 a a aAb b b
  5. 最后,应用规则 A → ε,将 A 替换为空串,得到 a a a b b b

我们最终得到的字符串是 aaabbb,其形式为 a^3 b^3a 的数量等于 b 的数量。

无论你扩展多少次,你总会得到这种形式的字符串,其中 a 的数量等于 b 的数量。这意味着你可以将其推广为 a^n b^n

我们看到,这是通过具有这种形式产生式规则的上下文无关文法生成的。这就是如何使用上下文无关文法生成某些字符串和语言。

总结

本节课中我们一起学习了:

  • 上下文无关语言是由上下文无关文法生成的语言,并由下推自动机接受。
  • 上下文无关文法由四元组 G = (V, Σ, S, P) 形式化定义,其核心特征是产生式规则为 A → α 的形式,其中 A 是非终结符。
  • 通过生成 a^n b^n 语言的例子,我们实践了如何使用上下文无关文法进行推导,并理解了其比正则文法更强大的表达能力。

008:歧义文法 🔍

在本节课中,我们将学习歧义文法的概念。我们将了解什么是歧义文法,并通过一个具体的例子来理解它。

概述

如果一个文法对于同一个字符串,能够生成两个或更多个不同的最左推导树,那么这个文法就被称为歧义文法。上一节我们介绍了推导树,本节我们将具体探讨歧义性。

什么是歧义文法?

一个文法 G 被称为是歧义的,当且仅当存在至少一个字符串 ω,使得 ω 可以由 G 通过两个或更多个不同的最左推导树生成。

核心概念

  • 歧义性关注的是最左推导树的数量。
  • 不能将一个最左推导树和一个最右推导树视为导致歧义的情况。

示例解析

为了更好地理解,我们来看一个具体的例子。

考虑以下文法 G

  • 非终结符S
  • 终结符a, b, +, *
  • 产生式规则 P
    1. S → S + S
    2. S → S * S
    3. S → a
    4. S → b
  • 开始符号S

我们的目标是生成字符串 a + a * b。我们将展示这个字符串可以通过两种不同的最左推导方式生成,从而证明该文法是歧义的。

推导方法一

我们从开始符号 S 出发,选择产生式 S → S + S 开始最左推导。

以下是推导步骤:

  1. S ⇒ S + S (应用规则 S → S + S
  2. ⇒ a + S (对最左边的 S 应用规则 S → a
  3. ⇒ a + S * S (对剩余的 S 应用规则 S → S * S
  4. ⇒ a + a * S (对最左边的 S 应用规则 S → a
  5. ⇒ a + a * b (对剩余的 S 应用规则 S → b

这个推导过程对应一个特定的最左推导树。

推导方法二

我们再次从开始符号 S 出发,但这次选择产生式 S → S * S 开始最左推导。

以下是推导步骤:

  1. S ⇒ S * S (应用规则 S → S * S
  2. ⇒ S + S * S (对最左边的 S 应用规则 S → S + S
  3. ⇒ a + S * S (对最左边的 S 应用规则 S → a
  4. ⇒ a + a * S (对中间的 S 应用规则 S → a
  5. ⇒ a + a * b (对最右边的 S 应用规则 S → b

这个推导过程生成了同一个字符串 a + a * b,但对应的是另一个不同的最左推导树。

结论

由于字符串 a + a * b 可以通过上述两种不同的最左推导方式(即两棵不同的最左推导树)从文法 G 中生成,因此我们判定该文法 G 是歧义文法

总结

本节课我们一起学习了歧义文法的定义:如果一个文法能为同一个字符串生成多个最左推导树,它就是歧义的。我们通过一个包含 +* 运算的简单表达式文法示例,具体演示了如何验证文法的歧义性。理解歧义性对于设计编程语言的语法和解析器至关重要。

009:上下文无关文法与上下文无关语言

在本节课中,我们将学习上下文无关文法(CFG)及其生成的语言——上下文无关语言(CFL)。我们将了解它们与正则文法的区别,并通过具体例子理解其工作原理。

概述

上一节我们介绍了正则文法,它生成的语言是正则语言。本节中,我们将放宽文法的规则限制,引入上下文无关文法。我们将看到,这种更强大的文法可以描述一些非正则的语言。

什么是上下文无关文法(CFG)?

上下文无关文法是一种文法,其中所有规则都具有特定的形式。

以下是上下文无关文法的定义:

  • 规则形式:所有规则都形如 A -> X
  • 左侧A 是一个单一的变量。
  • 右侧X 可以是变量和终结符的任意组合,也可以是空串 ε

这意味着,在上下文无关文法中,我们总是用一个单一的变量来替换为任意长度的字符串(由变量和终结符组成)。规则的应用不依赖于变量周围的“上下文”,因此得名“上下文无关”。

从正则文法到上下文无关文法

所有正则文法都已经是上下文无关文法。因为正则文法的规则(如 A -> aBA -> ε)完全符合 CFG 的定义:左侧是单一变量,右侧是允许的组合。

因此,我们可以得出一个重要结论:每一个正则语言也都是一个上下文无关语言

然而,反过来则不成立。存在一些上下文无关语言不是正则语言。接下来,我们通过一个例子来理解这一点。

一个关键例子:语言

考虑以下文法:

S -> 0A | ε
A -> S1

其中 S 是起始变量。

让我们推导这个文法生成的语言。我们可以生成空串 ε。应用规则 S -> 0A,然后 A -> S1,得到 0S1。如果此时 S 再推导为 ε,我们就得到字符串 01。通过重复这个过程,我们可以生成 0011000111 等等。

实际上,这个文法生成的语言是 L = {0^n 1^n | n ≥ 0}。我们之前已经证明过,这个语言不是正则语言。但是,我们刚刚为它找到了一个文法。这个文法不是正则文法,因为它包含了 A -> S1 这种规则(右侧是“变量+终结符”的顺序,而非正则文法要求的“终结符+变量”)。

这个例子至关重要,它表明:

  1. 我们新定义的这类文法(CFG)比正则文法更强大。
  2. 语言 {0^n 1^n} 是一个上下文无关语言(CFL),但它不是正则语言。

上下文无关语言(CFL)的定义

基于以上讨论,我们给出正式定义:

  • 上下文无关语言(CFL) 是能被某个上下文无关文法(CFG) 生成的语言。

所以,{0^n 1^n} 是一个 CFL,因为我们为它找到了一个 CFG。同时,所有正则语言也都是 CFL。

实例:回文串语言的 CFG

让我们构建一个更复杂的例子:回文串语言。
定义字母表 {0, 1} 上的回文串语言 PAL 为:
PAL = { w ∈ {0,1} | w = w^R }*
其中 w^R 表示 w 的反转。例如,0110, 010, ε, 0, 1 都属于 PAL。

我们可以为 PAL 设计一个上下文无关文法。设计思路基于回文的递归结构:一个回文要么是空串,要么是单个字符,要么在两端具有相同的字符,并且中间的子串本身也是一个回文。

以下是 PAL 的一个 CFG:

S -> 0S0 | 1S1 | 0 | 1 | ε

其中 S 是起始变量。

让我们分析这个文法:

  • 基础情况:规则 S -> ε, S -> 0, S -> 1 生成最短的回文(空串和单个字符)。
  • 递归情况:规则 S -> 0S0S -> 1S1 表示,如果一个字符串 S 已经是回文,那么在它两边加上相同的字符(0或1)后,形成的新字符串仍然是回文。

这个文法巧妙地利用了 CFG 的递归描述能力。可以证明,PAL 也是一个非正则语言,但它是一个上下文无关语言。

总结

本节课中我们一起学习了:

  1. 上下文无关文法(CFG) 的定义:规则形式为 A -> X,左侧是单一变量,右侧是变量和终结符的任意组合。
  2. 上下文无关语言(CFL) 的定义:由某个 CFG 生成的语言。
  3. CFG 与正则文法的关系:所有正则文法都是 CFG,因此所有正则语言都是 CFL。但存在不是正则语言的 CFL(例如 {0^n 1^n})。
  4. CFG 的递归特性:通过像 S -> 0S0 这样的递归规则,CFG 能够描述许多具有嵌套或对称结构的语言,例如回文串语言。

通过引入上下文无关文法,我们扩展了形式文法描述语言的能力,为处理更复杂的语言结构(如编程语言中的括号匹配、嵌套语句)奠定了理论基础。

010:NP难图问题 - 团判定问题 🧩

在本节课中,我们将学习一个重要的NP难图问题——团判定问题。我们将从理解基本概念开始,逐步学习如何证明一个问题是NP难的。

概述

我们将首先介绍“团”在图论中的定义,然后区分决策问题与优化问题。接着,我们会解释什么是NP难图问题,并学习证明一个问题是NP难的通用方法。最后,我们将应用此方法,证明团判定问题是一个NP难问题。


什么是团?🔍

为了理解团,我们首先需要了解完全图。

一个完全图是指图中任意两个不同的顶点之间都恰好有一条边相连。例如,一个有4个顶点的完全图,其边数为 n * (n-1) / 2,其中 n 是顶点数。

则是指一个图的子图,并且这个子图本身是一个完全图。换句话说,团是原图中一个顶点集合,该集合中任意两个顶点之间都有边相连。

例如,在一个图中,可能存在大小为2、3或4的团。寻找图中最大的团是一个经典的计算问题。


决策问题 vs. 优化问题 ⚖️

在计算复杂性理论中,问题通常分为决策问题和优化问题。

  • 决策问题:这类问题的答案只有“是”或“否”。

    • 示例:判断一个给定的图中是否存在一个大小为 K 的团。
    • 问题形式:是否存在一个大小为 K 的团?
  • 优化问题:这类问题要求找出某个量的最大值或最小值。

    • 示例:找出给定图中最大的团的大小。
    • 问题形式:图中最大的团是多大?

团判定问题属于决策问题。如果一个决策问题是NP难的,那么其对应的优化问题通常也是NP难的。


如何证明一个问题是NP难的? 🛠️

上一节我们区分了问题的类型,本节我们来看看证明一个问题是NP难的通用流程。

证明一个新问题(记作 L2)是NP难的标准方法是使用“规约”。

  1. 选择一个已知的NP难问题:首先,选取一个已经被证明是NP难的问题(记作 L1),例如布尔可满足性问题。
  2. 进行多项式时间规约:展示如何将 L1 的任意一个实例,在多项式时间内转化为 L2 的一个实例。
  3. 证明等价性:证明 L1 实例的答案是“是”,当且仅当,其对应的 L2 实例的答案也是“是”。

如果上述步骤成立,那么因为 L1 是NP难的,且可以在多项式时间内转化为 L2,所以 L2 也必然是NP难的。这个过程可以表示为:L1 ≤_p L2

我们将使用这个方法来证明团判定问题是NP难的。


证明:团判定问题是NP难的 🧪

现在,我们应用上述方法,将已知的NP难问题——布尔可满足性问题,规约到团判定问题。

已知:布尔可满足性问题是NP难的。
目标:证明团判定问题是NP难的。

规约思路:对于一个给定的合取范式布尔公式 F,我们将构造一个图 G,使得 F 是可满足的,当且仅当 G 中存在一个大小为 k 的团,其中 kF 中子句的数量。

构造步骤

假设布尔公式 Fk 个子句:C1, C2, ..., Ck

  1. 创建顶点

    • 对于公式 F 中的每一个文字(即变量或其否定形式),如果它出现在子句 Ci 中,就为它创建一个顶点。顶点可以标记为 (literal, i)
    • 这意味着,同一个变量在不同子句中或以其否定形式出现时,会创建不同的顶点。
  2. 创建边

    • 连接分别来自不同子句 CiCj (i ≠ j) 的两个顶点 (u, i)(v, j),当且仅当满足以下条件:
      • uv 不是互斥的(即 u 不是 v 的否定,v 也不是 u 的否定)。

举例说明

考虑布尔公式:
F = (x1 ∨ x2) ∧ (¬x1 ∨ ¬x2) ∧ (x1 ∨ x3)
这里有3个子句(k=3)。

  1. 构造图
    • 子句1 (x1 ∨ x2):创建顶点 (x1,1)(x2,1)
    • 子句2 (¬x1 ∨ ¬x2):创建顶点 (¬x1,2)(¬x2,2)
    • 子句3 (x1 ∨ x3):创建顶点 (x1,3)(x3,3)

  1. 连接边

    • 连接所有来自不同子句且文字不互斥的顶点。例如,(x1,1) 可以连接 (¬x2,2)(x3,3),但不能连接 (¬x1,2)(因为互斥)。

    最终构造出的图 G 中,顶点 (x2,1)(¬x1,2)(x3,3) 两两相连,形成了一个大小为3的团。

  1. 等价性证明
    • 如果 F 可满足:则每个子句中至少有一个文字为真。从每个子句中选取一个为真的文字对应的顶点,这些顶点构成的集合在 G 中必然形成一个大小为 k 的团(因为它们来自不同子句且代表同时为真的赋值,故不会互斥)。
    • 如果 G 有一个大小为 k 的团:由于团中顶点必须来自 k 个不同的子句(同一子句内的顶点无边相连),且对应的文字不互斥,因此可以为这些文字赋真值,并扩展为 F 的一个满足赋值。

通过这个构造,我们将布尔可满足性问题的一个实例,在多项式时间内转化为了团判定问题的一个实例,并保证了答案的等价性。因此,布尔可满足性问题可以规约到团判定问题。


总结

本节课我们一起学习了以下内容:

  1. 的定义:一个图中完全连通的子图。
  2. 决策问题(回答是/否)与优化问题(求最大/最小值)的区别。团判定问题属于决策问题。
  3. 证明一个问题是NP难的通用方法:通过多项式时间规约,将一个已知的NP难问题转化为目标问题。
  4. 团判定问题是NP难的证明:我们演示了如何将NP难的布尔可满足性问题,通过构造特定图的方式,规约到团判定问题,从而证明了后者也是NP难的。

需要指出的是,我们证明的是团判定问题是 NP难 的。要证明它是 NP完全 的,还需要证明它本身属于NP类(即存在一个非确定性图灵机可以在多项式时间内验证其解)。不过,团判定问题已知是一个经典的NP完全问题。

011:轻松掌握CYK算法(语法分析)⚙️

在本节课中,我们将要学习CYK算法。这是一种用于判断一个给定字符串是否能由某个上下文无关文法(CFG)生成的解析算法。我们将通过一个具体的例子,一步步演示如何构建CYK解析表,并最终判断字符串是否属于该文法定义的语言。

概述

CYK算法的核心思想是动态规划。它通过一个表格,自底向上地计算字符串所有子串可以由文法的哪些非终结符推导出来。我们将从一个简单的字符串开始,逐步分析更长的子串。

算法步骤详解

第一步:分析单个字符

首先,我们处理字符串中的每一个单个字符。对于字符串中的每个字符,我们查看文法中哪些非终结符可以直接推导出这个终结符

以下是字符串“B A A B A”中每个字符对应的可能非终结符:

  • 字符 B 可以由非终结符 B 推导。
  • 字符 A 可以由非终结符 AC 推导。
  • 字符 A 可以由非终结符 AC 推导。
  • 字符 B 可以由非终结符 B 推导。
  • 字符 A 可以由非终结符 AC 推导。

因此,在表格的第一行(长度为1的子串),我们填入对应的非终结符集合。

第二步:分析长度为2的子串

上一节我们分析了单个字符,本节中我们来看看如何分析长度为2的子串。我们不再看原始字符,而是看两个字符的组合。

对于子串“BA”,我们需要考虑其所有可能的拆分方式(即“B”和“A”的组合)。我们取第一个字符的可能非终结符集合({B})和第二个字符的可能非终结符集合({A, C})的笛卡尔积

以下是计算子串“BA”可能推导出的非终结符的步骤:

  • 组合 BA 得到 BA。在文法中查找,BA 可以由非终结符 A 推导。
  • 组合 BC 得到 BC。在文法中查找,BC 可以由非终结符 S 推导。
  • 因此,子串“BA”可以由非终结符 {A, S} 推导。

我们按照这个方法,计算所有长度为2的子串(“AA”, “AB”, “BA”)的可能非终结符,并填入表格第二行。

第三步:分析长度为3的子串

现在,我们开始分析长度为3的子串。对于长度为3的子串,如“BAA”,其拆分方式更多。我们需要考虑所有可能的二元拆分:(B, AA)(BA, A)

以下是分析子串“BAA”的过程:

  • 对于拆分 (B, AA):取“B”的非终结符集合 {B} 和子串“AA”的非终结符集合(从第二步可知为 {B}),做笛卡尔积得到 {BB}。在文法中查找,没有规则能推导出 BB
  • 对于拆分 (BA, A):取子串“BA”的非终结符集合 {A, S} 和“A”的非终结符集合 {A, C},做笛卡尔积得到 {AA, AC, SA, SC}。在文法中查找这些组合,发现没有规则能推导出它们。
  • 由于所有可能的拆分都无法在文法中找到对应的规则,因此在表格中对应“BAA”的位置填入“-”(表示空集)。

我们重复此过程,分析所有长度为3的子串(“AAB”, “ABA”)。

第四步:分析长度为4及以上的子串

随着子串长度增加,原理是相同的。对于长度为4的子串“BAAB”,我们需要考虑三种拆分:(B, AAB), (BA, AB), (BAA, B)

以下是关键步骤:

  • 我们查找之前步骤中已计算的子串“AAB”、“BA”、“AB”、“BAA”的非终结符集合。
  • 对每一种拆分,取两部分非终结符集合的笛卡尔积。
  • 在文法规则中查找这些组合。如果某个组合(如 BC)存在于文法中(即 S -> BC),则将左侧的非终结符(S)加入当前子串的集合。
  • 如果所有拆分得到的组合都不在文法中,则填入“-”。

我们继续此过程,直到处理完整个字符串(长度为5的子串)。

核心概念与表示

在CYK算法中,我们频繁地进行集合操作和规则查找。核心操作可以表示为:

笛卡尔积与规则查找
对于子串 s[i:j],我们检查所有拆分点 k (i < k < j)。如果存在非终结符 B 属于 s[i:k] 的集合,C 属于 s[k:j] 的集合,并且文法中存在规则 A -> BC,那么非终结符 A 就属于子串 s[i:j] 的集合。

这可以用伪代码表示:

for length from 2 to n: // 子串长度
    for i from 0 to n-length: // 子串起始位置
        for k from 1 to length-1: // 拆分点
            for each rule A -> B C in grammar:
                if B in table[i][i+k] and C in table[i+k][i+length]:
                    add A to table[i][i+length]

判断结果

完成整个表格的填充后,我们检查表格的最后一行最后一列(代表整个字符串)的集合中是否包含文法的起始符号(通常是 S)。

  • 如果包含 S,则该字符串可以由该文法生成。
  • 如果不包含 S,则该字符串不能被该文法生成。

总结

本节课中我们一起学习了CYK算法的完整流程。我们从分析单个字符开始,逐步合并,利用动态规划的思想,通过查找文法规则来判断更长子串的派生可能性。算法的关键在于理解如何通过已计算的较短子串结果,组合并查找文法规则来推导较长子串的结果。最终,通过检查表格顶端单元格是否包含起始符号,我们就能判定整个字符串是否属于该文法语言。

(图示:CYK算法解析表示例)

(图示:用于演示的上下文无关文法规则)

012:非确定性有限自动机 (NFA) 🧠

在本节课中,我们将开始学习一个新的主题:非确定性有限自动机,简称 NFA。我们将通过对比之前学过的确定性有限自动机来理解 NFA 的核心概念和特点。

回顾:确定性有限自动机 (DFA)

为了理解 NFA,我们首先需要回顾一下 DFA 的特性,这将帮助我们区分两者。

确定性有限自动机中的“确定性”一词源于“决定论”。这意味着,在 DFA 中,给定当前状态,我们可以确定地知道下一个状态是什么。

让我们参考一个之前讨论过的 DFA 图例。假设我们关注状态 A。在 DFA 中:

  • 如果状态 A 收到输入 1,它必定会转移到状态 B,而不会去任何其他状态。
  • 如果状态 A 收到输入 0,它必定会转移到状态 C。

公式化描述:对于 DFA 中的任意状态 q 和任意输入符号 a,转移函数 δ(q, a) 唯一确定一个下一个状态。

因此,在 DFA 中,每个状态对于每个可能的输入,都只有一个唯一且确定的下一状态。没有其他选择或随机性。这种特性使得 DFA 的设计相对简单直观,因为我们只需明确指定每个状态在每种输入下的唯一转移目标。

引入:非确定性有限自动机 (NFA)

现在,让我们来看看非确定性有限自动机。顾名思义,它的核心在于“非确定性”。

在 NFA 中,情况有所不同。给定当前状态,对于同一个输入,可能存在多个下一状态

观察提供的 NFA 图例,我们关注状态 A

  • 当状态 A 收到输入 0 时,它可以选择保持在状态 A 或者转移到状态 C。
  • 当状态 A 收到输入 1 时,它可以选择转移到状态 B 或者转移到状态 D。

核心概念:在 NFA 中,转移函数 δ(q, a) 的结果是一个状态的集合,而不仅仅是单个状态。这意味着存在多个可能的选择。

那么,当存在多个选择时,机器如何决定去往哪个状态呢?NFA 的定义允许两种理解方式:

  1. 随机选择:下一个状态可以从可能的状态集合中随机选取。
  2. 并行计算:机器可以同时“分裂”并探索所有可能的下一状态路径。

此外,NFA 引入了一个 DFA 中没有的新概念:ε-转移。在图中,从状态 D 到状态 E 的边上标有 ε

代码/公式描述ε 代表空字符串。δ(q, ε) 表示状态 q 可以在不消耗任何输入符号的情况下,直接转移到另一个状态。

以下是 NFA 与 DFA 的关键区别总结:

  • 状态转移:DFA 是确定性的(唯一下一状态);NFA 是非确定性的(多个可能下一状态)。
  • ε-转移:DFA 不允许;NFA 允许。
  • 设计复杂性:通常,描述同一种语言时,NFA 的设计可能比 DFA 更简单、更紧凑。

总结

本节课中,我们一起学习了非确定性有限自动机的基本概念。我们首先回顾了 DFA 的确定性特性,然后通过与 DFA 对比,引出了 NFA 的非确定性特性,即一个状态对同一输入可以有多个转移目标。我们还介绍了 NFA 特有的 ε-转移,它允许在不读取输入的情况下进行状态转换。理解 NFA 是学习计算理论中更复杂模型的重要一步。在接下来的课程中,我们将通过更多例子来加深对 NFA 的理解。

013:从NFA到DFA的转换

在本节课中,我们将学习如何将非确定性有限自动机转换为等价的确定性有限自动机。我们将通过一个具体的例子,详细讲解转换的步骤和原理。

概述

在之前的课程中,我们学习了确定性有限自动机和NFA。本节课我们将探讨两者之间的关系,并学习如何将任何一个NFA转换为一个等价的DFA。

NFA与DFA的关系

上一节我们介绍了DFA和NFA的定义。本节中我们来看看它们之间的核心联系。

每个DFA都是一个NFA,但反之则不成立。这意味着DFA是NFA的一个子集。然而,对于每一个NFA,都存在一个等价的DFA。也就是说,我们可以将任何NFA转换为一个接受相同语言的DFA。

如何证明这一点呢?关键在于对比两者的形式化定义。DFA和NFA都由一个五元组定义,其中只有一个元组不同,即状态转移函数 δ。

  • 对于DFA,转移函数是:δ: Q × Σ → Q
  • 对于NFA,转移函数是:δ: Q × Σ → P(Q) (其中P(Q)是Q的幂集)

观察这两个公式,可以发现DFA的转移结果(Q中的单个状态)包含在NFA的转移结果(Q的子集,即P(Q)的元素)之中。因此,每个DFA都可以被视为一个特殊的NFA。但反过来,一个能将状态转移到多个可能状态的NFA,并不直接符合DFA的定义。这就是我们需要进行转换的原因。

转换示例:设计一个NFA

为了演示转换过程,我们首先需要有一个NFA。让我们来设计一个。

假设语言 L 是所有以0开头的、由{0,1}组成的字符串的集合。其输入字母表 Σ 为 {0, 1}。

以下是该语言的一个NFA设计:

  • 起始状态为 A。
  • 在状态 A 读取输入 0 时,转移到状态 B。
  • 状态 B 是接受状态。
  • 在状态 B,无论读取 0 还是 1,都保持在状态 B。
  • 在状态 A 读取输入 1 时,转移未定义(进入“死亡”配置)。

我们可以用下面的状态转移表来描述这个NFA:

状态 输入 0 输入 1
A B φ
B B B

(注:φ 表示空集,即没有定义转移,这是一个“死亡”配置。)

将NFA转换为DFA

现在,我们将上一步得到的NFA状态转移表转换为等价的DFA状态转移表。

在DFA中,每个状态对每个输入符号都必须有且仅有一个转移。因此,我们需要处理NFA表中的“死亡”配置(φ)。

以下是转换步骤:

  1. 处理未定义转移:在NFA中,状态A在输入1时转到φ。在DFA中,这需要被替换为一个明确的“死亡状态”(或称“陷阱状态”),我们称之为状态C。一旦进入状态C,无论输入什么符号,都将停留在C。
  2. 复制确定转移:状态A在输入0时转到B,状态B在输入01时都转到B自身。这些转移在DFA中保持不变。
  3. 补充死亡状态转移:我们需要明确定义新状态C的转移:在输入01时,都停留在C。

应用这些规则后,我们得到以下DFA状态转移表:

状态 输入 0 输入 1
A B C
B B B
C C C

绘制DFA状态图

根据上面的DFA状态转移表,我们可以绘制出其状态转移图。

     0,1
    ┌───┐
    │   ▼
┌───┐  0   ┌───┐
│ A │ ───> │ B │
└───┘      └───┘
  │          ^
  │ 1        │
  ▼          │
┌───┐      │
│ C │ <────┘
└───┘      0,1
  • A 是起始状态。
  • B 是接受状态(用双圈表示)。
  • C 是死亡状态。
  • 从A出发,输入0则进入B,输入1则进入C。
  • 从B出发,输入01都保持在B。
  • 从C出发,输入01都保持在C。

检查此DFA:每个状态(A, B, C)对于每个输入符号(0, 1)都有明确定义的转移,因此它是一个完整的DFA。它所接受的语言正是“所有以0开头的01字符串”,与最初的NFA等价。

总结

本节课中我们一起学习了从NFA到DFA的转换。我们首先回顾了NFA与DFA在定义上的核心区别在于转移函数。然后,我们通过一个具体例子,演示了转换的三个关键步骤:处理未定义转移(引入死亡状态)、复制确定转移、以及补全新状态的转移规则。最终,我们成功地将一个NFA转换成了一个接受相同语言的、完整的DFA。

014:CYK算法详解 🧮

在本节课中,我们将学习CYK算法。这是一种成员资格算法,用于判断一个给定的输入字符串是否属于某个上下文无关文法所生成的语言。我们将从算法的基本概念开始,逐步讲解其工作原理和具体执行步骤。

概述

CYK算法是一种用于判断输入字符串是否属于某个上下文无关文法的成员资格算法。该算法要求文法必须为乔姆斯基范式。我们将通过构建一个动态规划表来系统地检查输入字符串是否能从文法的起始符号推导出来。

乔姆斯基范式

上一节我们介绍了CYK算法的用途,本节中我们来看看它对文法的要求。CYK算法要求输入的上下文无关文法必须表示为乔姆斯基范式

在乔姆斯基范式中,所有产生式必须符合以下两种形式之一:

  1. 一个变量推导出两个变量: A → BC
  2. 一个变量推导出一个终结符: A → a

以下是其一般格式的表示:

所有产生式应为:变量 → 两个变量 或 变量 → 单个终结符

算法输入与表格结构

了解了文法格式后,我们来看看算法的输入和核心数据结构。算法的输入包括一个乔姆斯基范式的文法和一个输入字符串 W

输入字符串 W 由一系列终结符组成:W = x1 x2 ... xn。其中 n 代表输入字符串的长度。

基于输入长度 n,算法需要构建一个 n x n 的动态规划表。表格的横轴用 i 表示,纵轴用 j 表示。每个单元格记为 T[i][j],其中 ij 的取值范围都是从1到 n

例如,当 n=4 时,表格结构如下:

i=1,2,3,4
j=1,2,3,4

每个单元格 T[i][j] 将用于存储能够推导出输入字符串中某个特定子串的所有变量的集合。

表格填充规则与步骤

现在我们已经有了表格,接下来看看如何填充它。填充过程自底向上进行,从单个字符开始,逐步合并更长的子串。

当 j = 1 时:单元格 T[i][1] 表示输入中第 i 个字符的直接推导。我们需要在文法中查找所有能直接推导出该终结符的变量,并将其填入单元格。如果没有任何变量能推导出该字符,则单元格为空(可用“-”表示)。

当 j > 1 时:单元格 T[i][j] 表示从第 i 个字符开始、长度为 j 的子串的推导。其值通过组合更短的子串推导结果得到。具体来说,对于所有可能的拆分点 k(从1到 j-1),我们考虑将子串拆分为两部分:第一部分是 T[i][k](长度为 k),第二部分是 T[i+k][j-k](长度为 j-k)。然后,我们检查文法中是否存在形如 A → BC 的产生式,其中 B 属于 T[i][k]C 属于 T[i+k][j-k]。如果存在,则将变量 A 加入 T[i][j]

最终,我们检查表格最右上角的单元格 T[1][n]。如果该单元格中包含文法的起始符号 S,则说明输入字符串可以从 S 推导出来,即输入属于该文法;否则不属于。

算法实例演示

理论部分已经介绍完毕,本节我们将通过一个具体例子来完整演示CYK算法的执行过程。

假设我们有以下符合乔姆斯基范式的文法:

  • S → AB | BC
  • A → BA | a
  • B → CC | b
  • C → AB | a

我们要判断的输入字符串是:W = b a a b,长度为 n=4

首先,我们构建一个4x4的表格,并将输入字符写在底部对应位置。

步骤1:填充 j=1 的列(直接推导)
以下是查找直接推导并填充表格的过程:

  • T[1][1] 对应字符 b。文法中能直接推出 b 的变量是 B。所以填入 {B}
  • T[2][1] 对应字符 a。文法中能直接推出 a 的变量是 AC(根据 A → aC → a)。所以填入 {A, C}
  • T[3][1] 对应字符 a。同上,填入 {A, C}
  • T[4][1] 对应字符 b。同 T[1][1],填入 {B}

步骤2:填充 j=2 的列(长度为2的子串)
以下是计算长度为2的子串推导的过程:

  • T[1][2] 对应子串 ba(即 W[1]W[2])。我们需要考虑所有拆分点 k=1。
    • 组合 T[1][1] ({B}) 和 T[2][1] ({A, C}),得到可能的变量对:BA, BC
    • 在文法中查找:S → BC 匹配 BC。因此,将 S 填入 T[1][2]
  • T[2][2] 对应子串 aaW[2]W[3])。
    • 组合 T[2][1] ({A, C}) 和 T[3][1] ({A, C}),得到可能的变量对:AA, AC, CA, CC
    • 在文法中查找:B → CC 匹配 CC。因此,将 B 填入 T[2][2]
  • T[3][2] 对应子串 abW[3]W[4])。
    • 组合 T[3][1] ({A, C}) 和 T[4][1] ({B}),得到可能的变量对:AB, CB
    • 在文法中查找:S → AB 匹配 AB。因此,将 S 填入 T[3][2]

步骤3:填充 j=3 的列(长度为3的子串)
以下是计算长度为3的子串推导的过程:

  • T[1][3] 对应子串 baaW[1]W[2]W[3])。需要考虑拆分点 k=1 和 k=2。
    • k=1: 组合 T[1][1] ({B}) 和 T[2][2] ({B}),得到 BB。文法中无匹配。
    • k=2: 组合 T[1][2] ({S}) 和 T[3][1] ({A, C}),得到 SA, SC。文法中无匹配。
    • 因此,T[1][3] 为空 {}-
  • T[2][3] 对应子串 aabW[2]W[3]W[4])。
    • k=1: 组合 T[2][1] ({A, C}) 和 T[3][2] ({S}),得到 AS, CS。文法中无匹配。
    • k=2: 组合 T[2][2] ({B}) 和 T[4][1] ({B}),得到 BB。文法中无匹配。
    • 因此,T[2][3] 也为空 {}

步骤4:填充 j=4 的列(整个字符串)
最后,我们计算整个字符串 baab 的推导,即 T[1][4]

  • 需要考虑拆分点 k=1, 2, 3。
  • k=1: 组合 T[1][1] ({B}) 和 T[2][3] ({}),结果为空。
  • k=2: 组合 T[1][2] ({S}) 和 T[3][2] ({S}),得到 SS。文法中无匹配。
  • k=3: 组合 T[1][3] ({}) 和 T[4][1] ({B}),结果为空。
  • 因此,T[1][4] 为空 {}

由于 T[1][4] 中不包含起始符号 S,我们得出结论:输入字符串 baab 不属于 该文法生成的语言。

总结

本节课中我们一起学习了CYK算法。我们首先了解了它是一种针对乔姆斯基范式文法的成员资格判定算法。接着,我们学习了如何根据输入字符串构建动态规划表,并详细讲解了自底向上填充表格的规则:从单个字符的直接推导开始,逐步合并以处理更长的子串。最后,通过一个完整的实例演示,我们实践了算法的每一步。判断的最终依据是表格最右上角的单元格是否包含文法的起始符号。CYK算法通过这种系统化的方式,能够有效地判定一个字符串是否由给定文法生成。

015:CYK算法如何工作?动态规划!

在本节课中,我们将学习CYK算法。这是一种用于判断一个给定的上下文无关文法是否能生成某个特定字符串的算法。其核心思想是使用动态规划技术,自底向上地解决问题。

概述

CYK算法是算法领域与计算理论领域的一个有趣交叉。它利用动态规划来解决上下文无关文法的成员资格问题,即判断一个字符串是否能由给定的文法生成。为了应用此算法,文法必须首先转换为乔姆斯基范式。

上下文无关文法基础

在深入算法之前,我们先回顾上下文无关文法的基本组成部分。一个上下文无关文法由四个要素构成:

  • V:变量集合。
  • Σ:终结符集合,可以理解为构成字符串的字母表。
  • R:规则集合。
  • S:起始变量。

文法的目标是生成字符串。我们从起始变量 S 开始,反复应用规则,将变量替换为规则右侧的符号串(可以是变量和终结符的混合),最终希望能得到我们想要的、完全由终结符组成的字符串。如果存在这样的规则应用序列,我们就说文法生成了该字符串。

规则的一般形式是 A → X,其中 A 是一个变量,X 是由变量和终结符组成的串。

乔姆斯基范式

为了使CYK算法有效工作,我们需要将文法转换为乔姆斯基范式。在CNF中,只允许三种类型的规则:

  1. S → ε (只有起始变量 S 可以直接生成空串 ε)
  2. A → a (一个变量生成单个终结符)
  3. A → BC (一个变量生成两个变量,且 BC 不能是起始变量)

一个重要的推论是:任何非起始变量最终都必须生成一个非空字符串。此外,可以证明,如果一个长度为 n 的字符串能被一个CNF文法生成,那么恰好需要 2n - 1 步规则应用。

算法核心思想:动态规划

现在,我们来看如何判断文法 G 是否能生成字符串 W = w1 w2 ... wn

如果字符串长度为1,我们只需检查是否存在规则 S → w1(或经过变量推导)。这很简单。

对于长度至少为2的字符串,要生成它,起始步骤必须应用 A → BC 这类规则。这意味着字符串 W 被分割成两部分:前半部分由 B 生成,后半部分由 C 生成。

因此,问题“S 能否生成整个字符串 W?”被转化为两个子问题:“变量 B 能否生成子串 w1...wi?”和“变量 C 能否生成子串 wi+1...wn?”。

这里的关键是,我们不知道正确的分割点 i 在哪里。如果使用简单的递归尝试所有可能的分割,效率会非常低,因为子问题之间没有共享信息。

自底向上的动态规划解法

CYK算法采用动态规划,自底向上地构建解。我们不再从整个字符串开始分割,而是从小子串开始,逐步组合。

我们定义一个更通用的问题:对于任意下标 ij(1 ≤ i ≤ j ≤ n),判断哪些变量可以生成子串 wi ... wj

以下是算法的步骤框架:

  1. 初始化:处理所有长度为1的子串(即单个字符)。对于每个位置 i,找出所有能直接生成终结符 wi 的变量 A(即存在规则 A → wi)。这对应CNF中的第二类规则。

  2. 递推计算:依次计算长度 L 从 2 到 n 的所有子串。

    • 对于每个长度为 L 的子串,其起始下标 i 从 1 到 n-L+1,则结束下标 j = i + L - 1
    • 对于该子串,我们尝试所有可能的分割点 k(i ≤ k < j),将其分为两部分:wi...wkwk+1...wj
    • 对于文法中每一条 A → BC 形式的规则,我们检查:是否已经计算出变量 B 能生成第一部分 wi...wk,并且变量 C 能生成第二部分 wk+1...wj
    • 如果存在这样的 k 和规则 A → BC 满足条件,那么我们就知道变量 A 能够生成子串 wi...wj
  3. 得到答案:在完成所有计算后,我们检查在用于表示整个字符串 w1...wn 的子串(即 i=1, j=n)对应的可生成变量集合中,是否包含起始变量 S。如果包含,则原文法能生成该字符串;否则不能。

算法复杂度

算法需要三层循环:

  • 第一层循环子串长度 L(O(n))。
  • 第二层循环子串起始位置 i(O(n))。
  • 第三层循环分割点 k(O(n))。
    对于每个 (i, j, k) 组合,我们需要检查文法中的所有 A → BC 型规则(假设文法大小为常数,或记为 |G|)。

因此,CYK算法的总时间复杂度为 O(n³ * |G|)。对于固定的文法,可以简化为 O(n³)

总结

本节课我们一起学习了CYK算法。该算法通过将上下文无关文法转换为乔姆斯基范式,并运用动态规划技术,自底向上地计算每个子串可以由哪些变量生成,从而高效地解决了文法成员资格问题。其核心在于将大问题分解为重叠的子问题,并利用表格存储子问题的解,避免了重复计算,最终在多项式时间内(O(n³))给出答案。

计算模型:16:多终态DFA到正则表达式的转换 🔄

在本节课中,我们将学习如何为一个具有多个终态的确定性有限自动机设计正则表达式。在此之前,我们已经掌握了为单终态DFA设计正则表达式的方法。本节将重点介绍处理多终态DFA的通用流程。

核心思路:当DFA有多个终态时,我们需要分别找出从起始状态到达每一个终态所接受的语言对应的正则表达式,最后对这些表达式取并集,即可得到整个DFA对应的正则表达式。


构建状态方程

首先,我们为DFA中的每一个状态建立一个方程。方程描述了“如何到达该状态”。对于状态 Q,其方程形式通常为:
Q = (所有进入Q的路径的表达式之和)

观察给定的DFA,我们有三个状态:Q1Q2Q3,其中 Q1Q2 是终态。

以下是每个状态对应的方程:

  1. 状态 Q1 的方程:进入 Q1 的路径有两条。一条是从起始状态直接进入(用 ε 表示),另一条是从 Q1 自身通过输入 0 循环进入。
    Q1 = ε + Q1·0 —— (方程1)

  2. 状态 Q2 的方程:进入 Q2 的路径有两条。一条是从 Q1 通过输入 1 进入,另一条是从 Q2 自身通过输入 1 循环进入。
    Q2 = Q1·1 + Q2·1 —— (方程2)

  3. 状态 Q3 的方程:进入 Q3 的路径有三条。分别是从 Q2 通过输入 0 进入,以及从 Q3 自身通过输入 01 循环进入。
    Q3 = Q2·0 + Q3·0 + Q3·1 —— (方程3)


求解终态的正则表达式

现在,我们分别求解两个终态 Q1Q2 的正则表达式。

求解终态 Q1

我们使用方程1:Q1 = ε + Q1·0
这个方程符合 阿登定理 (Arden‘s Theorem) 的形式:R = Q + R·P,其解为 R = Q·P*

  • 其中 R 对应 Q1
  • Q 对应 ε
  • P 对应 0

应用阿登定理:
Q1 = ε · 0*
根据正则表达式恒等式 ε·R = R,可得:
Q1 = 0* —— (方程4)

求解终态 Q2

我们使用方程2:Q2 = Q1·1 + Q2·1
首先,将刚刚求得的 Q1 的值(方程4)代入:
Q2 = (0*)·1 + Q2·1 —— (方程5)

方程5同样符合阿登定理 R = Q + R·P 的形式:

  • R 对应 Q2
  • Q 对应 0*·1
  • P 对应 1

应用阿登定理:
Q2 = (0*·1) · 1*
这可以简写为:
Q2 = 0*·1·1* —— (方程6)


合并终态表达式

我们已经得到了分别到达两个终态的正则表达式:

  • 到达 Q10*
  • 到达 Q20*·1·1*

整个DFA接受的语言,是能被 至少一个终态 接受的字符串的集合,因此需要取这两个表达式的并集(用 + 表示):
正则表达式 = 0* + 0*·1·1*

我们可以对这个表达式进行化简,提取公因子 0*
正则表达式 = 0* · (ε + 1·1*)

根据正则表达式恒等式 ε + R·R* = R*(其中 R1),括号内的部分 (ε + 1·1*) 就等于 1*
因此,最终化简后的正则表达式为:
正则表达式 = 0* · 1*


总结

本节课中,我们一起学习了为具有多个终态的DFA构造正则表达式的方法。关键步骤如下:

  1. 列出方程:为每个状态建立方程,描述所有进入该状态的路径。
  2. 逐个求解:利用阿登定理,分别求解出从起始状态到达每一个终态的正则表达式。
  3. 取并集:将求得的各个终态的正则表达式用并集运算符 (+) 连接起来,得到整个DFA的正则表达式。
  4. 化简(可选):利用正则表达式的恒等式对结果进行化简,使其更简洁。

处理多终态DFA的核心在于,分别处理每个终态,最后合并结果。只要掌握了阿登定理和基本的并集操作,这个过程就变得清晰而直接。

017:从DFA到正则表达式的转换 🔄

在本节课中,我们将学习如何为一个给定的确定有限自动机(DFA)找到对应的正则表达式。我们将通过一个具体的例子,逐步演示转换过程。

概述

我们将分析一个包含四个状态(Q1, Q2, Q3, Q4)的DFA,其中Q1既是初始状态也是最终状态。输入字母表为 {a, b}。我们的目标是为这个DFA推导出等价的正则表达式。

上一节我们介绍了从DFA构建方程的基本思想,本节中我们来看看如何具体应用这些步骤来求解。

步骤一:为每个状态建立方程

首先,我们需要为DFA中的每一个状态写出一个方程。方程描述了到达该状态的所有可能路径。

以下是建立方程的过程:

  1. 状态 Q1 的方程:Q1是初始状态和最终状态。到达Q1的路径包括:从“无”状态(即初始状态本身)的ε转移、从Q2通过输入b的转移,以及从Q3通过输入a的转移。

    • Q1 = ε + Q2·b + Q3·a (方程1)
  2. 状态 Q2 的方程:到达Q2的唯一路径是从Q1通过输入a。

    • Q2 = Q1·a (方程2)
  3. 状态 Q3 的方程:到达Q3的唯一路径是从Q1通过输入b。

    • Q3 = Q1·b (方程3)
  4. 状态 Q4 的方程:到达Q4的路径包括:从Q2通过输入a、从Q3通过输入b,以及从Q4自身通过输入a或b。

    • Q4 = Q2·a + Q3·b + Q4·a + Q4·b (方程4)

步骤二:代入与简化方程

由于我们的最终目标是得到代表最终状态Q1的正则表达式,我们将把其他状态的表达式代入方程1。

首先,将方程2和方程3代入方程1中的Q2和Q3:
Q1 = ε + (Q1·a)·b + (Q1·b)·a

简化后得到:
Q1 = ε + Q1·(ab + ba)

现在,我们得到了一个关于Q1的递归方程。这个方程符合一个标准形式。

步骤三:应用阿登引理

我们得到的方程 Q1 = ε + Q1·(ab + ba) 符合 阿登引理 的标准形式:
R = Q + R·P

其中:

  • R 对应 Q1
  • Q 对应 ε
  • P 对应 (ab + ba)

根据阿登引理,该方程的解为:
R = Q·P*

将我们的变量代入,得到:
Q1 = ε·(ab + ba)*

步骤四:应用恒等律得到最终表达式

根据正则表达式的恒等律,ε·R = R。因此,我们可以进一步简化:
Q1 = (ab + ba)*

总结

本节课中我们一起学习了如何为一个给定的DFA推导正则表达式。我们回顾了关键步骤:为每个状态建立方程,将非最终状态的表达式代入最终状态的方程,利用阿登引理解递归方程,并最终应用恒等律进行简化。对于本例中的DFA,我们得到的等价正则表达式是 (ab + ba)*。这个表达式描述了所有由交替的 abba 对组成的字符串(包括空串ε)。

018:谓词逻辑入门 🧠

在本节课中,我们将要学习谓词逻辑,也称为量化逻辑。这是一种比命题逻辑更强大的逻辑系统,它允许我们表达和推理关于对象及其属性的陈述。我们将学习其基本语法、核心概念(量词)以及如何用形式化的方式表达日常论证。

概述:为什么需要谓词逻辑?

在命题逻辑中,每个陈述(如“所有人都是会死的”)被视为一个不可分割的整体(一个命题)。这使得我们无法分析其内部结构,从而无法形式化某些推理。

例如,考虑经典的“苏格拉底论证”:

  1. 所有人都是会死的。
  2. 苏格拉底是人。
  3. 因此,苏格拉底是会死的。

在命题逻辑中,我们只能将其表示为 M(所有人都是会死的),S(苏格拉底是人),并试图推导出 I(苏格拉底是会死的)。但 MSI 之间没有逻辑联系,因此无法进行有效推理。

谓词逻辑通过引入谓词(描述对象属性的符号)和量词(描述属性适用范围的符号)来解决这个问题,使我们能够拆解和分析陈述的内部结构。

谓词逻辑的语法

上一节我们看到了命题逻辑的局限性,本节中我们来看看谓词逻辑的基本构建块。

以下是谓词逻辑的核心语法元素:

  • 谓词:表示对象属性的符号。我们使用大写字母 A, B, C, ..., Z 来表示。例如,D(x) 可以表示“x 是一只狗”,B(x) 表示“x 是蓝色的”。
  • :指代对象的符号。
    • 常量:指代特定、不变的对象,用小写字母 a, b, c, ..., t 表示。例如,s 可以代表“苏格拉底”。
    • 变量:指代泛型或未知的对象,用 x, y, z, u, v, w 表示。例如,在“对于所有 x...”中,x 就是一个变量。
  • 连接词:与命题逻辑相同,包括否定 ¬、合取 、析取 、蕴含 和双蕴含
  • 量词:这是谓词逻辑新增的核心概念。
    • 全称量词 :读作“对于所有...”。∀x 表示“对于论域中的所有对象 x”。
    • 存在量词 :读作“存在...”。∃x 表示“在论域中存在至少一个对象 x”。

如何使用量词:全称量词

理解了基本语法后,我们来看看如何用全称量词 来表达“所有...都...”这类陈述。

核心规则是:当使用全称量词 时,我们通常使用蕴含 来连接前提和结论

公式∀x (P(x) → Q(x))

这个公式的意思是:“对于论域中的每一个对象 x,如果 x 具有属性 P,那么 x 也具有属性 Q。” 这精确地捕捉了“所有 P 都是 Q”的含义。

让我们用例子来说明:

  • 陈述:所有狗都是蓝色的。
  • 形式化
    • D(x) 表示“x 是一只狗”。
    • B(x) 表示“x 是蓝色的”。
    • 形式化为:∀x (D(x) → B(x))
  • 解释:我们在论域中任意选取一个对象 x。如果它碰巧是一只狗(D(x) 为真),那么根据这个陈述,我们保证它一定是蓝色的(B(x) 也为真)。如果 x 不是狗,则这个陈述对 x 没有要求。

再看一个更复杂的例子:

  • 陈述:所有男人都喜欢蛋糕和派。
  • 形式化
    • M(x) 表示“x 是一个男人”。
    • L(x, y) 表示“x 喜欢 y”。
    • c 代表“蛋糕”,p 代表“派”(它们是常量)。
    • 形式化为:∀x (M(x) → (L(x, c) ∧ L(x, p)))
  • 解释:对于任何对象 x,如果 x 是一个男人,那么 x 喜欢蛋糕 并且 x 喜欢派。

如何使用量词:存在量词

上一节我们介绍了全称量词,本节中我们来看看存在量词 的用法。

核心规则是:当使用存在量词 时,我们通常使用合取 来连接各个属性

公式∃x (P(x) ∧ Q(x))

这个公式的意思是:“在论域中,存在至少一个对象 x,使得 x 具有属性 P 并且 x 具有属性 Q。” 这精确地捕捉了“有些 P 是 Q”的含义。

让我们用例子来说明:

  • 陈述:有些狗是蓝色的。
  • 形式化∃x (D(x) ∧ B(x))
  • 解释:我们声称在世界上存在那么一只(或多只)狗,它同时满足“是狗”和“是蓝色”这两个属性。注意,这并不排除存在非蓝色的狗。

为什么这里用 而不是 ?如果用 ∃x (D(x) → B(x)),只要找到一个不是狗的对象(使得 D(x) 为假),整个蕴含式就为真,这会导致“有些狗是蓝色的”这个陈述在“存在不是狗的东西”的世界里被误判为真,这显然不符合直觉。

再看一个例子:

  • 陈述:有些男人喜欢蛋糕和派。
  • 形式化∃x (M(x) ∧ L(x, c) ∧ L(x, p))
  • 解释:存在至少一个对象 x,x 是一个男人,并且 x 喜欢蛋糕,并且 x 喜欢派。

为什么选择特定的连接词? 🤔

我们知道了全称量词搭配 ,存在量词搭配 的规则,现在我们来深入理解其背后的原因。通过分析具体场景,可以更清楚地看到为什么这些搭配是必要的。

考虑一个小型“世界”,其中有三个个体:Mark(男,开心)、Jocelyn(女,悲伤)、Steve(男,开心)。我们想评估关于“所有男人都开心”这个陈述的不同形式化方式。

  1. 正确形式∀x (M(x) → H(x))

    • 检查:对于 Mark(男,开心),M(Mark) → H(Mark) 为真。对于 Jocelyn(女,悲伤),M(Jocelyn) 为假,所以 M(Jocelyn) → H(Jocelyn) 为真(假前提使蕴含为真)。对于 Steve(男,开心),同样为真。所有个体都使蕴含式为真,所以整个全称陈述为真。这符合“所有男人都开心”的直观事实。
  2. 错误形式∀x (M(x) ∧ H(x))

    • 检查:这个陈述要求每一个 x 都同时是男人且开心。对于 Jocelyn,M(Jocelyn) ∧ H(Jocelyn) 为假(因为她不是男人)。因此,整个全称陈述为假。但这歪曲了原意,原意只关心“男人”这个子集是否都开心,并不要求所有人都是男人。

这个例子表明,使用 会错误地要求论域中所有对象都必须满足前提条件。而使用 则能精准地表达:只要是男人,一定开心;对于不是男人的对象,不做要求。

现在考虑存在量词。假设同一个世界,我们想表达“有些男人开心”。

  1. 正确形式∃x (M(x) ∧ H(x))

    • 检查:是否存在一个 x,使得 M(x)H(x) 同时为真?是的,Mark 和 Steve 都满足。所以陈述为真。符合直觉。
  2. 错误形式∃x (M(x) → H(x))

    • 检查:我们需要找到一个 x 使得 M(x) → H(x) 为真。对于 Jocelyn(女,悲伤),M(Jocelyn) 为假,所以 M(Jocelyn) → H(Jocelyn) 为真(假前提)。因此,这个陈述在存在非男人的个体(如 Jocelyn)的世界里,竟然被判定为真。但这明显违背了“存在开心的男人”的原意,因为实际上并没有开心的男人(Mark 和 Steve 是开心的,但 Jocelyn 的“贡献”使陈述为真是一种逻辑上的意外)。

这个例子表明,对存在量词使用 会导致陈述过于宽松,只要存在不满足前提的对象,就可能使整个存在陈述为真,从而无法准确捕捉“存在同时满足多个属性的对象”这一含义。

总结

本节课中我们一起学习了谓词逻辑的基础知识:

  1. 动机:谓词逻辑通过引入谓词(描述属性)和量词,克服了命题逻辑无法分析陈述内部结构的局限,使得像“苏格拉底论证”这样的推理得以形式化。
  2. 核心语法:包括谓词(大写字母)、项(常量、变量)、连接词和两个新的量词——全称量词 和存在量词
  3. 量词的使用规则
    • 表达“所有 P 都是 Q”时,使用 ∀x (P(x) → Q(x))
    • 表达“有些 P 是 Q”时,使用 ∃x (P(x) ∧ Q(x))
  4. 规则背后的逻辑:我们通过具体的世界模型分析了为何全称量词通常搭配蕴含 (只对满足前提的对象施加结论),而存在量词搭配合取 (要求找到的对象同时满足所有条件)。这是确保形式化表达与直观含义一致的关键。

下一讲,我们将继续深入谓词逻辑的形式系统,学习更多的翻译技巧,并更详细地讨论变量的作用域等问题。

019:如何使用乘积构造法求两个正则语言的并集

在本节课中,我们将学习如何使用“乘积构造法”来构建一个接受两个正则语言并集的确定有限自动机。我们将通过一个具体的例子,将整个过程分解为清晰的步骤,确保初学者能够轻松理解。

概述

我们有两个正则语言:

  • L1:所有以 0 开头的字符串(即 0(0|1)*)。
  • L2:所有包含至少一个 1 的字符串。

我们的目标是构建一个DFA,使其接受的语言是 L1 ∪ L2,即满足L1条件L2条件的字符串。我们将使用乘积构造法来完成这个任务。

第一步:为L1和L2分别构建DFA

在使用乘积构造法之前,我们首先需要为每个语言单独构建一个DFA。

为L2构建DFA

L2要求字符串包含至少一个1

  • 我们从起始状态 Q0 开始。如果只读到0,我们尚未见到1,因此保持在Q0
  • 一旦读到第一个1,我们就转移到状态 Q1。从Q1开始,无论后续输入是什么,字符串都满足“包含至少一个1”的条件,因此Q1是接受状态,并且对01都有自循环。

以下是L2的DFA状态转移图:

       0         0,1
Start --> [Q0] ----> (Q1)
        \     /
         1

代码描述:DFA_L2 = ({Q0, Q1}, {0,1}, δ_L2, Q0, {Q1}),其中δ_L2定义如下:

  • δ_L2(Q0, 0) = Q0
  • δ_L2(Q0, 1) = Q1
  • δ_L2(Q1, 0) = Q1
  • δ_L2(Q1, 1) = Q1

为L1构建DFA

L1要求字符串以0开头。

  • 我们从起始状态 R0 开始。
  • 如果第一个字符是0,则转移到状态 R1。因为已经满足了“以0开头”的条件,所以R1是接受状态,并且对后续的01都有自循环。
  • 如果第一个字符是1,则转移到状态 R2。因为第一个字符是1,字符串永远不可能满足L1的条件,所以R2是一个“死状态”(非接受状态),并且对01都有自循环。

以下是L1的DFA状态转移图:

       0           0,1
Start --> [R0] ----> (R1)
        \     /
         1
          \
           v
          [R2] <-- 0,1

代码描述:DFA_L1 = ({R0, R1, R2}, {0,1}, δ_L1, R0, {R1}),其中δ_L1定义如下:

  • δ_L1(R0, 0) = R1
  • δ_L1(R0, 1) = R2
  • δ_L1(R1, 0) = R1
  • δ_L1(R1, 1) = R1
  • δ_L1(R2, 0) = R2
  • δ_L1(R2, 1) = R2

第二步:应用乘积构造法

现在我们已经有了两个独立的DFA,接下来我们将使用乘积构造法来创建它们并集的DFA。其核心思想是同步运行这两个自动机。

创建新状态

新DFA的每个状态都是原有两个DFA状态的一个有序对 (q, r),其中 q 来自DFA_L2,r 来自DFA_L1。这被称为笛卡尔积。

由于DFA_L2有2个状态(Q0, Q1),DFA_L1有3个状态(R0, R1, R2),新DFA将拥有 2 × 3 = 6 个状态。它们是:

  • (Q0, R0)
  • (Q0, R1)
  • (Q0, R2)
  • (Q1, R0)
  • (Q1, R1)
  • (Q1, R2)

确定起始状态

新DFA的起始状态是原有两个DFA起始状态的组合,即 (Q0, R0)

定义转移函数

对于新DFA中的每一个状态 (q, r) 和每一个输入符号 a01),其转移目标是 (δ_L2(q, a), δ_L1(r, a))。也就是说,我们分别查看两个原始DFA在当前状态和输入a下会转移到哪里,然后将这两个新状态组合成新DFA的目标状态。

以下是计算过程示例:

  • (Q0, R0) 出发:
    • 输入 0: DFA_L2从Q0到Q0,DFA_L1从R0到R1。因此,新目标状态是 (Q0, R1)
    • 输入 1: DFA_L2从Q0到Q1,DFA_L1从R0到R2。因此,新目标状态是 (Q1, R2)
  • (Q0, R1) 出发:
    • 输入 0: DFA_L2从Q0到Q0,DFA_L1从R1到R1。因此,新目标状态是 (Q0, R1)(自循环)。
    • 输入 1: DFA_L2从Q0到Q1,DFA_L1从R1到R1。因此,新目标状态是 (Q1, R1)
  • ... 以此类推计算所有状态和输入。

确定接受状态

因为我们要构建的是并集(L1 ∪ L2)的DFA,所以一个字符串被接受,当且仅当它至少被其中一个原始DFA接受

因此,新DFA的接受状态集合是:所有满足 q 是DFA_L2的接受状态 r 是DFA_L1的接受状态 的状态 (q, r)

  • 来自DFA_L2的接受状态是 Q1。所以,所有形如 (Q1, *) 的状态(即右列所有状态)都是接受状态。
  • 来自DFA_L1的接受状态是 R1。所以,所有形如 (*, R1) 的状态都是接受状态。这包括了 (Q0, R1)(Q1, R1)

综合以上两点,新DFA的接受状态是:(Q0, R1), (Q1, R0), (Q1, R1), (Q1, R2)

最终DFA与验证

根据以上步骤,我们可以绘制出新DFA的状态转移图。在实践中,我们可能会发现某些状态(如(Q0, R2)(Q1, R2))从起始状态无法到达,在优化DFA时可以将其移除。但为了完整展示乘积构造法,我们在此保留它们。

让我们验证一下这个DFA是否正确:

  • 空字符串:从起始状态(Q0, R0)开始,它不是接受状态。空字符串不以0开头,也不包含1,因此确实不应被接受。✅
  • 0开头的字符串(如0, 01, 001:读入第一个0后,进入状态(Q0, R1),这是一个接受状态。此后无论输入什么,都会保持在接受状态((Q0, R1)(Q1, R1))。这满足了L1的条件。✅
  • 包含1的字符串(如1, 10, 010:只要读到一个1,DFA_L2的部分就会进入Q1,从而使新状态进入右列((Q1, *)),而右列所有状态都是接受状态。这满足了L2的条件。✅
  • 既不满足L1也不满足L2的字符串(即全0串):对于全0串,DFA将始终停留在左列(Q0)和状态R1,即状态(Q0, R1)。这是一个接受状态吗?是的,因为r = R1是DFA_L1的接受状态。等等,这里似乎有问题?全0串并不以0开头吗?它是以0开头的。实际上,全0串(如0, 00满足L1(以0开头),因此应该被接受。我们的DFA中(Q0, R1)是接受状态,正好接受了它。如果字符串是空串,则停留在(Q0, R0)(非接受)。如果字符串是1开头的全0串?这不可能,因为1开头后就不是全0了。所以验证通过。✅

实际上,L1 ∪ L2 是“所有以0开头包含至少一个1的字符串”。唯一不被接受的字符串就是空字符串。我们的DFA恰好满足这一点。

总结

本节课中,我们一起学习了如何使用乘积构造法为两个正则语言的并集构建DFA。我们首先回顾了为单个语言构建DFA的方法,然后详细讲解了乘积构造法的三个核心步骤:

  1. 创建状态:新状态是原DFA状态的笛卡尔积。
  2. 定义转移:新转移同步模拟两个原DFA的转移。
  3. 确定接受状态:对于并集,新状态是接受状态当且仅当它在至少一个原DFA中是接受状态。

通过这个系统化的方法,我们可以将复杂的并集DFA构造问题分解为可管理的步骤。虽然最终得到的DFA可能包含不可达状态,但乘积构造法保证了其理论上的正确性,并且是理解有限自动机闭包性质的关键工具。

020:泵引理(针对正则语言)💧

在本节课中,我们将学习用于证明语言非正则的泵引理。泵引理是形式语言理论中的一个重要工具,它利用有限状态机的局限性来揭示某些语言无法被其描述。

概述

我们已经知道,正则语言是那些可以用有限状态机(Finite State Machine)表示或设计的语言。然而,由于有限状态机存在固有的限制(例如有限的内存),存在一些语言无法用其描述,这些语言被称为非正则语言。泵引理正是用来形式化地证明一个语言是非正则的有力工具。

需要特别注意的是:泵引理只能用于证明一个语言不是正则的,而不能用于证明一个语言是正则的

泵引理陈述

如果语言 A 是一个正则语言,那么存在一个泵长度 P,使得对于 A 中任何长度不小于 P 的字符串 s,都可以被分割成三个子串 s = xyz,并且满足以下三个条件:

  1. 对于每一个 i ≥ 0,字符串 xyⁱz 仍然属于语言 A
  2. 中间部分 y 的长度大于 0,即 |y| > 0
  3. 前两部分 xy 的总长度不超过泵长度 P,即 |xy| ≤ P

用公式总结如下:
若 A 正则,则 ∃ P > 0,使得 ∀ s ∈ A 且 |s| ≥ P,∃ 分割 s = xyz 满足:

  1. ∀ i ≥ 0, xyⁱz ∈ A
  2. |y| > 0
  3. |xy| ≤ P

这个引理的核心思想是:在一个足够长的正则语言字符串中,总能找到一个非空的、可重复(“泵送”)的子串 y,重复任意多次(包括零次)后,生成的新字符串仍然在原语言中。

如何使用泵引理证明语言非正则

上一节我们介绍了泵引理的内容,本节中我们来看看如何利用它进行证明。证明通常采用反证法,遵循以下步骤:

以下是使用泵引理证明语言 A 非正则的标准步骤:

  1. 假设:首先假设语言 A 是正则的。
  2. 选取泵长度:根据泵引理,如果 A 正则,则存在一个泵长度 P
  3. 构造挑战字符串:精心构造一个属于 A 的字符串 s,并确保其长度 |s| ≥ P
  4. 枚举所有可能分割:考虑将 s 分割成 xyz 的所有可能方式,且必须满足泵引理的条件2和3(即 |y| > 0|xy| ≤ P)。
  5. 导出矛盾:对于每一种合法的分割,证明都存在某个 i ≥ 0,使得泵送后的字符串 xyⁱz 不属于 语言 A。这与泵引理的条件1相矛盾。
  6. 得出结论:由于假设(A是正则的)导致了矛盾,因此原假设不成立,从而证明 A 不是正则语言。

关键在于第4步和第5步:你必须考虑所有满足条件(|y|>0 且 |xy|≤P)的分割方式,并对每一种方式都找到矛盾。仅仅对一种分割找到矛盾是不够的。

总结

本节课我们一起学习了泵引理。泵引理为证明语言的非正则性提供了一个清晰的理论框架。其核心是,假设语言正则,则长字符串中必存在一个可重复而不影响语言成员资格的子串。通过构造一个特殊的字符串,并证明其无法满足泵引理的所有条件,我们就能推翻“语言正则”的假设。

理解其陈述和证明步骤是关键。在下一讲中,我们将通过一个具体的例子来演练这些步骤,届时这个概念会更加清晰。

021:将英语翻译为谓词逻辑 🔍

在本节课中,我们将学习如何将英语句子翻译成谓词逻辑。我们将从只涉及常量的句子(如“Jeff”和“Fred”)开始,然后学习如何使用“所有”和“一些”这类量词进行基本翻译。在另一个视频中,我们将讨论如何处理像“没有”和“并非所有”这样的否定量词。

建立符号对应关系 🔑

首先,我们需要讨论如何建立符号对应关系。书写谓词逻辑公式有几种方式。例如,如果我想为“X is happy”这样的句子建立一个框架,通常有三种常见做法。

以下是几种表示“X is happy”的方式:

  • H(x):这是我们在其他视频中见过的形式。
  • H(x):你也可以用括号将参数括起来,以明确谓词与项是分开的。
  • happy(x):或者直接用英语写出,例如 happy(x)

如果你使用像 happy(x) 这样的写法,你不需要特别说明它的含义,因为我们知道它表示“x是快乐的”。相比之下,前两种写法则需要明确写出 H(x) 表示“x是快乐的”。

通常,我们就是这样处理谓词的。有时我们会有所谓的二元谓词,或者在语言学中称为及物动词。例如,如果你想表达“x爱y”,这个谓词涉及两个事物:x和y,并且有特定的方向。

以下是几种表示“x爱y”的方式:

  • L(x, y):我们可以这样建立对应关系。
  • L(x, y):或者使用括号内用逗号分隔x和y的记法。
  • loves(x, y):或者直接用英语写出 loves(x, y)

选择你最喜欢的记法,但如果你在大学里学习相关课程,他们可能会要求你使用一种非常特定的记法。这就是我们建立谓词对应关系的方法。

为了完整起见,我们还可以处理比较关系,比如“x比y声音大”。同样,我们也可以选择 L(x, y) 来表示这种情况,因为“louder”以L开头。这就是为什么我们需要为这种情况定义符号对应关系的原因。但如果我们写成 louder_than(x, y),它的含义就非常清晰了,方向总是告诉你x如何与y相关。

最后,在定义符号时,还需要定义常量,原因相同。例如,你可以用 c 代表Carlos。这些通常是专有名词或世界中的事物,并且用小写字母表示。例如,m 可以代表Mary。

如果你想表达“Carlos是快乐的”,你可以写 H(c),意思是“Carlos是快乐的”。你基本上是把常量 c 放到了变量 x 的位置上。如果你选择其他记法,比如 happy(c),或者直接写上Carlos的全名,含义同样清晰。再次强调,选择你想使用的记法。

现在我们已经完成了符号对应关系的建立,接下来看看如何翻译一些基本句子。

翻译不含量词的基本句子 📝

上一节我们介绍了如何建立符号对应关系,本节中我们来看看如何翻译只包含谓词和专有名词、不含量词的句子。

以下是几个翻译示例:

  • Jeffrey是快乐的:我们可以建立符号:j 代表Jeffrey,H(x) 代表“x是快乐的”。那么翻译就是 H(j)。或者,我们可以写成更清晰的记法 happy(j)。从现在开始,视频中将使用 H(j) 这种记法,因为这是大多数逻辑书中常见的。
  • Solomon和Kevin都是狗:这稍微复杂一些。我们建立:D(x) 代表“x是狗”,s 代表Solomon,k 代表Kevin。我的建议是将这个句子分解为两个小句:“Solomon是狗”和“Kevin是狗”。所以翻译是 D(s) ∧ D(k)
  • Carlos比Sue快乐,但比Fred悲伤:这里我们需要“比…快乐”和“比…悲伤”的比较关系。我们建立:H(x, y) 代表“x比y快乐”,S(x, y) 代表“x比y悲伤”。常量:c 代表Carlos,s 代表Sue,f 代表Fred。这个句子包含两个比较:“Carlos比Sue快乐”和“Carlos比Fred悲伤”。所以翻译是 H(c, s) ∧ S(c, f)
  • 当Kevin不喜欢James时,James是个捣蛋鬼:我们建立:T(x) 代表“x是捣蛋鬼”,D(x, y) 代表“x不喜欢y”,j 代表James,k 代表Kevin。“当…时”等同于“如果…那么…”,并且“当”后面的部分是条件。所以翻译是 D(k, j) → T(j)

记住,所有这些专有名词常量都直接代入公式中相应的变量位置。

理解全称量词 ∀ 🌐

上一节我们处理了不含量词的句子,本节中我们来看看如何使用量词。首先看全称量词

∀x P(x) 基本上意味着“每个x都具有属性P”。我们可以这样理解:假设我们有一个包含许多对象(如a0, a1, a2…)的论域。当我们说 ∀x P(x) 为真时,我们实际上是在说 P(a0) 为真,P(a1) 为真,P(a2) 为真,依此类推。这个全称量词遍历论域中的每个对象,并赋予其属性P。

现在,让我们思考如何将英语句子“每只狗都是快乐的”翻译成谓词逻辑。我们有两种可能的翻译:

  1. ∀x (dog(x) ∧ happy(x))
  2. ∀x (dog(x) → happy(x))

我们需要分析第一个翻译的含义。它用英语等价地说:“对于每个x,x是狗并且x是快乐的”。这意味着它对我们论域中的所有事物都赋予了“是狗”和“是快乐的”这两个属性。这基本上是在说宇宙中的一切都是快乐的狗。这不是我们想要的翻译

第二个翻译 ∀x (dog(x) → happy(x)) 则说:“对于每个x,如果x是狗,那么x是快乐的”。这更好地表达了“每只狗都是快乐的”,因为它将我们讨论的范围限制在狗这个集合内。它意味着所有在“狗”这个集合中的事物,也必须在“快乐的”这个更大的集合中。这才是我们想要的翻译。

理解存在量词 ∃ 🔍

理解了全称量词后,我们来看看存在量词

∃x P(x) 意味着“存在某个x具有属性P”。同样,考虑一个包含对象a0, a1, a2…的论域。当我们说 ∃x P(x) 时,我们是在说 P(a0) 为真, P(a1) 为真, P(a2) 为真,等等。我们只需要至少有一个对象具有属性P。

现在,翻译“有些狗是快乐的”。我们有两种可能:

  1. ∃x (dog(x) ∧ happy(x))
  2. ∃x (dog(x) → happy(x))

从集合的角度思考,“有些狗是快乐的”意味着在“狗”和“快乐的”这两个集合的交集中至少有一个事物。第一个翻译 ∃x (dog(x) ∧ happy(x)) 直接表达了这一点:“存在某个x,x是狗并且x是快乐的”。这等同于“有些狗是快乐的”。

第二个翻译使用蕴含符号 是不合适的。简单来说,如果有一个不是狗的事物(比如一只猫),无论它是否快乐,条件句 dog(猫) → happy(猫) 都可能为真(因为前件假时,条件句为真)。这意味着即使没有快乐的狗,整个公式也可能为真,这违背了原句的含义。因此,我们使用合取符号 来表示存在量词下的属性组合。

翻译练习(含量词)✏️

现在让我们做一些翻译练习来巩固理解。以下是几个例子:

  • 有些孩子是生气的
    • 符号:K(x):x是孩子;A(x):x是生气的。
    • 翻译:∃x (K(x) ∧ A(x))

  • 每个怪物都是可怕的

    • 符号:M(x):x是怪物;S(x):x是可怕的。
    • 翻译:∀x (M(x) → S(x))
  • 有些朋友是充满爱心和关怀的

    • 符号:F(x):x是朋友;L(x):x有爱心;C(x):x有关怀心。
    • 翻译:∃x (F(x) ∧ L(x) ∧ C(x))
  • 所有哺乳动物都爱Fred

    • 符号:M(x):x是哺乳动物;L(x, y):x爱y;f:Fred(常量)。
    • 翻译:∀x (M(x) → L(x, f))

处理更复杂的句子 🧩

上一节的练习相对简单,本节我们来看一些更复杂的句子。

  • 每个吃寿司的朋友都很高

    • 符号:F(x):x是朋友;E(x, y):x吃y;Sushi(y):y是寿司;T(x):x很高。
    • 分析:“每个吃寿司的朋友”是一个整体,作为条件的前件。我们需要表达:对于所有x,如果x是朋友并且x吃所有寿司,那么x很高。“吃所有寿司”需要另一个全称量词。
    • 翻译:∀x [ (F(x) ∧ ∀y (Sushi(y) → E(x, y))) → T(x) ]
  • 有个孩子取笑了Fred并吃了一位老师

    • 符号:K(x):x是孩子;M(x, y):x取笑y;A(x, y):x吃y;f:Fred;T(y):y是老师。
    • 分析:存在某个x(孩子),他做了两件事:取笑了Fred(常量f),并且吃了某位老师(存在某个y是老师)。
    • 翻译:∃x [ K(x) ∧ M(x, f) ∧ ∃y (T(y) ∧ A(x, y)) ]
  • 所有的狗都追逐所有的小猫

    • 符号:D(x):x是狗;K(y):y是小猫;C(x, y):x追逐y。
    • 翻译(简洁版):∀x ∀y [ (D(x) ∧ K(y)) → C(x, y) ]
    • 翻译(分步版):∀x [ D(x) → ∀y (K(y) → C(x, y)) ]

总结 📚

本节课中,我们一起学习了将英语句子翻译成谓词逻辑的基本方法。我们首先学习了如何为谓词和常量建立符号对应关系。然后,我们深入探讨了全称量词 和存在量词 的核心用法:全称量词通常与蕴含符号 搭配使用,以限制讨论范围;存在量词通常与合取符号 搭配使用,以声明某个对象同时具有多个属性。最后,我们通过一系列从简单到复杂的练习,巩固了这些翻译技巧。记住,在处理复杂句子时,仔细分析句子结构,并逐步引入量词和逻辑连接符是关键。在接下来的视频中,我们将学习如何处理包含“没有”和“并非所有”等否定量词的句子。

022:谓词逻辑与量词否定 🧮

在本节课中,我们将要学习谓词逻辑、量词以及如何对包含量词的语句进行否定。谓词逻辑扩展了命题逻辑的能力,允许我们讨论变量和对象属性,而量词则使我们能够表达“所有”或“存在”等概念。

什么是谓词逻辑?🔍

上一节我们回顾了命题逻辑的局限性,本节中我们来看看谓词逻辑如何解决这些问题。谓词逻辑允许我们处理包含变量的语句。

在命题逻辑中,我们无法表达像“X是偶数”这样的语句,因为我们不能为X代入具体的值。但在谓词逻辑中,这成为可能,因为谓词可以包含项。一个谓词看起来像 E(X),表示“X是偶数”这个命题或谓词。

另一个例子是二元谓词 G(X, Y),它可以表示“X大于Y”。这里的X和Y是变量。我们也可以代入常量。例如,G(2, 1) 表示“2大于1”,这是一个具有真值的语句,其值为真。而 G(3, 6) 表示“3大于6”,其值为假。

封闭公式开放公式 的区别在于:封闭公式具有真值,而开放公式没有。因此,G(X, Y) 本身不是一个陈述句,这意味着它在命题逻辑中根本无法存在。我们需要谓词逻辑来表达像 G(X, Y) 这样的语句,并为代入变量的常量分配真值。

这是谓词逻辑让我们能够做到的一件事。

量词:全称量词与存在量词 📊

但谓词逻辑更重要的功能是提供了量词。这就是倒写的A(∀)和反写的E(∃)。

倒写的A被称为全称量词∀x P(x) 读作“对于所有x,P(x)”。例如,∀x H(x) 可以表示“所有人都是快乐的”。

反写的E被称为存在量词∃x P(x) 表示“至少存在一个x”。它可以读作“存在某个x使得P(x)”或“对于某个x,x是P”。例如,∃x H(x) 表示“存在一个快乐的人”。

量词极大地增强了我们将句子翻译成逻辑表达式的能力,也帮助我们分析特定英语句子、场景或数学运算的真值条件。

将数学语句翻译为谓词逻辑 ➡️

我们在数学中处处使用这种符号。以下是将一些包含数学陈述的英语句子翻译成谓词逻辑的步骤。

第一个例子:“对于每一个实数n,都存在一个实数m,使得m²等于n。”

我们暂时不关注这个陈述是否为真,只专注于翻译。

  • “对于每一个实数n”:这是全称量词。所以我们写作 ∀n ∈ ℝ
  • “存在一个实数m”:这是存在量词。所以我们写作 ∃m ∈ ℝ
  • “使得m²等于n”:我们可以直接写作 m² = n

因此,整个语句翻译为:∀n ∈ ℝ, ∃m ∈ ℝ (m² = n)

第二个例子:“给定任意两个有理数x和y,√(xy) 也是有理数。”

这个句子看起来没有明显的量词,但实际上隐藏了一个全称量词。

  • “给定任意两个有理数x和y”:这意味着“对于所有有理数x和y”。所以我们写作 ∀x, y ∈ ℚ
  • “√(xy) 也是有理数”:我们可以说 √(xy) ∈ ℚ

因此,翻译为:∀x, y ∈ ℚ (√(xy) ∈ ℚ)。你也可以写成 ∀x ∈ ℚ, ∀y ∈ ℚ,两者完全等价,只是前者更简洁。

能够双向翻译——将英语句子转化为谓词逻辑,并能阅读和理解谓词逻辑表达式——是非常重要的。

量词的否定 🔄

现在,让我们讨论对量词进行否定,这通常是难点。我们想从命题逻辑的角度来理解 ∀x P(x)∃x P(x) 的否定,以便看清其深层结构。

假设我们有一个包含元素1, 2, ..., n的论域。

  • ∀x P(x) 何时为真?当论域中的每一个x都具有属性P时为真。这就像是说 P(1) ∧ P(2) ∧ ... ∧ P(n)。如果所有这些都为真,那么 ∀x P(x) 为真。
  • ∃x P(x) 何时为真?当至少有一个x具有属性P时为真。这就像是说 P(1) ∨ P(2) ∨ ... ∨ P(n)

这就是 ∀x P(x) 和 ∃x P(x) 的本质含义。现在,当我们否定它们时,利用这些定义就能看出否定是如何运作的。

让我们证明 ¬∀x P(x) 逻辑等价于 ∃x ¬P(x)

首先,根据定义,∀x P(x) 等价于 P(1) ∧ P(2) ∧ ... ∧ P(n)
现在,否定整个合取式:¬(P(1) ∧ P(2) ∧ ... ∧ P(n))
根据德摩根定律,我们可以分配这个否定,得到:¬P(1) ∨ ¬P(2) ∨ ... ∨ ¬P(n)
这看起来像什么?它看起来就像是 ∃x ¬P(x) 的定义,因为 ∃x Q(x) 的定义正是 Q(1) ∨ Q(2) ∨ ... ∨ Q(n)。这里,Q(x) 就是 ¬P(x)。

用英语来思考:“并非所有的狗都是棕色”意味着“存在一些狗不是棕色”。所以,“并非所有狗都是棕色”等价于“存在狗不是棕色”。

实际上,关于量词否定有四个重要的逻辑等价式:

  1. ∀x P(x) ≡ ¬∃x ¬P(x)
  2. ∃x P(x) ≡ ¬∀x ¬P(x)
  3. ¬∀x P(x) ≡ ∃x ¬P(x) (我们刚刚证明的这个)
  4. ¬∃x P(x) ≡ ∀x ¬P(x)

你可以使用类似的定义方法自行验证其余等价式。

一个记忆量词否定等价式的小技巧 💡

你可能会想,是否需要死记硬背这些等价式?其实有一个小技巧。

¬∀x P(x) ≡ ∃x ¬P(x) 为例。
我们可以用符号简化:+ 表示“无否定”,- 表示“有否定”。
那么 ¬∀x P(x) 可以写成 - ∀ + P
得到其等价式的规则是:翻转量词类型(∀ 变 ∃,∃ 变 ∀),并翻转所有符号(+ 变 -,- 变 +)
所以,- ∀ + P 变成 + ∃ - P
再翻译回来:+ ∃ 就是 ∃x- P 就是 ¬P(x)。结果正是 ∃x ¬P(x)

再试一个:∃x P(x)+ ∃ + P
应用规则:翻转量词(∃ 变 ∀),翻转符号(+ 变 -)。得到 - ∀ - P
翻译回来:- ∀¬∀x- P¬P(x)。结果 ¬∀x ¬P(x),这正是第二个等价式。

这个技巧在未来学习模态逻辑(涉及“必然”□和“可能”◇算子)时也会遇到,它们的否定规则是相似的。

复杂语句的否定实践 🛠️

最后,让我们运用刚才讨论的规则来对一个更复杂的语句进行否定。

假设我们要否定这个语句:∀x (∃y (P(x, y) ∧ Q(y)))

我们一步一步来,逐个量词进行否定。

  1. 首先,否定最外层的 ∀x。根据规则 ¬∀x ... ≡ ∃x ¬(...),我们得到:
    ∃x ¬(∃y (P(x, y) ∧ Q(y)))
  2. 现在,处理内部的 ¬∃y。根据规则 ¬∃y ... ≡ ∀y ¬(...),我们得到:
    ∃x ∀y ¬(P(x, y) ∧ Q(y))
  3. 最后,对内部的合取式应用德摩根定律:¬(A ∧ B) ≡ ¬A ∨ ¬B。所以:
    ∃x ∀y (¬P(x, y) ∨ ¬Q(y))

完成。因此,¬[∀x (∃y (P(x, y) ∧ Q(y)))] ≡ ∃x ∀y (¬P(x, y) ∨ ¬Q(y))

进行否定时,只需这样一步一步来:一次处理一个量词,将否定符号向内推,翻转量词,继续向内推,最后对内部的命题应用德摩根定律。

总结 📝

本节课中我们一起学习了谓词逻辑的核心概念。我们了解了谓词如何允许我们处理变量,并介绍了强大的全称量词(∀)存在量词(∃)。我们练习了将数学语句翻译成谓词逻辑公式。

更重要的是,我们深入探讨了量词的否定,学习了四个关键的逻辑等价式,并掌握了一个通过翻转量词和否定符号来记忆它们的小技巧。最后,我们通过一个例子实践了如何对复杂的嵌套量词语句进行逐步否定。掌握这些技巧对于理解和运用谓词逻辑至关重要。

023:谓词逻辑中的自由变量与约束变量 🔍

在本节课中,我们将学习谓词逻辑中两种不同类型的变量:自由变量与约束变量。我们将明确在谓词逻辑的句子中,哪些变量形式是可接受的,哪些是不可接受的,并理解其背后的原因。

自由变量与约束变量的定义

上一节我们介绍了谓词逻辑的基本概念,本节中我们来看看变量的两种状态。

一个变量 x自由的,当它出现在量词的作用域内。反之,一个变量是约束的,当它出现在某个量词的作用域内。

量词的作用域通常由紧随其后的括号界定。例如,在公式 ∀x ( ... ) 中,括号 ( ... ) 内的所有 x 的出现都被这个全称量词 ∀x 约束

作用域与变量绑定示例

以下是理解变量绑定与作用域的几个关键点:

  • 约束变量示例:考虑公式 ∃x (D(x) ∧ P(x))。量词 ∃x 的作用域是 (D(x) ∧ P(x))。因此,在这个作用域内的两个 x 都被约束。
  • 自由变量示例:在公式 ∃x (D(x) ∧ P(x)) ∧ Q(x) 中,Q(x) 中的 x 位于量词 ∃x 的作用域之外,因此它是一个自由变量
  • 变量重命名:在公式 ∀x (Dog(x) → Blue(x)) ∧ ∀y (Cat(y) → Gray(y)) 中,第一个量词约束 x,第二个量词约束 y。实际上,第二个量词也可以使用 x,写作 ∀x (Cat(x) → Gray(x))。虽然这可能会造成阅读混淆,但在逻辑上是完全允许的,因为两个量词的作用域是独立的,它们约束的是不同的“哑变量”。

合式公式的要求

一个谓词逻辑句子被称为合式公式,当且仅当其中没有自由变量的出现。

我们不允许自由变量出现在合式公式中,原因在于自由变量的指代是模糊且不确定的。例如,仅包含 P(x) ∧ G(x) 的公式无法告诉我们这两个 x 是否指代同一个对象。通过使用量词约束变量,我们能够精确地定义变量的取值范围和指代关系,确保逻辑表达式的清晰和无歧义。

以下是关于合式公式的几点说明:

  • 允许混合命题:公式中可以同时包含量词约束的谓词和独立的命题常量。例如,∃x (D(x) ∧ P(x)) ∧ R 是合式的,其中 R 是一个命题。
  • 量词约束“空”变量:公式 ∃x P(a)(意为“存在某个x,使得a具有性质P”)是合式的。虽然量词 ∃x 约束的变量 x 没有在作用域内实际出现(出现次数为0),但该公式中确实没有自由变量,因此满足合式公式的条件。
  • 量词语法错误:单独的 ∃ P(x) 是不正确的,因为量词()后面必须紧跟一个要约束的变量。

量词的命题逻辑视角与否定

理解量词的另一种方式是从命题逻辑的角度来看待它们。

  • 全称量词 ∀x P(x) 可以理解为对论域中所有个体 a₁, a₂, ..., aₙ,性质 P 都成立。这类似于一个合取P(a₁) ∧ P(a₂) ∧ ... ∧ P(aₙ)
  • 存在量词 ∃x P(x) 意味着论域中至少有一个个体具有性质 P。这类似于一个析取P(a₁) ∨ P(a₂) ∨ ... ∨ P(aₙ)

基于这种视角,我们可以推导出量词否定的一些重要等价关系:

  1. ¬∀x P(x) ≡ ∃x ¬P(x)

    • 解释:“并非所有x都具有性质P” 等价于 “存在某个x不具有性质P”。
    • 推导¬∀x P(x)¬(P(a₁) ∧ P(a₂) ∧ ... ∧ P(aₙ))。根据德摩根律,这等价于 ¬P(a₁) ∨ ¬P(a₂) ∨ ... ∨ ¬P(aₙ),而这正是 ∃x ¬P(x) 的含义。
  2. ¬∃x P(x) ≡ ∀x ¬P(x)

    • 解释:“不存在具有性质P的x” 等价于 “所有x都不具有性质P”。
    • 推导¬∃x P(x)¬(P(a₁) ∨ P(a₂) ∨ ... ∨ P(aₙ))。根据德摩根律,这等价于 ¬P(a₁) ∧ ¬P(a₂) ∧ ... ∧ ¬P(aₙ),而这正是 ∀x ¬P(x) 的含义。

记忆技巧:量词的对偶性

有一个简单的技巧可以帮助记忆上述等价关系,它体现了量词之间的“对偶性”:

  • ∀x 视为 ¬∃x¬
  • ∃x 视为 ¬∀x¬

核心规则:当对一个带量词的公式进行变换(如求否定或寻找等价形式)时,可以遵循一个模式:将量词类型互换(∀↔∃),并在量词和谓词前各加一个否定(¬)。如果原来有否定,则可能相互抵消。

例如:

  • ∀x P(x) 等价于 ¬∃x ¬P(x)。( 换为 ,前后加 ¬
  • ¬∃x P(x) 等价于 ∀x ¬P(x)。( 换为 ,去掉原有的 ¬ 并在谓词前加 ¬

这个模式在模态逻辑等其他逻辑系统中也有类似体现(如“必然”算子 □ 与“可能”算子 ◇ 的关系:□P ≡ ¬◇¬P)。

总结

本节课中我们一起学习了谓词逻辑中自由变量约束变量的核心区别:约束变量受量词管辖,指代明确;自由变量则不然,会导致语句含义模糊。因此,合式公式必须不含任何自由变量。我们还从命题逻辑合取与析取的角度理解了全称量词与存在量词的含义,并掌握了它们与否定运算符交互时的关键等价规则:¬∀x P(x) ≡ ∃x ¬P(x) 以及 ¬∃x P(x) ≡ ∀x ¬P(x)。最后,介绍的量词对偶性记忆技巧,能帮助我们更灵活地进行公式变换。理解这些概念是后续进行形式化推理和证明的重要基础。

024:上下文无关文法构建示例

在本节课中,我们将学习如何为不同的语言构建上下文无关文法。我们将通过一系列具体的例子,从简单到复杂,逐步掌握CFG的构建方法。

概述

上下文无关文法是一种用于描述编程语言语法和某些形式语言的有力工具。它由一组变量、终结符、产生式和一个起始符号组成。本节将通过多个示例,演示如何根据语言描述来设计相应的产生式集合。

示例一:任意数量的a

首先,我们构建一个CFG,用于生成字母表 Σ = {a} 上由任意数量a组成的字符串。该语言包括空串ε、单个a、两个a等。

该语言对应的正则表达式是 a*。为了生成任意数量的a,我们需要一个递归的产生式。

以下是该文法的产生式集合:

  • S → aS
  • S → ε

其中,S是起始符号。第一条产生式S → aS是递归步骤,用于生成更多的a。第二条产生式S → ε是基础步骤,用于终止递归并允许生成空串。

该文法的四元组形式化表示为:
G = ({S}, {a}, P, S),其中P是上述产生式集合。

示例二:任意数量的a和b

接下来,我们构建一个CFG,用于生成由任意数量的a和b以任意顺序组合而成的字符串。语言包括ε, a, b, aa, ab, ba, bb等。

该语言对应的正则表达式是 (a|b)*。我们可以扩展上一个示例的思路。

该文法的产生式如下:

  • S → aS
  • S → bS
  • S → ε

由于这些产生式具有相同的左部(头),我们可以使用竖线|将它们合并写为:S → aS | bS | ε

该文法的四元组形式化表示为:
G = ({S}, {a, b}, P, S),其中P是上述产生式集合。

示例三:至少包含两个a的字符串

现在,我们构建一个CFG,用于生成所有至少包含两个字母a的字符串。字符串中这两个a的前后可以有任何数量的a和b。

该语言对应的正则表达式是 (a|b)* a (a|b)* a (a|b)*。关键在于确保字符串中至少出现两个a。

我们可以引入一个新的非终结符A来生成任意数量的a和b(即(a|b)*)。文法的核心思想是:起始符号S生成“任意a/b串 + a + 任意a/b串 + a + 任意a/b串”。

该文法的产生式可以设计为:

  • S → A a A a A
  • A → aA | bA | ε

这里,A负责生成(a|b)*部分,而S的结构保证了至少两个a的出现。

示例四:包含子串“000”的字符串

此例要求构建CFG,用于生成所有恰好包含一个“000”子串的字符串。这个“000”子串的前后可以有任意数量的0和1。

该语言对应的正则表达式是 (0|1)* 000 (0|1)*。构建思路与上一个例子类似。

我们可以引入非终结符A来生成(0|1)*。文法的核心是确保“000”出现且仅出现一次(通过文法结构隐含约束,通常指“至少一次”,更严格的“恰好一次”需要更复杂的设计,此处按“至少一次”理解)。

该文法的产生式可以设计为:

  • S → A 000 A
  • A → 0A | 1A | ε

示例五:a^n b^n 型字符串

这是一个经典例子:构建CFG,用于生成形如a^n b^n(n ≥ 1)的字符串,即a的数量与b的数量相等,且所有a在前,所有b在后。例如:ab, aabb, aaabbb。

关键点在于保持a和b的数量相等。我们可以使用递归产生式在生成a的同时“预订”一个b。

该文法的产生式如下:

  • S → aSb
  • S → ab

第一条产生式S → aSb是递归步骤,每次应用它都会增加一个a和一个b。第二条产生式S → ab是基础步骤,用于终止递归并生成最小的合法字符串ab

对于字符串aaabbb,推导过程可以是:S ⇒ aSb ⇒ aaSbb ⇒ aaabbb

示例六:a^n b^2n 型字符串

此例是上一个的变体:构建CFG,用于生成形如a^n b^{2n}(n ≥ 1)的字符串,即b的数量是a的两倍。

思路类似,但在递归步骤中,每次生成一个a时,需要生成两个b。

该文法的产生式如下:

  • S → aSbb
  • S → abb

示例七:w c w^R 型字符串(奇回文)

此例要求构建CFG,用于生成形如w c w^R的字符串,其中w是由a和b组成的任意字符串,w^Rw的逆序,c是一个中心标记符。例如:a c a, ab c ba。这是一种带中心标记的奇长度回文。

核心思想是:如果字符串以a开始,则必须以a结束;如果以b开始,则必须以b结束。递归地,去掉首尾符号后,中间部分仍然符合这个模式。

该文法的产生式如下:

  • S → aSa
  • S → bSb
  • S → c

前两条产生式递归地构建回文结构,第三条产生式提供中心标记,从而终止递归。

对于字符串ab c ba,推导过程可以是:S ⇒ aSa ⇒ abSba ⇒ abcba

示例八:a^n b^m c^n 型字符串

此例要求构建CFG,用于生成形如a^n b^m c^n(n, m ≥ 1)的字符串,即首尾a和c的数量相等,中间有至少一个b。

我们需要同时处理两个约束:1) a和c数量相等;2) 中间有b。可以先用递归产生式生成a...c的框架,再在框架内生成b。

该文法的产生式可以设计为:

  • S → aSc
  • S → aBc
  • B → bB | b

第一条产生式S → aSc递归生成外层的a和c。第二条产生式S → aBc将递归转换为生成中间b的阶段。非终结符B通过B → bB | b生成至少一个b。

对于字符串aa bbb cc,推导过程可以是:S ⇒ aSc ⇒ aaScc ⇒ aaBcc ⇒ aabBcc ⇒ aabbBcc ⇒ aabbbcc

示例九:a^n b^n c^n d^n 型字符串(非CFL)

我们考虑语言 a^n b^n c^n d^n(n ≥ 1)。可以证明,这个语言不是上下文无关语言,因此不存在CFG能精确生成它。

直观上,CFG的栈式记忆能力有限。要同时匹配a、b、c、d四组数量,需要记忆多个独立的计数,这超出了CFG的能力范围(属于更强大的上下文有关文法范畴)。

示例十:a^n b^n c^m d^m 型字符串

此例要求构建CFG,用于生成形如a^n b^n c^m d^m(n, m ≥ 1)的字符串。这可以看作是两个独立结构a^n b^nc^m d^m的拼接。

我们可以用两个非终结符分别生成这两个部分。

该文法的产生式可以设计为:

  • S → XY
  • X → aXb | ab
  • Y → cYd | cd

这里,X负责生成a^n b^n部分,Y负责生成c^m d^m部分,起始符号S将它们组合起来。

示例十一:a^{m+n} b^m c^n 型字符串

最后,我们构建CFG,用于生成形如a^{m+n} b^m c^n(m, n ≥ 1)的字符串。我们可以将字符串重写为a^m a^n b^m c^n

观察发现,这可以视为a^m b^ma^n c^n的组合。我们可以设计产生式,首先生成a^n c^n的框架,同时允许在框架的a部分前添加额外的a,并为这些额外的a生成对应的b。

该文法的产生式可以设计为:

  • S → aSc | A
  • A → aAb | ab

第一条产生式S → aSc生成a^n c^n的核心结构。S → A将生成任务转交给AA → aAb | ab则在核心结构的前端添加a^m b^m部分。

对于字符串aaa bb c(m=2, n=1),推导过程可以是:S ⇒ aSc ⇒ aAc ⇒ aaAbc ⇒ aabbc

总结

本节课中,我们一起学习了上下文无关文法的构建方法。我们从生成简单模式(如任意数量的字符)开始,逐步深入到生成需要计数和匹配的复杂模式(如a^n b^n)。关键步骤包括:

  1. 理解语言:明确语言包含哪些字符串。
  2. 识别模式:寻找字符串中的递归或重复结构。
  3. 设计产生式
    • 使用递归产生式(如S → aS)生成重复模式。
    • 使用基础产生式(如S → εS → ab)终止递归。
    • 引入新的非终结符来处理语言中不同的子部分或约束条件。
  4. 形式化表示:将文法写成四元组(V, T, P, S)的形式。

通过练习这些示例,你应该能够掌握为许多常见语言设计CFG的基本技巧。

025:为特定语言寻找上下文无关文法 🧩

在本节课中,我们将学习如何为一系列特定的语言构造上下文无关文法。我们将从简单的语言开始,逐步过渡到更复杂的模式,并详细解释每个文法规则的设计思路。

概述

上下文无关文法是一种用于描述编程语言语法和某些模式化字符串集合的强大工具。本节我们将针对八种不同的语言模式,逐一推导出对应的上下文无关文法。每种语言都由字母表 {a, b, c} 上的字符串组成,并遵循特定的计数规则。

语言一:aⁿbⁿ,其中 n ≥ 0

这种语言包含的字符串特点是:先有 n 个连续的 a,然后有 n 个连续的 b。最小的字符串是空串 λ。

以下是该语言的文法规则:

  • S -> aSb | λ
    • 规则 S -> aSb 确保 ab 的数量同步增加。
    • 规则 S -> λ 允许推导出空串,并作为递归的终止条件。

语言二:aⁿbⁿ,其中 n ≥ 1

这种语言与语言一类似,但不包含空串 λ。因此,最小的字符串是 ab

上一节我们介绍了包含空串的文法,本节中我们来看看如何排除空串。以下是该语言的文法规则:

  • S -> aSb | ab
    • 基础规则 S -> ab 直接生成最小的有效字符串。
    • 递归规则 S -> aSb 用于生成更长的字符串。

语言三:aⁿbⁿ,其中 n 为偶数

这种语言要求 ab 的数量相等且为偶数。因为 0 是偶数,所以空串 λ 也属于该语言。

以下是该语言的文法规则:

  • S -> aaSbb | λ
    • 规则 S -> aaSbb 每次增加两个 a 和两个 b,保证数量保持相等且为偶数增长。
    • 规则 S -> λ 生成空串。

语言四:aⁿbⁿ,其中 n 为奇数

这种语言要求 ab 的数量相等且为奇数。因此,最小的字符串是 ab(n=1)。

以下是该语言的文法规则:

  • S -> aaSbb | ab
    • 基础规则 S -> ab 生成最小的奇数对字符串。
    • 递归规则 S -> aaSbbab 的基础上,每次增加两个 a 和两个 b,从而得到更大的奇数对字符串(如 aaabbb,即 n=3)。

语言五:aⁿbⁿ,其中 n 是 3 的倍数

这种语言要求 ab 的数量相等且是 3 的倍数。0 也是 3 的倍数,所以空串 λ 属于该语言。

以下是该语言的文法规则:

  • S -> aaaSbbb | λ
    • 规则 S -> aaaSbbb 每次增加三个 a 和三个 b,确保数量是 3 的倍数。
    • 规则 S -> λ 生成空串。

语言六:aⁿbⁿ,其中 n 不是 3 的倍数

这种语言比前几种更复杂,它排除了 n 为 3 的倍数的情况(如 n=0,3,6...),但包含 n=1,2,4,5,7... 等情况。

为了构造这个文法,我们可以先生成所有 aⁿbⁿ 的字符串,然后设法“跳过”那些 n 是 3 的倍数的字符串。以下是该语言的文法规则:

  • S -> A | B
  • A -> aaSbb | ab
  • B -> aaaSbbbS | aab
    • 非终结符 A 负责生成 n=1 和 n=2 的字符串(ab, aabb),并通过递归 aaSbb 生成所有 n ≡ 1 或 2 (mod 3) 的字符串(如 n=4,5,7,8...)。
    • 非终结符 B 的规则 aaaSbbbS 首先生成一个 n=3 的倍数字符串(aaabbb),然后通过后面的 S 继续附加一个“非3倍数”的字符串,从而组合出像 aaabbb ab (即 a⁴b⁴) 这样的字符串。

语言七:aⁿbᵐcⁿ⁺ᵐ,其中 n, m ≥ 0

这种语言的字符串分为三段:n 个 a,m 个 b,以及数量为前两者之和的 c。我们可以将其视为两部分拼接而成。

以下是该语言的文法规则:

  • S -> XY
  • X -> aXb | λ
  • Y -> bYc | λ
    • 非终结符 X 生成 aⁿbⁿ 部分。
    • 非终结符 Y 生成 bᵐcᵐ 部分。
    • XY 拼接 (S -> XY) 后,中间的 b 会合并,最终形成 aⁿbᵐcⁿ⁺ᵐ 的模式。例如,X 生成 aab (n=2),Y 生成 bbcc (m=2),拼接后得到 aabbbcc,即 a²b³c⁴,符合 n=2, m=2, n+m=4。

语言八:aⁿbᵐcⁿ⁺ᵐ,其中 n, m ≥ 1

这种语言与语言七类似,但要求 ab 都至少出现一次,因此字符串中不会出现空段或单独的 c

以下是该语言的文法规则:

  • S -> XY
  • X -> aXb | ab
  • Y -> bYc | bc
    • 与语言七的关键区别在于,基础规则从 λ 替换为 abbc。这确保了 ab 都至少出现一次。
    • X -> ab 保证至少有一个 a 和一个 b
    • Y -> bc 保证在 b 段之后至少有一个 c

总结

本节课中我们一起学习了为八种具有不同计数规则的语言设计上下文无关文法。我们从最简单的 aⁿbⁿ 模式出发,逐步增加了对奇偶性、倍数关系的约束,最后处理了更复杂的 aⁿbᵐcⁿ⁺ᵐ 拼接模式。核心在于通过递归规则捕捉数量的同步关系,并通过基础规则或额外的非终结符来处理边界条件(如空串、最小值、排除特定情况等)。掌握这些构造技巧是理解和使用上下文无关文法的关键。

026:为特定语言寻找上下文无关文法(第二部分)

在本节课中,我们将学习如何为一系列特定语言构造上下文无关文法。我们将分析语言的定义,并逐步推导出能够生成这些语言中所有字符串的文法规则。

上一节我们介绍了上下文无关文法的基本概念,本节中我们来看看如何为更复杂的语言构造文法。

问题9:语言 L =

这个语言包含的字符串形式为:a的数量等于n+m,b的数量等于m,c的数量等于n。n和m可以为零。

以下是构造该文法的关键思路:

  • 起始符号S需要同时生成a、b和c,并确保它们的数量关系正确。
  • 核心思想是使用递归产生式来同步生成a和c,同时单独生成a和b。
  • 因为m和n可以为零,所以文法需要能生成空串λ。

基于以上分析,我们得到以下上下文无关文法:

S -> A
A -> λ | aAb | aAc

解释

  • S -> A:起始符号。
  • A -> λ:生成空串(当n=0且m=0时)。
  • A -> aAb:生成一个a和一个b,并递归调用A。这确保了生成的a和b数量相等(对应m)。
  • A -> aAc:生成一个a和一个c,并递归调用A。这确保了生成的a和c数量相等(对应n)。由于A可以继续生成aAb或aAc,最终a的总数将是n+m。

问题10:语言 L =

此语言与问题9类似,但要求n和m至少为1,因此不能包含空串。

以下是构造该文法的关键思路:

  • 文法必须保证至少生成一个b和一个c。
  • 起始产生式需要直接引入至少一个c,并确保后续能生成b。

基于以上分析,我们得到以下上下文无关文法:

S -> aSc | aBc
B -> aBb | ab

解释

  • S -> aSc:递归生成成对的a和c,确保a和c的数量n≥1。
  • S -> aBc:切换到B来生成a和b对。此产生式确保了至少有一个c(由最后的c提供)和至少一个b(由B提供)。
  • B -> aBb:递归生成成对的a和b。
  • B -> ab:生成最终的a和b,确保至少有一个b。

问题11:语言 L =

这个语言要求c的数量等于a的数量加上b的数量。

以下是构造该文法的关键思路:

  • 我们需要同步生成a和c,以及b和c。
  • 可以使用一个中间变量来分别处理a和b的生成,并确保为它们添加相应数量的c。

基于以上分析,我们得到以下上下文无关文法:

S -> aSc | B
B -> bBc | λ

解释

  • S -> aSc:每生成一个a,就生成一个c。
  • S -> B:当a生成完毕后,切换到B来生成b和c。
  • B -> bBc:每生成一个b,就生成一个c。
  • B -> λ:允许b和m为零的情况。

问题12:语言 L =

此语言是问题11的变体,要求n和m至少为1。

以下是构造该文法的关键思路:

  • 文法必须保证至少生成一个a和一个b。
  • 需要调整产生式,避免生成空串。

基于以上分析,我们得到以下上下文无关文法:

S -> aSc | aBc
B -> bBc | bc

解释

  • S -> aSc:生成a和c对,确保n≥1。
  • S -> aBc:引入第一个a和最后一个c,并切换到B来处理b的生成。
  • B -> bBc:递归生成b和c对。
  • B -> bc:生成最后一个b和c,确保m≥1。

问题13:语言 L =

这个语言要求b的数量m在a的数量n到3倍n之间(包含端点)。

以下是构造该文法的关键思路:

  • 需要产生式能生成b的数量等于a、两倍a或三倍a。
  • 为每种情况设计对应的产生式组合。

基于以上分析,我们得到以下上下文无关文法:

S -> aSb | aSbb | aSbbb | λ

解释

  • S -> aSb:生成一个a和一个b(m = n)。
  • S -> aSbb:生成一个a和两个b(m = 2n)。
  • S -> aSbbb:生成一个a和三个b(m = 3n)。
  • S -> λ:生成空串(当n=0,m=0时)。
    通过选择不同的产生式进行组合,可以生成所有满足 n ≤ m ≤ 3n 的字符串。

问题14:语言 L =

这个语言要求b的数量m在2倍a的数量到3倍a的数量之间。

以下是构造该文法的关键思路:

  • 与问题13类似,但b的基数更大。
  • 需要产生式能生成b的数量等于两倍a或三倍a。

基于以上分析,我们得到以下上下文无关文法:

S -> aSbb | aSbbb | λ

解释

  • S -> aSbb:生成一个a和两个b(m = 2n)。
  • S -> aSbbb:生成一个a和三个b(m = 3n)。
  • S -> λ:生成空串。
    这些产生式的组合可以覆盖所有满足 2n ≤ m ≤ 3n 的条件。

本节课中我们一起学习了为六种不同规律的上下文无关语言设计文法。关键步骤是:首先分析语言中字符串的数量关系约束,然后设计递归产生式来同步生成不同符号,并特别注意边界条件(如符号数量是否能为零)。通过掌握这些模式,你可以为更多复杂的语言构造上下文无关文法。

posted @ 2026-03-29 09:47  布客飞龙III  阅读(96)  评论(0)    收藏  举报