VisFormalLang 形式语言可视化

第 7 章 下推自动机

| 关于   英文版   «  7.2. 下推自动机与上下文无关语言   ::   目录   ::   8.1. 上下文无关语言的性质  »

7.3. 确定的下推自动机

7.3.1. 确定的下推自动机

定义(Definition): 一个 PDA \(M = (Q, \Sigma, \Gamma, \delta, q_0, z, F)\) 是 确定性的(deterministic) ,如果对每一个 \(q \in Q\)、\(a \in \Sigma \cup \{\lambda\}\)、\(b \in \Gamma\):

1. \(\delta(q, a, b)\) 至多包含一个元素
2. 如果 \(\delta(q, \lambda, b)\) 非空,则必须对每一个 \(c \in \Sigma\) 都有 \(\delta(q, c, b)\) 为空。

定义(Definition): \(L\) 是 确定的上下文无关语言(deterministic context-free language) (DCFL),当且仅当存在一个确定性的 PDA \(M\) ,使得 \(L = L(M)\) 。

示例(Example)

语言 \(L = \{a^nb^n | n \ge 0\}\) 是确定的 CFL。

证明(Proof): PDA \(M = (\{q_0, q_1, q_2\}, \{a, b\}, \{0, 1\}, \delta, q_0, 0, \{q_0\})\) 其中
\(\delta(q_0, a, 0) = \{(q_1, 10)\}\),
\(\delta(q_1, a, 1) = \{(q_1, 11)\}\),
\(\delta(q_1, b, 1) = \{(q_2, \lambda)\}\),
\(\delta(q_2, b, 1) = \{(q_2, \lambda)\}\),
\(\delta(q_2, \lambda, 0) = \{(q_0, \lambda)\}\)

接受上述语言。它满足确定性的条件。

注意,这台机器确实 有 \(\lambda\) 转移。关键在于,仍然只有一个选择(这是由栈顶是什么决定的)。从这个意义上说,它并不仅仅是一条"免费搭车"(free ride)的转移。

示例(Example)

我们之前用于 \(\{ww^R | w\in{\Sigma}^{+}\}, \Sigma = \{a, b\}\) 的 PDA 是非确定性的。

它包含这些转移:
\(\delta(q_0, a, a) = \{(q_0, aa)\}\)
\(\delta(q_0, \lambda, a) = \{(q_1, a)\}\)

这违反了确定性的条件。(你知道为什么吗?)

现在,我们的 PDA 不是确定性的这一事实,当然 并不能 证明 \(\{ww^R | w\in{\Sigma}^{+}\}, \Sigma = \{a, b\}\) 不是确定的 CFL。

但是,确实存在不是确定性的 CFL。我们将会看到,其中之一就是它。这是符合直觉的。当从左到右扫描输入时,我们怎么能确定性地知道何时从 \(w\) 切换到 \(w^R\) 呢?

示例(Example):

\(L = \{a^nb^n|n \ge 1\} \cup \{a^nb^{2n}| n\ge 1\}\) 是 CFL ,但不是 DCFL。

显然,这两种语言都是 CFL。显然,它们的并集也是 CFL。但请想象一下这个"显然的"PDA 是如何工作的:起始状态转移到"正确的"机器,让它识别某一种语言中的字符串。但我们怎样才能确定性地做到这一点呢?我们需要一种完全不同的方法。

虽然这还不是该语言不是确定性的证明,但下面有一个证明。

定理(Theorem): \(L_1 \cup L_2\) 不是 DCFL (因为 \(a^nb^nc^n\) 不是 CFL)。

证明(Proof):

\(L = \{a^nb^n: n \ge 1\} \cup \{a^nb^{2n}: n \ge 1\}\)
为 \(\{a^nb^n: n\ge 1\}\) 构造一个 NPDA 、为 \(\{a^nb^{2n}: n \ge 1\}\) 构造一个 NPDA 都很容易。 这两个 NPDA 可以通过一个新的起始状态和 \(\lambda\) 转移连接在一起,构造出 L 的 NPDA。因此,L 是 CFL。
现在证明 \(L\) 不是 DCFL 。 假设存在一个确定性的 PDA \(M\) ,使得 \(L = L(M)\) 。我们将构造一个能识别非 CFL 语言的 PDA ,并由此导出矛盾。
如下构造一个 PDA \(M'\) :

待处理

type: Figure

显示图

创建两个副本的 \(M: M_1\) 和 \(M_2\) 。 \(M_1\) 和 \(M_2\) 中的相同状态称为表亲。

2. 移除 \(M_1\) 中接受状态的接受地位,移除 \(M_2\) 中初始状态的初始地位。 在我们的新 PDA 中,将从 \(M_1\) 开始,在 \(M_2\) 中接受。
3. 从 \(M_1\) 中旧接受状态出发的出弧,改为结束于其 在 \(M_2\) 中的目的地的表亲。这就把 \(M_1\) 和 \(M_2\) 合并成了一个 PDA。必须存在这样的出弧,因为你必须同时识别 \(a^nb^n\) 和 \(a^nb^{2n}\)。在读入 \(n\) 个 b 之后,如果没有更多的 b 就必须接受,如果还有更多的 b 就继续。
4. 把所有读入 b 且其目的地在 \(M_2\) 中的转移,都改为读入 c。
这就是我们新 PDA 的构造。
当我们读入 \(a^nb^n\) 并停在 \(M_1\) 的一个旧接受状态时, 我们将转移到 \(M_2\) ,并读取 \(a^nb^{2n}\) 的其余部分。由于在 \(M_2\) 中只有 b 被替换成了 c ,因此这台新机器接受 \(a^nb^nc^n\)。
我们的新 PDA 接受的语言是 \(a^nb^nc^n\)。 但这不是 CFL。矛盾!因此不存在确定性的 PDA \(M\) 使得 \(L(M) = L\)。证毕(Q.E.D.)。

基于这些信息,我们现在可以更新形式语言宇宙(Formal Languages Universe)的模型。

lt8hier

7.3.2. 确定的上下文无关语言的文法

现在我们知道:

  • 所有 CFL 都可以由 CFG 生成,并由 NPDA 实现。

  • 并非所有 CFL 都能用 DPDA 生成。

  • 因此,某些 CFG 只与非确定性的 PDA 相关联。 就能力而言,非确定性给了我们更多的东西。

我们为什么关心?因为我们希望高效地分析(parse)。也就是说,我们希望快速确定给定的字符串是否能由给定的文法生成。如果我们要做到这一点,确定性似乎是一个基本要求。

可以把 PDA 看作一种分析设备。无回溯(no backtracking)要求我们能在每一步都对下一步做什么作出决定。这等同于知道下一步该用哪一条文法产生式。显然,当文法已经被简化时(没有单位产生式),这是线性时间,因为每一步推导都会消耗一个输入字符。嗯……除了 \(\lambda\) 产生式。但我们很快就会看到,对于线性时间处理来说,它们其实并不是什么问题。

7.3.2.1. 带向前看的自顶向下分析

从起始符号开始。从左到右扫描字符串。在每一步中,当我们看到当前字符时,只想遵循一条规则。也许我们看不到针对当前字符的产生式,反而要从栈中弹出某个东西(\(\lambda\) 产生式)。这就是为什么如果我们在处理一个字符时不会在栈上压入太多东西,\(\lambda\) 产生式仍然是线性的。

7.3.2.2. S 文法

回顾:一个 S 文法(S-grammar)的所有产生式都具有如下形式:
\(A \rightarrow ax\)
其中 \(A \in V\)、\(a \in T\)、\(x \in V^*\) ,并且任何对 \((A, a)\) 至多只能出现在一条规则中。

显然,这样的文法可以被高效地分析。但是,S 文法比我们想要的更受限制。许多有用的语言构造无法用 S 文法定义。我们希望在尽可能大的程度上加以推广,以覆盖更广泛的 CFL 子集。

7.3.2.3. LL(k) 文法

LL 表示"从左到右(left-to-right)",并构造"最左推导(left-most derivation)"。 \(k\) 表示我们可以向前看至多 \(k-1\) 个字符。每个 S 文法都是 LL 的,但还有更多的文法也是 LL 的。

考虑这个文法:
\(S \rightarrow aSb \mid ab\)

这不是一个 S 文法。但是,这 是 一个 LL 文法。通过查看接下来的两个字符,我们总能知道该应用哪条规则。

如果我们看到 \(ab\) ,则应用 \(S \rightarrow ab\)。
<< 什么被消耗了,什么放到了栈上? >>
否则,应用 \(S \rightarrow aSb\)
<< 什么被消耗了,什么放到了栈上? >>
考虑这个文法:
\(S \rightarrow SS \mid aSb \mid ab\)

这是一个有用的文法!它刻画了嵌套括号。对于任何 \(k\) ,这 不是 一个 LL(k) 文法。(你能看出为什么吗?)

仅仅因为文法不是 LL(k) ,并不意味着该语言不可能是确定性的。关于它为什么不是 LL(k) 的推理,应该有助于你看清如何修正这个文法。

考虑这个文法:\(S \rightarrow aSbS \mid \lambda\)
这是 LL 的。
示例(Example):推导 \(w = abab\)。
\(S \Rightarrow aSbS \Rightarrow abS \Rightarrow abaSbS \Rightarrow ababS \Rightarrow abab\)。
当下一个输入符号是 \(a\) 时,我们必须使用 \(S \rightarrow aSbS\)。
当下一个输入符号是 \(b\) 或字符串为空时,我们必须使用 \(S \rightarrow \lambda\)。
最后一个问题:这个文法接受空字符串。如果我们不喜欢这一点,可以很容易地修正。 只需定义一个新的起始符号,避开 \(\lambda\) 产生式。
\(S_0 \rightarrow aSbS\)
\(S \rightarrow aSbS \mid \lambda\)

   «  7.2. 下推自动机与上下文无关语言   ::   目录   ::   8.1. 上下文无关语言的性质  »

关闭窗口