INT201 Decision,Comuptation and Language / Theory of Computation 决策,计算和语言 / 计算理论 Pt.2 上下文无关文法

1.Introducion

前面我们已经介绍了有穷自动机和正则表达式这两种不同但等价的描述语言的方式,我们也知道了它们的局限性,比如它们无法描述 { 0 n 1 n ∣ n ≥ 0 } \{0^n1^n|n≥0\} { 0n1nn0}
本文将介绍上下文无关文法(context-free grammar)以描述某些应用广泛的具有递归结果特征的语言。
在自然语言中,有的时候名词短语可以出现在动词短语中,而动词短语也可能出现在名词短语种,因此在名词、动词、介词以及它们的短语之间的关系中存在着自然的递归,上下文无关文法有助于整理并理解这些关系,从而支持机器翻译和语言识别等应用。
在程序设计语言中,上下文无关文法被应用于语法分析器(parser)中,用于对程序设计语言的语法进行分析和解释。
与上下文无关文法相关的语言集合称为上下文无关语言(context-free language),它包括所有的正则语言以及许多其他语言类。还将给出上下文无关文法的形式化定义以及研究上下文无关语言的性质。并介绍识别上下文无关语言的机器下推自动机(pushdown automata)。

1.1 什么是上下文无关文法

下面给出一个上下文无关文法的示例,称为 G 1 G_1 G1
A → 0 A 1 A \rightarrow 0A1 A0A1
A → B A \rightarrow B AB
B → # B \rightarrow \# B#
一个文法有一组substitution rule(替换规则)组成,substitution rule又称为production(产生式)。每条规则占一行,由一个符号和一个字符串构成,符号和字符串之间用箭头隔开。符号称为variable(变元),字符串由variable和terminal(终结符)组成,terminal也是一种符号。一般variable用大写字母表示,terminal类似于输入字符,常用小写字母、数字或特殊符号表示。一个variable被指定为start variable(起始变元),通常出现在第一条规则的左边。如例子所示, G 1 G_1 G1有3条规则, A A A B B B是variables, A A A是start variable, 0 0 0 1 1 1 # \# #是terminals。
我们按照以下方法,能根据文法生成其所描述的语言的每一个字符串。
1.写下strat variable,它是第一条规则左边的variable,除非另有所指。
2.取一个已写下的variable,找到以该variable开始的规则,把这个variable替换成规则右边的字符串。
3.重复步骤2,知道写下的字符串没有variable为止。
例如,文法 G 1 G_1 G1生成的字符串 000 # 111 000\#111 000#111。获取一个字符串的替换序列称为derivation(派生)。文法 G 1 G_1 G1生成的字符串 000 # 111 000\#111 000#111的derivation过程如下:
A ⇒ 0 A 1 ⇒ 00 A 11 ⇒ 000 A 111 ⇒ 000 B 111 ⇒ 000 # 111 A ⇒ 0A1 ⇒ 00A11 ⇒ 000A111 ⇒000B111 ⇒ 000\#111 A0A100A11000A111000B111000#111
用上述方法生成的所有字符串构成该文法的语言(language of the grammar)。用 L ( G 1 ) L(G_1) L(G1)表示文法 G 1 G_1 G1的语言,可以看出 L ( G 1 ) = { 0 n # 1 n ∣ n ≥ 0 } L(G_1) = \{0^n\#1^n|n≥0\} L(G1)={ 0n#1nn0}。能够用上下文无关文法生成的语言称为上下文无关语言(CFL,context-free language)。为方便起见,在描述一个上下文无关文法时,对左边variable相同的规则采用缩写的形式,比如上面例子中的 A → 0 A 1 A → 0A1 A0A1 A → B A → B AB可以缩写成 A → 0 A 1 ∣ B A → 0A1|B A0A1∣B符号" ∣ | "表示“或”。

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

context-free grammar(上下文无关文法)是一个4元组 G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V V V是一个有穷集合,称为variables(变元集)。
2. Σ Σ Σ是一个有穷集合,且与 V V V不相交,即 V ∩ Σ = ∅ V ∩ Σ = ∅ VΣ=,称为terminals(终结符集)。
3. R R R是一个有穷规则集(rules),每条规则由一个variable(变元)和一个由variable(变元)及terminal(终止符)组成的字符串构成,因此规则的形式可以表示为 A → w , w ∈ ( V ∪ Σ ) ∗ A → w,w ∈ (V ∪ Σ)^∗ Aw,w(VΣ)
4. S S S是start variable(起始变元),因此 S ∈ V S∈ V SV
u u u, v v v w w w是由variable(变元)及terminal(终结符)组成的字符串, A → w A → w Aw是文法的一条规则,称 u A v uAv uAv yield(生成) u w v uwv uwv,记作 u A v ⇒ u w v uAv ⇒ uwv uAvuwv
如果 u = v u=v u=v,或者存在序列 u 1 , u 2 , . . . , u k u_1,u_2,...,u_k u1,u2,...,uk,使得u⇒u_1 ⇒ u_2 ⇒ . . . ⇒ u_k⇒v,其中 k ≥ 0 k≥0 k0,则称 u u u derive(派生) v v v,记作 u ⇒ ∗ v u\overset{*}⇒v uv。该文法的语言是 { w ∈ Σ ∗ ∣ S ⇒ ∗ w } \{w∈Σ^*|S\overset{*}⇒w \} { wΣSw},即 L ( G ) = { w ∈ Σ ∗ ∣ S ⇒ ∗ w } L(G)=\{w∈Σ^*|S\overset{*}⇒w \} L(G)={ wΣSw},其中 L ( G ) ⊆ Σ ∗ L(G) ⊆ Σ^∗ L(G)Σ
注:rule(规则)的右边是一个字符串,但可以是空字符串,即 ε ε ε,它不会被视作terminal(终结符)但是可以作为一种特殊形式存在。
例如:CFG G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { S } V = \{S\} V={ S}
2. Σ = { 0 , 1 } Σ=\{0,1\} Σ={ 0,1}
3. R : S → 0 S ∣ ε R: S → 0S | ε R:S0Sε
该文法生成的语言是由任意0组成的语言,可记作 { 0 n , n ≥ 0 } \{0^n, n≥0\} { 0n,n0}

1.2.1 上下文无关文法的例子

CFG G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { S } V = \{S\} V={ S}
2. Σ = { a , b } Σ=\{a,b\} Σ={ a,b}
3. R : S → a S b ∣ S S ∣ ε R: S → aSb| SS | ε R:SaSbSSε
该文法生成abab,aaabbb,aababb等字符串。如果把 a a a看作左括号“(”,把 b b b看作右括号“)”,可以看出该 L ( G ) L(G) L(G)是所有正常嵌套的括号字符串构成的语言,这便是具有递归结果特征的语言。

CFG G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { < E X P R > , < T F R M > , < F A C T O R > } V = \{<EXPR>,<TFRM>,<FACTOR>\} V={ <EXPR>,<TFRM>,<FACTOR>}
2. Σ = { a , + , × , ( , ) } Σ=\{a,+,×,(,)\} Σ={ a,+,×,(,)}
3. R : < E X P R > → < E X P R > + < T E R M > ∣ < T E R M > R: <EXPR> →<EXPR> + <TERM>|<TERM> R:<EXPR>→<EXPR>+<TERM><TERM>
< T E R M > → < T E R M > × < F A C T O R > ∣ < F A C T O R > <TERM>→<TERM> × <FACTOR>|<FACTOR> <TERM>→<TERM>×<FACTOR><FACTOR>
< F A C T O R > → ( < E X P R > ) ∣ a <FACTOR>→(<EXPR>)|a <FACTOR>→(<EXPR>)a
该文法可以生成字符串 a + a × a a+a×a a+a×a ( a + a ) × a (a+a)×a (a+a)×a
编译程序把用程序设计语言编写的代码翻译成另一种更适合机器执行的代码。编译程序提取被编译代码的语义,这一过程称为parsing(语法分析)。代码的语法分析树可以表达编译代码的意思。

1.2.2 设计上下文无关文法

构造一个CFG可以通过以下几种方式:
1.一个CFL可能是由多个较简单的CFL组成的,为一个CFL构造CFG可以先将其拆分成几个较简单的CFL,分别构造这些CFL的CFG,最后将它们合并在一起,这一步我们只需要将原来的规则都放在一起,然后加入新的规则 S → S 1 ∣ S 2 ∣ . . . ∣ S k S→S_1|S_2|...|S_k SS1S2∣...∣Sk,其中 S 1 , S 2 , . . . , S k S_1,S_2,...,S_k S1,S2,...,Sk是各个文法的start variable(起始变元)。
例如:我们要得到语言 { 0 n 1 n , n ≥ 0 } ∪ { 1 n 0 n , n ≥ 0 } \{0^n1^n, n≥0\}∪\{1^n0^n, n≥0\} { 0n1n,n0}{ 1n0n,n0}的文法,先构造语言 { 0 n 1 n , n ≥ 0 } \{0^n1^n, n≥0\} { 0n1n,n0}的文法
S 1 → 0 S 1 1 ∣ ε S_1→0S_11|ε S10S11∣ε
再构造语言 { 1 n 0 n , n ≥ 0 } \{1^n0^n, n≥0\} { 1n0n,n0}的文法
S 2 → 1 S 2 0 ∣ ε S_2→1S_20|ε S21S20∣ε
然后加上规则 S → S 1 ∣ S 2 S→S_1|S_2 SS1S2,我们就得到了这个语言的文法:
S → S 1 ∣ S 2 S→S_1|S_2 SS1S2
S 1 → 0 S 1 1 ∣ ε S_1→0S_11|ε S10S11∣ε
S 2 → 1 S 2 0 ∣ ε S_2→1S_20|ε S21S20∣ε

2.如果我们遇到一个语言是正则的,可以先构造对应的DFA,然后根据DFA构造对应的CFG。通过下述方法我们可以把任何一台DFA转换成等价的CFG:对于DFA的每一个状态 q i q_i qi,指定一个变元 R i R_i Ri。对于转移函数 δ ( q i , a ) = q j δ (q_i,a)=q _j δ(qi,a)=qj,则把规则 R i → a R j R_i→aR_j RiaRj加入CFG。如果 q i q_i qi是DFA的接受状态,则把规则 R i → ε R_i→ε Riε加入CFG。设 q 0 q_0 q0是DFA的起始状态,则取R_0作为CFG的起始变元。

3.验证子串。某些上下文无关语言中的字符串有两个“相互联系”的子串,为了检查这两个子串中的一个是否对应于另一个,识别这种语言的机器需要记住其中一个子串的信息,而这个信息是无界的。例如,在语言 { 0 n 1 n , n ≥ 0 } \{0^n1^n, n≥0\} { 0n1n,n0}中我们需要检查字符串中0的个数是否等于1的个数,机器需要记住0的个数。对于这种情况,可以利用 R → u R v R→uRv RuRv形式的规则,它产生的字符串包含 u u u的部分对应包含 v v v的部分,这样就可以解决这个问题了。

4.利用递归。在更复杂的语言中,字符串可能包含一定的结构,这种结构又递归地作为另一种(或者同一种)结构的一部分出现。如前文这个例子CFG G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { < E X P R > , < T F R M > , < F A C T O R > } V = \{<EXPR>,<TFRM>,<FACTOR>\} V={ <EXPR>,<TFRM>,<FACTOR>}
2. Σ = { a , + , × , ( , ) } Σ=\{a,+,×,(,)\} Σ={ a,+,×,(,)}
3. R : < E X P R > → < E X P R > + < T E R M > ∣ < T E R M > R: <EXPR> →<EXPR> + <TERM>|<TERM> R:<EXPR>→<EXPR>+<TERM><TERM>
< T E R M > → < T E R M > × < F A C T O R > ∣ < F A C T O R > <TERM>→<TERM> × <FACTOR>|<FACTOR> <TERM>→<TERM>×<FACTOR><FACTOR>
< F A C T O R > → ( < E X P R > ) ∣ a <FACTOR>→(<EXPR>)|a <FACTOR>→(<EXPR>)a
其中每次出现符号a,就可以递归地产生一个用括号括起来地完整的算数表达式来替换。我们可以把生成这种结构地variable(变元)放在规则中这种结构对应可能出现递归的地方。

我们针对第二条规则给出以下例子:
我们现在有一个语言,这个语言由0和1组成,且101是这个语言字符串的子串,我们现在需要用CFG表示这个语言。
起始我们可以发现这个语言是一个正则语言,这里直接给出它的DFA。
在这里插入图片描述
现在我们试着用前面的规则将这个DFA改成CFG。
每个状态上都有对应的转移函数,所以这些状态都会是一个变元,因此我们的 V = { S , A , B , C } V=\{S,A,B,C\} V={ S,A,B,C}。转移函数的输入是0或者1,这些就是我们的terminals(终结符),我们得到了 Σ = { 0 , 1 } Σ=\{0,1\} Σ={ 0,1}。我们的起始状态是 S S S,所以我们的start variable(起始变元)就是 S S S。现在就剩下rules(规则集)没有确定了,我们将转移函数一个一个变为规则放入我们的规则集。如 S S S在输入 0 0 0后会转移到 S S S,因此规则是 S → 0 S S→0S S0S。同理 S S S在输入 1 1 1后会转移到 A A A,规则就是 S → 1 A S→1A S1A。类似地我们可以得到 A → 1 A ∣ 0 B A→1A|0B A1A∣0B B → 0 S ∣ 1 C B→0S|1C B0S∣1C C → 0 C ∣ 1 C C→0C|1C C0C∣1C而我们的 C C C是接受状态,因此还要加入 C → ε C→ε Cε。我们把上面的答案汇总,就得到了CFG: G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { S , A , B , C } V=\{S,A,B,C\} V={ S,A,B,C}
2. Σ = { 0 , 1 } Σ=\{0,1\} Σ={ 0,1}
3. R : S → 0 S ∣ 1 A R:S→0S|1A R:S0S∣1A
A → 1 A ∣ 0 B A→1A|0B A1A∣0B
B → 0 S ∣ 1 C B→0S|1C B0S∣1C
C → 0 C ∣ 1 C ∣ ε C→0C|1C|ε C0C∣1Cε

由这个第二条规则,我们可以把所有的DFA改成CFG,在这基础上我们可以得到一个推论:每一个正则语言都是上下文无关语言。
这里的证明也比较简单,要证明这个只需要证明每个这个语言里的字符串都会是上下文无关的,也就是可以从start varibale(起始变元)派生出这个字符串,由于每一个转移函数都会是一条规则,所以被原来DFA接受的字符串也会被CFG派生出来。因此这个推论是正确的。

而有的上下文无关语言是正则语言无法表达的,我们可以得到下图,它清晰地说明了这个关系,以及上下文无关语言起始也有它的局限性。
在这里插入图片描述
语言的等级制度如图所示。
在这里插入图片描述

这里还介绍一下上下文无关语言的封闭性,CFLs在union、concatenation、kleene star下是封闭的,但是对intersection(交集)、complementation(补集)下不封闭。

1.3 Chomsky Normal Form(CNF,乔姆斯基范式)

在使用上下文无关文法时,简化的形式往往更方便,一种最简单、最有用的形式称为Chomsky Normal Form(乔姆斯基范式)。
称一个上下文无关文法为Chomsky Normal Form(乔姆斯基范式),如果它的每一个规则具有以下形式:
A → B C A→BC ABC
A → a A→a Aa
S → ε S→ε Sε
其中A、B和C时任意的variable(变元),a是任意的terminal(终结符),S是start variable(起始变元)。
那每一个上下文无关语言都有对应的Chomsky Normal Form(乔姆斯基范式)吗?
答案是是的。
这里介绍定理:任意上下文无关语言都可以用一个Chomsky Normal Form(乔姆斯基范式)的上下文无关文法产生。
证明思路:我们可以分几个阶段把不符合要求的规则替换成等价的符合要求的规则。首先,添加一个新的start variable(起始变元)从而保证原来的start variable(起始变元)不会出现在在规则的右边,然后删除所有形如 A → ε A→ε Aε ε ε ε规则,再删除所有形如 A → B A→B AB的单一规则。在删除时,要对文法做适当的弥补,以确保仍然产生相同的语言。最后,把所有留下来的规则转换成适当的形式。
证明:第一步,添加一个新的start variable(起始变元) S 0 S_0 S0和规则 S 0 → S S_0→S S0S,其中 S S S是原来的起始变元。这样可以保证原来的start variable(起始变元)不会出现在在规则的右边。
第二步,考虑所有的 ε ε ε规则。删除一条 ε ε ε规则 A → ε A→ε Aε,这里 A A A不是start variable(起始变元),然后对在规则右边出现的每一个 A A A,删去这个 A A A后得到一条新的规则。换言之,如果 B → x A y B→xAy BxAy是一条规则,其中x和y是由variable(变元)和terminal(终结符)组成的字符串,则删除 A → ε A→ε Aε后需要添加规则 B → x y B→xy Bxy。对 A A A的每一次出现都要这样进行,比如对于规则 B → A x A B→AxA BAxA,删去后需要添加 B → x A B→xA BxA B → A x B→Ax BAx B → x B→x Bx。如果有规则是 B → A B→A BA,则需要添加 B → ε B→ε Bε,除非前面已经删除过规则 B → ε B→ε Bε。重复进行以上步骤,直到删除所有不包括不包括start variable(起始变元)的 ε ε ε规则。
第三步,处理所有的单一规则。删除一条单一规则 A → B A→B AB,然后做出对应补充,只要有一条规则 B → x B→x Bx,就要添加规则 A → x A→x Ax,除非 A → x A→x Ax是已在前面被删除的单一规则,和前文一样x是由variable(变元)和terminal(终结符)组成的字符串。重复进行以上步骤,直到删除所有的单一规则。
最后,把所有留下的规则转换成适当的形式。把每一条规则 A → x 1 x 2 . . . x k A→x_1x_2...x_k Ax1x2...xk替换成规则 A → x 1 A 1 A→x_1A_1 Ax1A1 A 1 → x 2 A 2 A_1→x_2A_2 A1x2A2 A 2 → x 3 A 3 A_2→x_3A_3 A2x3A3,…, A k − 2 → x k − 1 A k A_{k-2}→x_{k-1}A_k Ak2xk1Ak,其中 k ≥ 3 k≥3 k3,每一个 x i x_i xi是一个variable(变元)或terminal(终结符), A i A_i Ai是新的variable(变元)。用新变元 B i B_i Bi替换上面规则中的terminal(终结符) x i x_i xi,并添加新的规则 B i → x i B_i→x_i Bixi。比如 A → B 1 B 2 B 3 A→B_1B_2B_3 AB1B2B3删除后添加 A → B 1 A 1 A→B_1A_1 AB1A1 A 1 → B 2 B 3 A_1→B_2B_3 A1B2B3。又比如 A → a b A→ab Aab删除后添加 A → B 1 B 2 A→B_1B_2 AB1B2 B 1 → a B_1→a B1a B 2 → b B_2→b B2b
下面我们看一个例子:将CFG G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { A , B } V=\{A,B\} V={ A,B}
2. Σ = { 0 , 1 } Σ=\{0,1\} Σ={ 0,1}
3. A A A是start variable(起始变元)
4. R : A → B A B ∣ B ∣ ε R:A→BAB|B|ε R:ABABBε
B → 00 ∣ ε B→00|ε B00∣ε
第一步,去掉start variable(起始变元)在右边的规则,因此我们得到:
S → A S→A SA
A → B A B ∣ B ∣ ε A→BAB|B|ε ABABBε
B → 00 ∣ ε B→00|ε B00∣ε
第二步,去掉所有 ε ε ε规则,其中又有两条规则需要删除。
去掉 A → ε A→ε Aε,由于我们有 S → A S→A SA A → B A B A→BAB ABAB
所以我们得到:
S → A ∣ ε S→A|ε SAε
A → B A B ∣ B ∣ B B A→BAB|B|BB ABABBBB
B → 00 ∣ ε B→00|ε B00∣ε
去掉 B → ε B→ε Bε,由于我们有 A → B A B A → BAB ABAB, A → B A → B AB, A → B B A → BB ABB,所以我们得到:
S → A ∣ ε S→A|ε SAε
A → B A B ∣ B ∣ B B ∣ A B ∣ B A ∣ A A→BAB|B|BB|AB|BA|A ABABBBBABBAA
B → 00 B→00 B00
第三步,去掉所有的单一规则,其中又有几条规则需要删除。
去掉 A → A A→A AA,由于是左右都是一样的字符串,所以不影响别的规则:
S → A ∣ ε S→A|ε SAε
A → B A B ∣ B ∣ B B ∣ A B ∣ B A A→BAB|B|BB|AB|BA ABABBBBABBA
B → 00 B→00 B00
去掉 S → A S→A SA,得到:
S → ε ∣ B A B ∣ B ∣ B B ∣ A B ∣ B A S→ε|BAB|B|BB|AB|BA SεBABBBBABBA
A → B A B ∣ B ∣ B B ∣ A B ∣ B A A→BAB|B|BB|AB|BA ABABBBBABBA
B → 00 B→00 B00
去掉 S → B S→B SB,得到:
S → ε ∣ B A B ∣ B B ∣ A B ∣ B A ∣ 00 S→ε|BAB|BB|AB|BA|00 SεBABBBABBA∣00
A → B A B ∣ B ∣ B B ∣ A B ∣ B A A→BAB|B|BB|AB|BA ABABBBBABBA
B → 00 B→00 B00
去掉 A → B A→B AB,得到:
S → ε ∣ B A B ∣ B B ∣ A B ∣ B A ∣ 00 S→ε|BAB|BB|AB|BA|00 SεBABBBABBA∣00
A → B A B ∣ B B ∣ A B ∣ B A ∣ 00 A→BAB|BB|AB|BA|00 ABABBBABBA∣00
B → 00 B→00 B00
第四步,将剩余不符合规则的规则转换成适当的形式。先将右边符号数大于2的进行拆分。
去掉 S → B A B S→BAB SBAB,得到:
S → ε ∣ B B ∣ A B ∣ B A ∣ 00 ∣ B A 1 S→ε|BB|AB|BA|00|BA_1 SεBBABBA∣00∣BA1
A → B A B ∣ B B ∣ A B ∣ B A ∣ 00 A→BAB|BB|AB|BA|00 ABABBBABBA∣00
B → 00 B→00 B00
A 1 → A B A_1→AB A1AB
去掉 A → B A B A→BAB ABAB,得到:
S → ε ∣ B B ∣ A B ∣ B A ∣ 00 ∣ B A 1 S→ε|BB|AB|BA|00|BA_1 SεBBABBA∣00∣BA1
A → B B ∣ A B ∣ B A ∣ 00 ∣ B A 2 A→BB|AB|BA|00|BA_2 ABBABBA∣00∣BA2
B → 00 B→00 B00
A 1 → A B A_1→AB A1AB
A 2 → A B A_2→AB A2AB
还有一些是符号数是2,但是里面有terminal(终结符)存在。
去掉 S → 00 S→00 S00,得到:
S → ε ∣ B B ∣ A B ∣ B A ∣ B A 1 ∣ A 3 A 3 S→ε|BB|AB|BA|BA_1|A_3A_3 SεBBABBABA1A3A3
A → B B ∣ A B ∣ B A ∣ 00 ∣ B A 2 A→BB|AB|BA|00|BA_2 ABBABBA∣00∣BA2
B → 00 B→00 B00
A 1 → A B A_1→AB A1AB
A 2 → A B A_2→AB A2AB
A 3 → 0 A_3→0 A30
去掉 A → 00 A→00 A00,得到:
S → ε ∣ B B ∣ A B ∣ B A ∣ B A 1 ∣ A 3 A 3 S→ε|BB|AB|BA|BA_1|A_3A_3 SεBBABBABA1A3A3
A → B B ∣ A B ∣ B A ∣ B A 2 ∣ A 4 A 4 A→BB|AB|BA|BA_2|A_4A_4 ABBABBABA2A4A4
B → 00 B→00 B00
A 1 → A B A_1→AB A1AB
A 2 → A B A_2→AB A2AB
A 3 → 0 A_3→0 A30
A 4 → 0 A_4→0 A40
去掉 B → 00 B→00 B00,得到:
S → ε ∣ B B ∣ A B ∣ B A ∣ B A 1 ∣ A 3 A 3 S→ε|BB|AB|BA|BA_1|A_3A_3 SεBBABBABA1A3A3
A → B B ∣ A B ∣ B A ∣ B A 2 ∣ A 4 A 4 A→BB|AB|BA|BA_2|A_4A_4 ABBABBABA2A4A4
B → A 5 A 5 B→A_5A_5 BA5A5
A 1 → A B A_1→AB A1AB
A 2 → A B A_2→AB A2AB
A 3 → 0 A_3→0 A30
A 4 → 0 A_4→0 A40
A 5 → 0 A_5→0 A50

2. Pushdown automata(下推自动机)

2.1 上下文无关语言的封闭性

前文我们介绍过上下文无关语言的封闭性,CFLs在union、concatenation、kleene star下是封闭的,但是对intersection(交集)、complementation(补集)下不封闭。这里我们进行详细叙述。

1.在union(交集)下封闭。
这个证明其实可以参考设计CFG的第一条,我们只需要将它原来的两个CFG的状态集、终结符集取并集,然后规则集取并集的基础上再添加一个新的规则包含新的起始状态 S → S 1 ∣ S 2 S→S_1|S_2 SS1S2即可。
例如: L 1 = { a n b n c m , m ≥ 0 , n ≥ 0 } L_1 = \{a^nb^nc^m,m≥0, n≥0\} L1={ anbncmm0,n0}
L 2 = { a n b m c m , m ≥ 0 , n ≥ 0 } L_2 = \{a^nb^mc^m,m≥0, n≥0\} L2={ anbmcmm0,n0}
L 3 = L 1 ∪ L 2 = { a i b j c k , i ≥ 0 , j ≥ 0 , k ≥ 0 , i = j 或 j = k } L_3 = L_1 \cup L_2 = \{a^ib^jc^k,i\geq 0, j\geq 0, k\geq 0, i=j或 j=k\} L3=L1L2={ aibjcki0,j0,k0,i=jj=k}

2.在concatenation(连接)下封闭。
这个证明也和前面类似,我们这次规则集添加的新的规则是 S → S 1 S 2 S→S_1S_2 SS1S2即可。
例如: L 1 = { a n , n ≥ 0 } L_1 = \{a^n, n≥0\} L1={ ann0}
L 2 = { b m , m ≥ 0 } L_2 = \{b^m,m≥0\} L2={ bmm0}
L 3 = L 1 L 2 = { a i b j , ∣ , i ≥ 0 , j ≥ 0 } L_3 = L_1L_2 = \{a^ib^j , | , i\geq 0, j\geq 0\} L3=L1L2={ aibj,,i0,j0}

3.在kleene star(星号)下封闭。
这个证明和上面的连接类似,我们这次规则集添加的新的规则是 S → S 1 S ∣ ε S → S_1S|ε SS1Sε,我们需要添加一个 ε ε ε然后只要是原来CFL中的字符串,连接在一起就可以是新的CFL中的字符串。
例如: L 1 = { a n b n , n ≥ 0 } L_1 = \{a^nb^n, n≥0\} L1={ anbnn0}
L 2 = L 1 ∗ = { a n 1 b n 1 . . . a n i b n i , n ≥ 0 , i ≥ 1 } L_2= L_1^* = \{a^{n_1}b^{n_1}...a^{n_i}b^{n_i}, n≥0, i≥1\} L2=L1={ an1bn1...anibnin0,i1}

2.2 上下文无关文法的歧义性

有时在一个文法中能够用几种不同的方式产生出同一个字符串,这样的字符串会有即可不同的语法分析树,对于几个不同的含义。(或者说不同的派生对应不同的意思)。如果文法以不同的方式产生同一个字符串,则称文法歧义地(ambiguously)产生这个字符串。比如前文例子CFG G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { < E X P R > , < T F R M > , < F A C T O R > } V = \{<EXPR>,<TFRM>,<FACTOR>\} V={ <EXPR>,<TFRM>,<FACTOR>}
2. Σ = { a , + , × , ( , ) } Σ=\{a,+,×,(,)\} Σ={ a,+,×,(,)}
3. R : < E X P R > → < E X P R > + < T E R M > ∣ < T E R M > R: <EXPR> →<EXPR> + <TERM>|<TERM> R:<EXPR>→<EXPR>+<TERM><TERM>
< T E R M > → < T E R M > × < F A C T O R > ∣ < F A C T O R > <TERM>→<TERM> × <FACTOR>|<FACTOR> <TERM>→<TERM>×<FACTOR><FACTOR>
< F A C T O R > → ( < E X P R > ) ∣ a <FACTOR>→(<EXPR>)|a <FACTOR>→(<EXPR>)a
它可以歧义地产生字符串 a + a × a a+a×a a+a×a,如图。
在这里插入图片描述
又比如这个例子CFG G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { S , N P , V P , D e t , N o m i n a l , N o u n , P P , P r e p o s i t i o n , V e r b } V = \{S, NP, VP, Det, Nominal, Noun, PP, Preposition, Verb\} V={ S,NP,VP,Det,Nominal,Noun,PP,Preposition,Verb}
2. Σ = { T h e , s p y , s a w , c o p , w i t h , a , t e l e s c o p e } Σ=\{The, spy, saw, cop, with, a, telescope\} Σ={ The,spy,saw,cop,with,a,telescope}
3. R : S → N P   V P R: S →NP \ VP R:SNP VP
N P → D e t   N o m i n a l NP →Det \ Nominal NPDet Nominal
N o m i n a l → N o u n ∣ N o m i n a l   P P Nominal →Noun| Nominal \ PP NominalNounNominal PP
V P → V P   P P ∣ V e r b   N P VP →VP \ PP| Verb \ NP VPVP PPVerb NP
P P → P r e p o s i t i o n   N P PP →Preposition \ NP PPPreposition NP
D e t → T h e ∣ a Det→The|a DetThea
N o u n → s p y ∣ c o p ∣ t e l e s c o p e Noun→spy|cop|telescope Nounspycoptelescope
V e r b → s a w Verb→saw Verbsaw
P r e p o s i t i o n → w i t h Preposition→with Prepositionwith
它也会歧义地产生字符串The spy saw a cop with a telescope。如图。
在这里插入图片描述

2.3 PDA(pushdown automata, 下推自动机)

pushdown automata(下推自动机)能力于上下文无关文法等价。因此,在证明一个语言是上下文无关的时候,有两种选择:可以给出生成它的上下文无关文法,也可以给出识别它的下推自动机。
因此被pushdown automata(下推自动机)所接受的语言是context-free languages(上下文无关语言)。
pushdown automata(下推自动机)和有限状态自动机,但是PDAs有一个称为stack(栈)的额外设备,这也因此带来了两个关于stack(栈)的操作:push(入栈)将一个符号写入栈顶,pop(出栈)将一个栈顶的符号删除。(关于栈的问题由于数据结构教过,这里便不再详细叙述)。
栈的作用体现在它能保存无限的信息量。因为有限状态自动机不能用有限的状态储存无限的字符串,所以他无法识别 { 0 n 1 n ∣ n ≥ 0 } \{0^n1^n|n≥0\} { 0n1nn0},而PDA可以用栈保存它看见的0的个数,从而能够识别这个语言,因此,栈的无界性使得PDA可以保存大小没有限制的数。对于 { 0 n 1 n ∣ n ≥ 0 } \{0^n1^n|n≥0\} { 0n1nn0}语言,PDA会读取输入串的符号,每读1个0,把它推入栈,一旦看见1之后,每读一个1,把一个0弹出栈,当栈中的0被清空时恰好读完输入串,则接受这个输入。如果在还有1没有读的时候栈已变空,或者在栈中还有0的时候1已经读完了,或者0出现在1的后面,则拒绝这个输入。
有限状态自动机有确定型的和非确定型的,下推自动机也有确定型的和非确定型的。确定型有限状态自动机与非确定型有限状态自动机能识别相同的语言,但非确定型下推自动机能识别的语言可能确定型下推自动机不能识别。由于非确定型下推自动机等价于上下文无关文法,所以我们集中讨论非确定型下推自动机。

2.3.1 下推自动机的形式化定义

除了栈以外,下推自动机的形式化定义类似于有限状态自动机。栈是一个存放符号的设备,这些符号取自某个字母表。机器对于它的输入和栈,可以使用不同的字母表,因此需要同时指定一个输入字母表 Σ Σ Σ和一个栈字母表 Γ Γ Γ
任何自动机的形式化定义的核心都是转移函数,因为要用它描述自动机的动作。给定 Σ ε = Σ ∪ { ε } Σ_ε = Σ ∪ \{ε\} Σε=Σ{ ε} Γ ε = Γ ∪ { ε } Γ_ε = Γ ∪ \{ε\} Γε=Γ{ ε},转移函数的定义域为 Q × Σ ε × Γ ε Q × Σ_ε× Γ_ε Q×Σε×Γε。于是,在当前状态下,下一个读到的输入符号(读取输入符号的时候我们会把这个过程当作一个tape(带子)将要输入的字符串分成一个一个符号,箭头会一格一格沿着tape(带子)移动和栈顶的符号决定了下推自动机的下一个动作。这两个符号都可以是 ε ε ε,使机器能够在不读输入符号或者不读栈中符号的情况下做动作。
关于转移函数的值域,需要考虑在特定情形下允许自动机做什么,他可能进入某个新的状态并可能在栈顶写入一个符号。通过返回 Q Q Q的一个成员和 Γ ε Γ_ε Γε的一个成员,即 Q × Γ ε Q × Γ_ε Q×Γε的一个成员,函数 δ δ δ可以指出这个动作。由于在这个模型中允许非确定性,所以可能有若干个合法的下一个动作,转移函数通过返回 Q × Γ ε Q × Γ_ε Q×Γε的一个子集,即 P ( Q × Γ ε ) P(Q × Γ_ε) P(Q×Γε)的一个成员来体现转移动作的非确定性。所以这些子集合合并在一起,得到转移函数 δ δ δ的形式为 δ : Q × Σ ε × Γ ε → P ( Q × Γ ε ) δ:Q × Σ_ε× Γ_ε→P(Q × Γ_ε) δQ×Σε×ΓεP(Q×Γε)
或者从这张图理解转移函数,PDA本来处于状态 q i q_i qi,它读取了符号a,并从栈中pop(弹出)了一个符号b,然后它转移到了状态 q j q_j qj,并向栈中push(推入)了一个符号c。因此 a ∈ Σ ε a ∈ Σ_ε aΣε, b ∈ Γ ε b ∈ Γ_ε bΓε, c ∈ Γ ε c ∈ Γ_ε cΓε,且如果 a = ε a=ε a=ε,代表没有读取任何输入符号。如果 b = ε b=ε b=ε,代表没有符号从栈中弹出。如果 c = ε c=ε c=ε,代表没有符号推入栈中。如果 c = u 1 u 2 . . . u k c=u_1u_2...u_k c=u1u2...uk即如果c是一系列符号组成的字符串,那现在栈顶的符号将是 u k u_k uk
在这里插入图片描述

因此我们现在得到了下推自动机的形式化定义:
pushdown automata(下推自动机)是6元组 ( Q , Σ , Γ , δ , q 0 , F ) (Q, Σ, Γ, δ, q_0, F) (Q,Σ,Γ,δ,q0,F),这里 Q , Σ , Γ , F Q, Σ, Γ, F Q,Σ,Γ,F都是有穷集合,且
1. Q Q Q是状态集。
2. Σ Σ Σ是输入字母表。
3. Γ Γ Γ是栈字母表。
4. δ : Q × Σ ε × Γ ε → P ( Q × Γ ε ) δ:Q × Σ_ε× Γ_ε→P(Q × Γ_ε) δQ×Σε×ΓεP(Q×Γε)是转移函数。
5. q 0 ∈ Q q_0∈Q q0Q是起始状态。
6. F ⊆ Q F⊆Q FQ是接受状态集。
一台下推自动机 M = ( Q , Σ , Γ , δ , q 0 , F ) M=(Q, Σ, Γ, δ, q_0, F) M=(Q,Σ,Γ,δ,q0,F)的计算过程如下:它接受输入 w w w,如果能够把 w w w写成 w = w 1 w 2 . . . w m w = w_1w_2...w_m w=w1w2...wm,这里每一个 w i ∈ Σ ε w_i∈Σ_ε wiΣε,并且存在状态序列 r 0 , r 1 , . . . , r m ∈ Q r_0,r_1,...,r_m∈Q r0,r1,...,rmQ和字符串序列 s 0 , s 1 , . . . , s m ∈ Γ ∗ s_0,s_1,...,s_m∈Γ^* s0,s1,...,smΓ满足下述3个条件:
1. r 0 = q 0 r_0 = q_0 r0=q0 s 0 = ε s_0 = ε s0=ε,该条件表示 M M M从起始状态和空栈开始。
2.对于 i = 0 , . . . , m − 1 i=0,...,m-1 i=0,...,m1,有 ( r i + 1 , b ) ∈ δ ( r i , w i + 1 , a ) (r_{i+1},b)∈δ(r_i,w{i+1},a) (ri+1,b)δ(ri,wi+1,a)(前面的是后面转移函数最后结果的一个子集),其中 s i = a t , s i + 1 = b t , a , b ∈ Γ ε , t ∈ Γ ∗ s_i=at, s_{i + 1} = bt,a,b∈Γ_ε,t∈Γ^* si=at,si+1=bt,a,bΓε,tΓ(本来栈中的字符串是at,但是转移的时候弹出了a,然后推入了b,因此转移后栈中的字符串是bt了)。该条件说明 M M M在每一步都完全按照当时的状态、栈顶符号和下一个输入符号动作。
3. r m ∈ F r_m∈F rmF,该条件说明在输入结束时出现一个接受状态。
注:PDA是否接受某个字符串与栈中的元素无关,接受与否只与最终状态是否是接收状态有关。如果一个PDA在其状态为接收状态且栈元素满足某些条件被定义为接受状态,我们可以添加弹出转换来使其称为等效的标准PDA。(下一节将介绍)
PDA的形式化定义起始没有提供检验空栈的直接手段。这里可以使用一个特殊符号 $ 放入栈中,就能够起到检验空栈的作用,当机器再次看到 $ 的时候,就知道栈实际上已经空了。在PDA的非形式化描述中用这种方式判断空栈。
类似地,PDA不能直接检验是否到达输入的末端,只有PDA位于输入末端的时,接收状态才起作用。于是,我们现在假设PDA能够检验是否到达输入的末端,方式同样通过 $ 进行判断。
下面我们看一个PDA的例子这个例子识别语言 { 0 n 1 n , n ≥ 0 } \{0^n1^n,n≥0\} { 0n1n,n0} M = ( Q , Σ , Γ , δ , q 0 , F ) M=(Q, Σ, Γ, δ, q_0, F) M=(Q,Σ,Γ,δ,q0,F)
1. Q = { q 1 , q 2 , q 3 , q 4 } Q = \{q_1, q_2, q_3, q_4\} Q={ q1,q2,q3,q4}
2. Σ = { 0 , 1 } Σ = \{0, 1\} Σ={ 0,1}
3. Γ = { 0 , Γ= \{0, Γ={ 0, $ } \} }
4. q 1 q_1 q1是起始状态
5. F = { q 1 , q 4 } F=\{q_1, q_4\} F={ q1,q4}
6. δ : Q × Σ ε × Γ ε → Q × Γ ε ∗ δ: Q × Σ_ε× Γ_ε → Q × Γ_ε^∗ δ:Q×Σε×ΓεQ×Γε
δ δ δ由下表给出,表中空白项表示 ∅ ∅
在这里插入图片描述
用状态图描述PDA如下。
在这里插入图片描述
由于n可以是0,所以起始状态 q 1 q_1 q1也是一个接受状态。给一个特殊符号以标记栈底从而进行后续是否到达输入字符的末端的判断,在 q 2 q_2 q2状态下每有一个0输入,便储存到栈中,然后读到一个1便进入下一个状态 q 3 q_3 q3去判断0和1的数量是否一致,然后利用特殊符号 $ 去判断是否是输入字符的末端,从而决定是否接受这个字符串。
我们借这个图再复习一遍转移函数,我们用 a , b → c a,b→c a,bc表示当机器从输入中读到 a a a时可以用 c c c替换栈顶的符号 b b b a a a b b b c c c中的任何一个都可以是 ε ε ε。如果 a a a ε ε ε,则机器做这个转移,而不读取输入中的任何符号。如果 b b b ε ε ε,则机器做这个转移,而不读栈中的任何符号,也不从栈中弹出如何符号。如果 c c c ε ε ε,则机器做这个转移,而不在栈中写任何符号。
我们把PDA M M M所接受的所有输入字符串构成的集合称为被 M M M所识别的语言,用 L ( M ) L(M) LM来表示。
我们再看两个PDA的例子,例一是PDA识别的语言是 { w w R ∣ w ∈ { 0 , 1 } ∗ } \{ww^R|w∈\{0,1\}^*\} { wwRw{ 0,1}}
开始时,把读到的符号推入占中,在每一步非确定性地猜想已经到达字符串地中点,然后变成把读到的每一个符号弹出栈,检查在输入中和在栈顶读到的符号是否一样。如果他们总是一样的,并且当输入结束时栈同时被清空,则接受,否则拒绝。
最后的状态图如图所示。
在这里插入图片描述
这个例子也说明了有的语言需要一台非确定型PDA。
例二也使用到了非确定性,例二的PDA识别的语言是 { a i b j c k ∣ i , j , k ≥ 0 且 i = j 或 i = k } \{a^ib^jc^k|i,j,k≥ 0且i=j或i=k\} { aibjcki,j,k0i=ji=k}
识别该语言的PDA先读a,并且把a推入栈。当读完a时,机器把它们全部放到栈中,以便能够把它们与b或c进行匹配。由于机器不知道下面a是与b匹配还是与c匹配,需要想点办法,在这里就需要使用到非确定性。
利用非确定性,我们就能猜想这台PDA的a是与b匹配还是与c匹配,因此机器有两个非确定性的分支,每一种分支对应上面的猜想。如果有一个匹配成功,则对应的分支接受,从而整个机器接受。
在这里插入图片描述

3. PDA和CFG的等价性以及CFL的Pumping Lemma(泵引理)

3.1 Equivalence between PDA and CFG(PDA和CFG的等价性)

CFG(上下文无关文法)与PDA(下推自动机)在能力上是等价的,它们都能够描述上下文无关语言类。由前面的定义可知上下文无关语言是能用上下文无关文法描述的语言,因此本节需要证明:一个语言是上下文无关的,当且仅当存在一台下推自动机识别它。
因此我们需要证明两个方向,从而证明这个定理。
我们先证明比较简单的从左到右,也就是CFG到PDA。
引理:如果一个语言是上下文无关的,则存在一台下推自动机识别它。
证明思路:设 A A A是一个CFL,根据定义,存在一个CFG G G G产生它。我们要说明如何把 G G G转换成一台等价的PDA P P P
通过确定是否存在关于输入 w w w的派生,当 G G G产生 w w w时,PDA P P P接受这个输入。派生是当文法产生一个字符串时所做的替换序列,派生的每一步产生一个variable(变元)和terminal(终结符)组成的intermediate string(中间字符串)。设计 P P P,以确定是否有一系列使用 G G G的规则替换,能够从start variable(起始变元)导出 w w w
检验是否有关于 w w w的派生的困难在于判断要做的替换,PDA的非确定性能够保证它猜想出正确的替换序列,在派生的每一步,非确定性地选择关于某个variable(变元)的一条规则,并且对这个variable(变元)做替换。
PDA P P P开始时把start variable(起始变元)写入它的栈,一个接一个地做替换,经过一系列地中间字符串,最终它可能达到一个仅含有terminal(终结符)的字符串,这表示它用文法 G G G派生出一个字符串。如果这个字符串与它接收到的输入相同,则 P P P接受它。
P的非形式描述如下:
1.把标记符 $ 和start variable(起始变元)放入栈中。
2.重复下述步骤:
a.如果栈顶是variable(变元) A A A,则非确定地选择一个关于 A A A的规则,并且把 A A A替换成这条规则右边的字符串。
b.如果栈顶是终结符 a a a,则读取下一个输入符号,并且把它与 a a a进行比较。如果它们匹配,则弹出 a a a,继续重复,如果它们不匹配,则这个非确定性分支拒绝。
c.如果栈顶是符号 $ ,则进入接收状态,如果此刻输入已全部读完,则接受这个输入串。
下图帮助你更好理解上面的描述。
在这里插入图片描述
现在我们给出证明完善这个PDA的构造细节:对于PDA P = ( Q , Σ , Γ , δ , q s t a r t , F ) P=(Q, Σ, Γ, δ, q_{start}, F) P=(Q,Σ,Γ,δ,qstart,F),我们为了构造地更清楚一些,采用一种缩写记号表示转移函数,用这种记号方式,机器能够一步把一个字符串写入栈内。同时引入附加的状态,实现每次写入这个字符串地一个符号,从而模拟出一次写入字符串的动作。在下述形式构造中实现了这样的模拟。
q q q r r r是PDA的状态, a ∈ Σ ε a∈Σ_ε aΣε, s ∈ Γ ε s∈Γ_ε sΓε。我们要求PDA读 a a a并且弹出 s s s时,从 q q q r r r,而且要它同时把整个字符串 u = u 1 . . . u l u=u_1...u_l u=u1...ul推入栈。可以如下完成这个动作:引入新的状态 q 1 , . . . , q l − 1 q_1,...,q_{l-1} q1,...,ql1,并且令转移函数如下:
δ ( q , a , s ) δ(q,a,s) δ(q,a,s)包含 ( q 1 , u l ) (q_1,u_l) (q1,ul)
δ ( q 1 , ε , ε ) = { q 2 , u l − 1 } δ(q_1,ε,ε)=\{q_2, u_{l-1}\} δ(q1,ε,ε)={ q2,ul1}, δ ( q 2 , ε , ε ) = { q 3 , u l − 2 } δ(q_2,ε,ε)=\{q_3, u_{l-2}\} δ(q2,ε,ε)={ q3,ul2} δ ( l 1 , ε , ε ) = { r , u 1 } δ(l_1,ε,ε)=\{r, u_1\} δ(l1,ε,ε)={ r,u1}
使用记号 ( r , u ) ∈ δ ( q , a , s ) (r,u)∈δ(q,a,s) (r,u)δ(q,a,s)表示当 q q q P P P的状态, a a a是下一个输入符号以及 s s s是栈顶符号时,PDA P能够读 a a a和弹出 s s s,然后把字符串 u u u推入栈和转移到状态 r r r
在这里插入图片描述
P P P的状态集 Q = { q 1 , q 2 , q 3 } ∪ E Q=\{q_1,q_2,q_3\}∪E Q={ q1,q2,q3}E,这里 E E E是实现刚才描述的缩写所需要的状态集合,开始状态为 q 1 q_1 q1,只有一个接受状态 q 3 q_3 q3
转移函数定义如下。从初始化栈开始,把符号 $ 和 S S S推入栈,实现步骤1: δ ( q 1 , ε , ε ) = { ( q 2 , S δ(q_1, ε,ε)=\{(q_2, S δ(q1,ε,ε)={(q2,S$ ) } )\} )},然后进行步骤2主循环中的转移。
首先,处理情况(a),这时栈顶是一个variable(变元)。令 δ ( q 2 , ε , A ) = { ( q 2 , w } δ(q_2, ε, A)=\{(q_2, w\} δ(q2,ε,A)={(q2,w}其中 A → w A→w Aw R R R中的一条规则。
接着,处理情况(b),这时栈顶是一个terminal(终结符)。令 δ ( q 2 , a , a ) = { ( q 2 , ε } δ(q_2, a, a)=\{(q_2, ε\} δ(q2,a,a)={(q2,ε}
最后,处理情况©,这时栈顶是一个空栈标记符 $ 。令 δ ( q 2 , ε , δ(q_2, ε, δ(q2,ε, $ ) = { ( q 3 , ε } )=\{(q_3, ε\} )={(q3,ε}
因此我们得到了最后的状态图:
在这里插入图片描述
现在我们看一个例子,将这个CFG转换为PDA, G = ( V , Σ , R , S ) G = (V, Σ, R, S) G=(V,Σ,R,S)
1. V = { S , T } V=\{S, T\} V={ S,T}
2. Σ = 0 , 1 Σ={0, 1} Σ=0,1
3. R : S → 0 T S 1 ∣ 1 T 0 , T → 1 R:S → 0TS1 | 1T0, T → 1 R:S0TS1∣1T0,T1
它的转换如下:先将其写成如下结果
在这里插入图片描述
再补充intermediate states(中间状态),结果如下
在这里插入图片描述
我们现在证明另一个方向,将PDA转换成CFG,也就是设计模拟自动机的文法。
引理:如果一个语言被一台下推自动机识别,则它是上下文无关的。
证明思路:现有一台PDA P P P,要构造一台CFG G G G,它产生 P P P接受的所有字符串。换言之,如果一个字符串能够使 P P P从它的起始状态转移到一个接收状态,则 G G G应该产生这个字符串。
为了获得这个结果,对于 P P P的每一对状态 p p p q q q,文法有一个变元 A p q A_{pq} Apq它产生所有能够把 P P P p p p和空栈一块带到 q q q和空栈的字符串。可以看出不管栈的内容在状态 P P P时是什么,这样的字符串也能够把从 P P P p p p带到 q q q,而且保证栈的内容在状态 q q q和在状态 p p p时一样。
首先,为了简化工作,对 P P P进行简化,使其具有以下三个特点:
1.有唯一的接受状态 q a c c e p t q_{accept} qaccept
2.在接受之前清空栈。
3.每一个转移把一个符号推入栈或者把一个符号弹出栈,但不同时做这两个动作。
前两点比较简单,第三点需要将 P P P的每一个同时推入和弹出的转移替换成两个转移,中间要经过一个新的状态;把每一个既不推入也不弹出的转移替换成两个转移,先推入任意一个栈符号,然后再把它弹出。
要设计 G G G,使得 A p q A_{pq} Apq产生把 P P P p p p带到 q q q并且以空栈开始和结束的所有字符串,必须了解 P P P对这样的字符串如何运行。对于任何一个这样的字符串 x x x,因为 P P P的每一个动作或者是推入或者是弹出,但是对空栈不能弹出,所以 P P P x x x的第一个动作一定是推入。类似地,因为在结束时栈是空的,所以对 x x x地最后一个动作一定是弹出。
在P对x的计算过程中可能出现两种情况:仅在计算的开始和结束时,栈可能是空的;或者除开始和结束时之外,在计算中的某个地方,栈变成空的。如果是前一种情况,最后弹出的符号一定就是开始时推入的那个符号。用规则 A p q → a A r s b A{pq}→aA{rs}b ApqaArsb模拟前一种情况,其中 a a a是在做第一个动作时读到的输入符号, b b b是在做最后一个动作时读到的输入符号, r r r是跟在 p p p后面的状态, s s s q q q的前一个状态。用规则 A p q → A p r A r q A{pq}→A{pr}A{rq} ApqAprArq模拟后一种情况,其中 r r r是栈在计算中间变成空的时候的状态。
证明:设 P = ( Q , Σ , Γ , δ , q 0 , { q a c c e p t } ) P=(Q, Σ, Γ, δ, q_0, \{q_{accept}\}) P=(Q,Σ,Γ,δ,q0,{ qaccept}),要构造 G G G G G G的变元集是 V = { A p q ∣ p , q ∈ Q } V = \{A{pq}|p, q∈Q\} V={ Apqp,qQ},终结符集 Σ = Σ Σ=Σ Σ=Σ,起始变元是 A q 0 q a c c e p t A_{q_0q_{accept}} Aq0qaccept。下面三点描述了 G G G的规则:
1.对每一个 p ∈ Q p∈Q pQ,把规则 A p p → ε A_{pp}→ε Appε放入 G G G中。
2.对每一个 p , q , r ∈ Q p,q,r∈Q p,q,rQ,把规则 A p q → A p r A r q A_{pq}→A{pr}A{rq} ApqAprArq放入 G G G中。
3.对每一个 p , q , r , s ∈ Q , u ∈ Γ p,q,r,s∈Q,u∈Γ p,q,r,sQ,uΓ a , b ∈ Σ ε a,b∈Σ_ε a,bΣε,如果 δ ( p , a , ε ) δ(p,a,ε) δ(p,a,ε)包含 ( r , u ) (r,u) (r,u) δ ( s , b , u ) δ(s,b,u) δ(s,b,u)包含 ( q , ε ) (q,ε) (q,ε),则把规则 A p q → a A r s b A_{pq}→aA_{rs}b ApqaArsb放入 G G G中。
在这里插入图片描述
在这里插入图片描述
下面证明 A p q A_{pq} Apq产生 x x x当且仅当 x x x能够把 P P P从状态 p p p和空栈一块带到状态 q q q和空栈,从而证明上述构造是正确的。把当且仅当条件中的每一方向作为一个单独的断言。

断言:如果 A p q A_{pq} Apq产生 x x x,则 x x x能够把 P P P p p p和空栈一块带到 q q q和空栈。
使用归纳法来证明这个断言。
归纳基础:派生只有一步。
派生只有一步,说明右边一定不含变元,所以一定是 A p p → ε A{pp}→ε Appε,显然,输入 ε ε ε P P P p p p和空栈带到 p p p和空栈,这是我们的归纳基础。
归纳步骤:假设断言对长度不超过 k k k的派生成立,其中 k ≥ 1 k≥1 k1,下面证明断言长度对长度为 k + 1 k+1 k+1的派生也成立。
假设 A p q ⇒ ∗ x A_{pq}\overset{*}⇒x Apqx使用 k + 1 k+1 k+1步。该派生的第一步是 A p q ⇒ a A r s b A_{pq}⇒aA_{rs}b ApqaArsb A p q ⇒ A p r A r q A_{pq}⇒A_{pr}A_{rq} ApqAprArq,分别处理这两种情况。
对于第一种情况,根据 x x x中由 A r s A_{rs} Ars产生的部分 y y y,有 x = a y b x=ayb x=ayb,因为 A r s ⇒ ∗ y A_{rs}\overset{*}⇒y Arsy使用 k k k步,根据归纳假设, P P P能够从 r r r和空栈一块转移到 s s s和空栈。因为 A p q → a A r s b A_{pq}→aA_{rs}b ApqaArsb G G G的一条规则,故对某个栈符号 G G G δ ( p , a , ε ) δ(p,a,ε) δ(p,a,ε)包含 ( r , u ) (r,u) (r,u) δ ( s , b , u ) δ(s,b,u) δ(s,b,u)包含 ( q , ε ) (q,ε) (q,ε)。于是,如果 P P P从状态 p p p和空栈开始,那么在读到 a a a后,它能够转移到状态 r r r,并且把 u u u推入栈顶。然后读 y y y,把它带到 s s s,并且在栈中留下 u u u。接着在读到 b b b后,它能够转移到 q q q,并且把 u u u弹出栈。因此, x x x能够把 P P P p p p和空栈带到 q q q和空栈。
对于第二种情况,根据 x x x中由 A p r A_{pr} Apr A r q A_{rq} Arq分别产生的部分 y y y z z z,有 x = y z x=yz x=yz。因为 A p r ⇒ ∗ y A_{pr}\overset{*}⇒y Apry A r q ⇒ ∗ z A_{rq}\overset{*}⇒z Arqz都不超过 k k k步,根据归纳假设, y y y能够把 P P P p p p带到 r r r z z z能够把 P P P r r r带到 q q q,而且在派生的开始和结束时都是空栈。因此, x x x能够把 P P P p p p和空栈带到 q q q和空栈。这就完成了归纳步骤。

断言:如果 x x x能够把 P P P p p p和空栈带到 q q q和空栈,则 A p q A_{pq} Apq产生 x x x
通过对输入 x x x P P P p p p和空栈到 q q q和空栈的计算步数作归纳,来证明这个断言。
归纳基础:计算有0步。
如果计算有0步,则它开始和结束在同一个状态,比如说是 p p p。因此,我们要证明 A p p ⇒ ∗ ε A_{pp}\overset{*}⇒ε Appε。在0步内, P P P无法读入任何字符,故 x = ε x=ε x=ε。根据 G G G的构造,它有规则 A p p → ε A{pp}→ε Appε,这就证明了归纳基础。
归纳步骤:假设断言对长度不超过 k k k的计算成立,其中 k ≥ 0 k≥0 k0,要证明断言对长度为 k + 1 k+1 k+1的计算也成立。
假设 P P P有一个计算,在 k + 1 k+1 k+1步内 x x x p p p连同空栈一块带到 q q q,或者仅从计算的开始和结束时是空的,或者在其他某个地方栈也变成空的。
对于第一种情况,第一步推入栈的符号一定和最后一步弹出栈的符号相同,把这个符号称为 u u u。设 a a a是第一步读的输入符号, b b b是最后一步读的输入符号, r r r是第一步后的状态, s s s是最后一步之前的状态,那么 δ ( p , a , ε ) δ(p,a,ε) δ(p,a,ε)包含 ( r , u ) (r,u) (r,u) δ ( s , b , u ) δ(s,b,u) δ(s,b,u)包含 ( q , ε ) (q,ε) (q,ε),从而在 G G G中有规则 A p q → a A r s b A_{pq}→aA_{rs}b ApqaArsb
y y y x x x中不包括 a a a b b b在内的部分,即 x = a y b x=ayb x=ayb,输入 y y y能够把 P P P r r r带到 s s s而不触及栈底的符号 u u u,从而 P P P能够在输入 y y y上连同空栈一块从 r r r转移到 s s s。由于已经从原来关于 x x x的计算中删去第一步和最后一步,故关于 y y y的计算有 ( k + 1 ) − 2 = k − 1 (k+1)-2=k-1 (k+1)2=k1步,于是,根据归纳假设有 A r s ⇒ ∗ y A_{rs}\overset{*}⇒y Arsy,从而, A p q ⇒ ∗ x A_{pq}\overset{*}⇒x Apqx。对于第二种情况,设 r r r是关于 x x x的计算中除开始和结束之外栈变成空的时候的状态,于是,计算从 p p p r r r和从 r r r q q q的部分都超过 k k k步,记 y y y为计算前一部分读的输入, z z z为后一部分读的输入,根据归纳假设有 A p r ⇒ ∗ y A_{pr}\overset{*}⇒y Apry A r q ⇒ ∗ z A_{rq}\overset{*}⇒z Arqz。由于 G G G中有规则 A p q → A p r A r q A_{pq}→A_{pr}A_{rq} ApqAprArq,故 A p q ⇒ ∗ x A_{pq}\overset{*}⇒x Apqx,证毕。
这就完成了第二个方向引理的证明,从而完成了对于PDA和CFG等价性的证明。
因此我们可以更好地得出结论:每一个正则语言都是上下文无关的。
因为每一个正则语言都可以用DFA识别,而每一台DFA都是一台PDA,因此每一个正则语言都是上下文无关的。

3.2 Pumping Lemma for CFL(上下文无关语言的泵引理)

我们在前面介绍了Pumping Lemma(泵引理)从而证明某些语言不是正则的,我们将对上下文无关语言给出类似的Pumping Lemma(泵引理)从而证明某些语言不是上下文无关的。它指出了每一个上下文无关语言都有一个特殊的值,称为pumping length(泵长度),使得这个语言中的所有长度等于或大于这个值的字符串都能够被“抽取”,这次的抽取是指字符串能被划分成5段,其中第2段和第4段可以同时重复任意多次,而且所得到的字符串仍然在这个语言中。

关于上下文无关语言的Pumping Lemma(泵引理):如果 L L L是上下文无关语言,则存在数 p ≥ 1 p≥1 p1(泵长度),使得 L L L中任何一个长度不小于 p p p的字符串 s s s都能被划分为5段 s = u v x y z s=uvxyz s=uvxyz且满足下述条件:
1. ∣ v y ∣ ≥ 1 |vy|≥1 vy1( v v v y y y不都为空)
2. ∣ v x y ∣ ≤ p |vxy|≤p vxyp
3.对于每一个 i ≥ 0 , u v i x y i z ∈ L i≥0, uv^ixy^iz∈L i0,uvixyizL
证明思路:设 L L L是CFL, G G G是产生 L L L的CFG。要证明 L L L中任何足够长的字符串 s s s都能够被抽取,并且抽取后的字符串仍在 L L L中。
s s s A A A中一个很长的字符串。由于 s s s L L L中,它可以用 G G G派生出来,从而有一棵语法分析树。由于 s s s很长, s s s的语法分析树就会随之一定很高,也就是说,这棵语法分析树一定有一条很长的从树根的起始变元到树叶上的终结符的路径。根据鸽巢原理,在这条长路径上一定有某个变元 R R R重复出现。如图,这种重复使得我们可以用第一次出现的 R R R下面的子树代替第二次出现的 R R R下面的子树,并且仍得到一棵合法的语法分析树。由此,可以像图中表示的那样,把 s s s切成5段 u v x y z uvxyz uvxyz,重复第2段和第4段,得到的字符串仍在 L L L中。换言之,任意的,对任意的 i ≥ 0 , u v i x y i z ∈ L i≥0, uv^ixy^iz∈L i0,uvixyizL
在这里插入图片描述
下面主要说明全部3个条件的细节以及如何计算泵长度 p p p

G G G是CFL L L L的一个CFG,令 b b b是规则右边符号数的最大值(假设大于等于 2 2 2)。在 G G G的任一棵语法分析树中,一个结点最多有 b b b个子叶,也就是说,离起始变元1步最多有 b b b个子叶,离起始变元不超过 2 2 2步最多有 b 2 b^2 b2个子叶;离起始变元不超过 h h h步最多有 b h b^h bh个子叶,因此如果语法分析树的高度不超过 h h h,则它产生的字符串的长度不超过 b h b^h bh。反之,如果一个产生的字符串长度不小于 b h + 1 b^h+1 bh+1,则生成它的每个语法分析树高度至少为 h + 1 h+1 h+1
G G G中变元的数目为 ∣ v ∣ |v| v。令泵长度 p = b ∣ V ∣ + 1 p=b^{|V|+1} p=bV+1,则 A A A中任一长度不小于 p p p的字符串 s s s的语法分析树的高度不小于 ∣ V ∣ + 1 |V|+1 V+1,这是因为 b ∣ V ∣ + 1 ≥ b h + 1 b^{|V|+1}≥b^h+1 bV+1bh+1(前一段最后一句话)。
未说明如何抽取 s s s,设 c c c的一棵语法分析树,如果 s s s有若干语法分析树,取 c c c是结点数最少的语法分析树,由于 c c c的高度不小于 ∣ V ∣ + 1 |V|+1 V+1,从而从根节点出发的最长路径的长度不小于 ∣ V ∣ + 1 |V|+1 V+1并包含 ∣ V ∣ + 2 |V|+2 V+2个结点,其中一个结点为终结符,其他为变元。因此该路径至少有 ∣ V ∣ + 1 |V|+1 V+1个变元,因为只有叶是终结符,故这条最长的路径上至少有|V|+1个变元,而 G G G只有 ∣ V ∣ |V| V个变元,故有某个变元 R R R在这条路径上不只出现一次。为了后面的方便,选取 R R R为这条路径上在最下面的 ∣ V ∣ + 1 |V|+1 V+1个变元中重复出现的变元。
如上图将 s s s划分为 u v x y z uvxyz uvxyz。在每一个 R R R的下面有一棵子树,它产生 s s s的一部分。上面的 R R R有一棵较大的子树,产生 v x y vxy vxy;下面的 R R R有一棵较小的子树,恰好产生 x x x。这两棵树由同一个变元产生,因而可以相互替换,并且扔得到一棵有效的语法分析树。对于每一个 i > 1 i>1 i>1,用较大的子树反复替换较小的子树以给出字符串 u v i x y i z uv^ixy^iz uvixyiz的语法分析树,用较小的子树替换较大的子树以产生字符串 u x z uxz uxz。这就证实了引理中的条件3。
下面是条件1和2.
为了得到条件1,必须保证 v v v y y y不都是 ε ε ε。如果他们都为 ε ε ε,则用较小的子树替换较大的子树得到的语法分析树的结点比 c c c少,并且仍然能够产生 s s s。但这是不可能的,因为我们已经选取 c c c作为 s s s的结点数最少的语法分析树,这也是为什么要这样选取 c c c的原因。
为了得到条件2,必须保证 v x y vxy vxy的长度不超过 p p p,在 s s s的语法分析树中,上面的 R R R产生 v x y vxy vxy。我们选取 R R R使得它的两次出现都落在所在路径的最下面的 ∣ V ∣ + 1 |V|+1 V+1个变元中,而这条路径又选取的是语法分析树中的最长路径,因此 R R R产生 v x y vxy vxy的子树的高度不超过 ∣ V ∣ + 1 |V|+1 V+1。这么高的树只能产生长度不超过 b ∣ v ∣ + 1 = p b^{|v|+1}=p bv+1=p的字符串。

我们现在尝试用Pumping Lemma(泵引理)证明语言 L = { a n b n c n ∣ n ≥ 0 } L=\{a^nb^nc^n|n≥0\} L={ anbncnn0}不是上下文无关的。
假设 L L L是CFL,令 p p p L L L的泵长度,根据Pumping Lemma(泵引理),这个 p p p一定存在。选取字符串 s = a p b p c p s=a^pb^pc^p s=apbpcp,显然, s s s属于 B B B且长度不小于 p p p,我们现在需要证明无论我们如何将 s s s划分为 u v x y z uvxyz uvxyz,都会违法Pumping Lemma(泵引理)的一个条件:
1.假设当 v v v y y y都只含有一种符号时, a a a b b b b b b c c c不会都在v中,同样也不会都在 y y y中,这是字符串不可能含有个数相同的 a a a b b b c c c。因此,它不属于 L L L,这违反了条件3,矛盾。
2.当 v v v或者 y y y含有一种以上符号时, u v 2 x y 2 z uv^2xy^2z uv2xy2z可能含有个数相同的3种符号,但是这些符号的次序不可能正确,这也产生了矛盾。
由于这两种情况必然有一个发生,而这两种情况都会产生矛盾,所以矛盾不可避免,因此假设 L L L是CFL是错误的,得证, L L L不是CFL。