形式语言与自动机主要知识

最后更新于

构造FA#

构造接收是3倍数的二进制串,高位先读。

等价类:模3余数为 ii 的状态 qiq_i,初始状态为 qsq_s,接受状态为 q0q_0

q0q_0 代表的等价类:3k3k

  • 下一位为 0 时:2(3k)+0=6k2(3k) + 0 = 6k,模3余数为0,转移到 q0q_0
  • 下一位为 1 时:2(3k)+1=6k+12(3k) + 1 = 6k + 1,模3余数为1,转移到 q1q_1

q1q_1 代表的等价类:3k+13k + 1

  • 下一位为 0 时:2(3k+1)+0=6k+22(3k + 1) + 0 = 6k + 2,模3余数为2,转移到 q2q_2
  • 下一位为 1 时:2(3k+1)+1=3(2k+1)2(3k + 1) + 1 = 3(2k + 1),模3余数为0,转移到 q0q_0

q2q_2 代表的等价类:3k+23k + 2

  • 下一位为 0 时:2(3k+2)+0=6k+42(3k + 2) + 0 = 6k + 4,模3余数为1,转移到 q1q_1
  • 下一位为 1 时:2(3k+2)+1=6k+52(3k + 2) + 1 = 6k + 5,模3余数为2,转移到 q2q_2
flowchart LR
    Begin((S)) --> qs(($$q_s$$)) -->|1| q1(($$q_1$$))
    qs -->|0| q0((($$q_0$$)))
    q0 -->|0| q0
    q0 -->|1| q1
    q1 -->|0| q2(($$q_2$$))
    q1 -->|1| q0
    q2 -->|0| q1
    q2 -->|1| q2
    style Begin fill:none,stroke:none

改:接收模3余1且模5余2的二进制串,高位先读。
只要设计等价类:qijq_{ij}(模3余i且模5余j)。

左线性文法到NFA#

SaBcS \to a | Bc
关键在于:

  1. 将 “a” 视为 “q0aq_0a”(加入了一个初始状态)
  2. 用“规约”的思想,反着看
flowchart LR
    Begin(( )) --> Start(($$q_0$$)) --a--> S(((S)))
    B((B)) --c--> S
    style Begin fill:none,stroke:none

NFA到DFA#

构建DFA:{xx{0,1}+x中含形如110的子串}\{x | x \in \{0,1\}^+ \text{且} x \text{中含形如110的子串}\}

1. 构建NFA#

flowchart LR
    q0(($$q_0$$))
    q1(($$q_1$$))
    q2(($$q_2$$))
    q3((($$q_3$$)))
    S((S)) --> q0 --"0,1"--> q0 --"1"--> q1 --"1"--> q2 --"0"--> q3 --"0,1"--> q3
    style S fill:none,stroke:none

这个题目有些简单,自己稍微改改就成DFA了。下面是机械的方式:

2. NFA转DFA#

原理是NFA的状态组合得到DFA的状态,从头到尾模拟着走一遍NFA即可。
下表是逐行写的,右侧出现新状态就添加新行:

状态输入0输入1新名称
[q0][q_0][q0][q_0][q0,q1]\color{red}{[q_0, q_1]} 新状态0
[q0,q1]\color{red}{[q_0, q_1]}[q0][q_0][q0,q1,q2][q_0, q_1, q_2]1
[q0,q1,q2][q_0, q_1, q_2][q0,q3][q_0, q_3][q0,q1,q2][q_0, q_1, q_2]2
[q0,q3][q_0, q_3][q0,q3][q_0, q_3][q0,q1,q3][q_0, q_1, q_3]3
[q0,q1,q3][q_0, q_1, q_3][q0,q3][q_0, q_3][q0,q1,q2,q3][q_0, q_1, q_2, q_3]4
[q0,q1,q2,q3][q_0, q_1, q_2, q_3][q0,q3][q_0, q_3][q0,q1,q2,q3][q_0, q_1, q_2, q_3]5
  • 初始状态:[q0][q_0]
  • 接受状态:包含 q3q_3 的状态

如果发现当前所有 NFA 状态都无法继续转移(没有定义),对应转移后 DFA 状态 [][\emptyset]。该 DFA 状态无论输入什么都转移到自己,对应“陷阱状态”。
例如,不接收包含 0000 的 NFA 中,设“已经读取到第一个0”的状态为 q1q_1,只要不定义 q1q_1 在输入0时的转移即可。此时转化为 DFA 会自动增加陷阱状态 [][\emptyset]

3. 最小化DFA#

原理:把不可区分的状态合并。
可区分的xΣ\exist x \in \Sigma^* 使得 δ(q,x)F\delta(q,x) \in Fδ(p,x)F\delta(p,x) \in F 有且只有一个成立,则 qqpp 是可区分的。

过程:

  1. “终态”和“非终态”一定可以区分,先标记上(用标记表示可区分)
  2. 对没有标记的状态对正推,如果关联到的状态对已经标记了,那么就传染
  3. 最终剩下的就是等价状态对

下表中,打勾的为可区分,打叉的为等价,写数对的为下一步可转移;终态用粗体表示;对称只写上三角。

012345
0X(1,2)(0,3)
1X(0,3)
2X
3X(3,3)(3,3)
4X(3,3)
5X

注意:(0, 1) 可以转移到 (0, 0)(1, 2),虽然 (0, 0) 不可区分,但是 (1, 2) 可区分((1, 2) 可转移到 (0, 3),被传染了可区分性),只要染上可区分就会被传染。

发现 3453 \equiv 4 \equiv 5,合并为一个状态。

4. 得到DFA#

flowchart LR
    q0((0))
    q1((1))
    q2((2))
    q345(((3,4,5)))
    S((S)) --> q0 --0--> q0
    q0 --1--> q1
    q1 --0--> q0
    q1 --1--> q2
    q2 --0--> q345
    q2 --1--> q2
    q345 --0,1--> q345
    style S fill:none,stroke:none

正则文法和右线性文法的gap#

正则文法的定义:只准有

  • AaBA \to aB
  • AaA \to a

其中 A,BVA, B \in VaT+a \in T^+。容易证明其实可以限制 aTa \in T

右线性文法的定义:只准有

  • AaBA \to aB
  • AaA \to a

其中 A,BVA, B \in VaTa \in T^*

两者的区别在于正则文法要求 aa 至少是一个终结符,而右线性文法允许 aa 是空串 ϵ\epsilon。显然,正则文法的变量不能“总是”派生出空串,而右线性文法可以;正则文法是右线性文法的一个子集。

但这不影响两者的等价性。等价性的证明为:给出任意一方,可以构造出接收同一语言的另一方。要弥补双方的差异,只需要找到可以接收空串正则文法。关键:将 ϵ\epsilon 作为构造的正则文法的一个终结符。

所以此时构建出的正则文法的形式描述是和右线性文法不一样的。子集是语法层面的子集,但等价是语言层面的等价。

证明不是RL#

pumping lemma 泵引理#

证明 L={0m1nmn}L = \{0^m1^n | m \neq n\} 不是RL:

  1. 假设它是RL,那么存在一个 pumping length NN
  2. m=Nm = N,则 z=0N1nz = 0^N1^{n}.
  3. 任意划分:j[0,N),k[1,Nj]\forall j \in [0, N), \forall k \in [1, N-j] jj 是为了满足 uvN|uv| \leq Nkk 是为了满足 v1|v| \geq 1,两个一起实现了任意划分),进行如下划分:
    • u=oNjku=o^{N-j-k}
    • v=okv=o^k
    • w=0j1nw=0^j1^n
      此时 uviw=0N+k(i1)1n,i0uv^iw = 0^{N+k(i-1)}1^n, i \geq 0
  4. 要证明不是RL,只要找到 ii 使得 uviwLuv^iw \notin L 即可。此时 i=nNk+1i=\frac{n-N}{k}+1 存在,即要求 nNn-Nkk 的倍数,问题变为找到这样的 nn。考虑到 kNk \leq N,取 nN=N!n-N = N! 即可。

利用封闭性#

仍然对上一题:

  1. 已知 A={0n1n}A = \{0^n1^n\} 不是RL、 C={01}C = \{0^*1^*\} 是RL。
  2. LL 是 RL,根据运算封闭性,CLC - L 也是RL,但这实际上得到了非RL的 AA,矛盾,所以 LL 不是RL。

转换为CNF#

构造与下列文法等价的CNF文法:

  • SaBBbAAS \to aBB | bAA
  • AbbAϵA \to bbA | \epsilon
  • BaBaaaϵB \to aBa | aa | \epsilon

机械做法为:先化简CFG,然后转变为CNF。

1. 消除 ε-产生式#

观察到 AABB 都有 ϵ\epsilon-产生式,所以需要消除它们。只要将这两个变量出现的地方分裂为“X非空”和“X为空”两种情况,代入即可。

  • SaBBaBabAAbAbS \to aBB | aB | a | bAA | bA | b
  • AbbAbbA \to bbA | bb
  • BaBaaaB \to aBa | aa

2. 消除单变量产生式#

没有单变量产生式。

3. 去无用符号#

3.1 从后向前:从终极符串出发逆推#

终极符串有a b aabb,所以都是有用的。

3.2 从前向后:从开始符号出发正推#

SS 出发可以推导出 AABB,所以它们都是可达的。

补充一个例题#

删除下列文法中的无用符号:

  • SABCAaS \to AB | CA | a
  • AaA \to a
  • BBCABDEdB \to BC | AB | DE | d
  • CaBbC \to aB | b

先从后向前:终极符串有 a bd,所以 SABCS A B C 都有用;剩下 DDEE 没有派生,删除,得到:

  • SABCAaS \to AB | CA | a
  • AaA \to a
  • BBCABdB \to BC | AB | d
  • CaBbC \to aB | b

然后从前向后:从 SS 出发可以推导出 AABBCC,所以它们都是可达的。

4. 调整为 CNF 的形式#

只允许右侧要么是两个变量、要么是一个终极符。
先把终极符串解决:原本的 AABB 名称变为 XXYY,功能变为派生单个终极符:

  • SAYYAYaBXXBXbS \to AYY | AY | a | BXX | BX | b
  • XBBXBBX \to BBX | BB
  • YAYAAAY \to AYA | AA
  • AaA \to a
  • BbB \to b

再把多变量串解决——引入新的变量:

  • AYAYA_Y \to AY
  • BXBXB_X \to BX
  • SAYYAYaBXXBXbS \to A_YY | AY | a | B_XX | BX | b
  • XBBXBBX \to BB_X | BB
  • YAYAAAY \to A_YA | AA
  • AaA \to a
  • BbB \to b

上下文无关语言的构造#

构造产生0和1数目相同、且非空串的文法(上下文无关语言):

  • S0B1AS \to 0B | 1A: B 表示1要多一个,A 表示0要多一个
  • B11S0BBB \to 1 | 1S | 0BB
  • A00S1AAA \to 0 | 0S | 1AA

一个错误解法:
S1X00X101X10XX01X10S \to 1X0 | 0X1 | 01X | 10X | X01 | X10
因为产生不了 000111111000


构造接收 L={1n02nn1}L = \{1^n0^{2n} | n \geq 1\} 的PDA:

  • 状态:q0q_0(记忆状态),q1q_1(读了第一个0),q2q_2(读了第二个0),qfq_f(接受状态)
  • 输入字母表:{0,1}\{0, 1\}
  • 栈字母表:{Z,A}\{Z, A\}ZZ 是初始栈符号,AA 用来记忆读了多少个1)
  • 转移函数:
    • δ(q0,1,Z)={(q0,AZ)}\delta(q_0, 1, Z) = \{(q_0, AZ)\}
    • δ(q0,1,A)={(q0,AA)}\delta(q_0, 1, A) = \{(q_0, AA)\}
    • δ(q0,0,A)={(q1,A)}\delta(q_0, 0, A) = \{(q_1, A)\} 读了第一个0
    • δ(q1,0,A)={(q2,ϵ)}\delta(q_1, 0, A) = \{(q_2, \epsilon)\}
    • δ(q2,0,A)={(q1,A)}\delta(q_2, 0, A) = \{(q_1, A)\} 读了奇数个0
    • δ(q2,ϵ,Z)={(qf,ϵ)}\delta(q_2, \epsilon, Z) = \{(q_f, \epsilon)\} 空栈和终态同时接收

图灵机#

计算阶乘:输入为 nn 个 0,输出 为 n!n! 个 0。
基本思路:

n!=[n×(n1)×...×i]×i×...×2=[result+result×(i1)]×...×2result0=1resultt=resultt1×(nt+1),1t<n=resultt1+resultt1×(nt)\begin{aligned} n! &= [n \times (n-1) \times ... \times i] \times i \times ... \times 2\\ &= [result + result \times (i-1)] \times ... \times 2\\ result_0 &= 1\\ result_t &= result_{t-1} \times (n - t + 1), 1 \leq t < n \\ &= result_{t-1} + result_{t-1} \times (n - t) \end{aligned}
  1. 先在末尾加上 10,回到开头。
  2. 消去一个输入的 0,在遇到 1 之前,每次读取到一个 0,就把后面的内容复制到最后
  3. 遇到 1 开始返回。
  4. 如果没有 0 可以消去(遇到的是 1)则直接把 1 改为空,进入终止状态

追加的时候有些小技巧,比如暂时用 X 标记已经读取的,Y 标记已经追加的,最后统一变为 0

δ\delta01BXY解释
q0q_0(q0,0,R)(q_0,0,R)(q0,0,R)(q_0,0,R)(q1,1,R)(q_1,1,R)到末尾加1
q1q_1(qb,0,L)(q_{b},0,L)加0后回头
qbq_{b}(qb,0,L)(q_{b},0,L)(qb,1,L)(q_{b},1,L)(q2,B,R)(q_2,B,R)(qb,0,L)(q_{b},0,L)(qb,0,L)(q_{b},0,L)到开头(back)
顺别负责将X和Y置为0
q2q_2(q3,B,R)(q_3,B,R)(qf,B,R)(q_f,B,R)消去第一个0,开始乘法
遇到1说明没0了,结束
q3q_3(q4,X,R)(q_4,X,R)(qe,1,R)(q_{e},1,R)已读取,准备追加
遇到1说明本轮乘法结束
先把X和Y置为0再开启下一轮
qeq_{e}(qe,0,R)(q_{e},0,R)(qe,1,R)(q_{e},1,R)(qb,B,L)(q_{b},B,L)(qend,X,R)(q_{end},X,R)(qe,Y,R)(q_{e},Y,R)到末尾(end)
q4q_4(q4,0,R)(q_4,0,R)(q5,1,R)(q_5,1,R)找到第一个输出位置
q5q_5(q6,X,R)(q_6,X,R)(q7,Y,L)(q_7,Y,L)读取一个输出的0
读到Y说明复制完了
q6q_6(q6,0,R)(q_6,0,R)(qb2,Y,L)(q_{b2},Y,L)(q6,Y,R)(q_6,Y,R)读一个就尾部复制一个
qb2q_{b2}(qb2,0,L)(q_{b2},0,L)(q5,X,R)(q_5,X,R)(qb2,Y,L)(q_{b2},Y,L)回到输入的第一个0继续复制
q7q_7(q8,1,L)(q_8,1,L)(q7,0,L)(q_7,0,L)向左将输出的X变为0
回到输入侧
q8q_8(q8,0,L)(q_8,0,L)(q3,X,R)(q_3,X,R)到输入最左侧开始下一轮追加

模拟 3!=63!=6

序号状态序号状态序号状态
10q000BB\underset{q_0}{0}00BB2000Bq0B000 \underset{q_0}{B}B30001Bq1000 1 \underset{q_1}{B}
40001qb0000 \underset{q_{b}}{1} 0乘2乘1
5Bqb00010\underset{q_{b}}{B}0001033Bqb001000\underset{q_{b}}{B}00100056Bqb01000000\underset{q_b}{B}01000000
60q20010\underset{q_2}{0}0010340q201000\underset{q_2}{0}01000570q2100000\underset{q_2}{0}100000
7B0q3010B\underset{q_3}{0}01035B0q31000B\underset{q_3}{0}100058B1q3000000B\underset{q_3}{1}000000
8X0q410X\underset{q_4}{0}1036X1q4000X\underset{q_4}{1}0005910qe000001\underset{q_{e}}{0}00000
9X01q40X0\underset{q_4}{1}037X10q500X1\underset{q_5}{0}00601000000qb100000\underset{q_{b}}{0}
10X010q5X01\underset{q_5}{0}38X1X0q60X1X\underset{q_6}{0}0611q2000000\underset{q_2}{1}000000
11X01XBq6X01X\underset{q_6}{B}39X1X00Bq6X1X00\underset{q_6}{B}62B0qf00000B\underset{q_f}{0}00000
12X01Xqb2YX01\underset{q_{b2}}{X}Y40X1X00qb2YX1X0\underset{q_{b2}}{0}Y000000=6000000 = 6
13X01XYq5X01X\underset{q_5}{Y}41X1X0q50YX1X\underset{q_5}{0}0Y
14X01Xq7YX01\underset{q_7}{X}Y42X1XX0q6YX1XX\underset{q_6}{0}Y
15X01q70YX0\underset{q_7}{1}0Y43X1XX0YBq6X1XX0Y\underset{q_6}{B}
16X0q810YX\underset{q_8}{0}10Y44X1XX0Yqb2YX1XX0\underset{q_{b2}}{Y}Y
17Xq8010Y\underset{q_8}{X}010Y45X1XX0q5YYX1XX\underset{q_5}{0}YY
18X0q310YX\underset{q_3}{0}10Y46X1XXXYq6YX1XXX\underset{q_6}{Y}Y
19XX1q40YXX\underset{q_4}{1}0Y47X1XXXYYYBq6X1XXXYYY\underset{q_6}{B}
20XX10q5YXX1\underset{q_5}{0}Y48X1XXXYYqb2YX1XXXY\underset{q_{b2}}{Y}Y
21XX1XYq6XX1X\underset{q_6}{Y}49X1XXXYq5YYX1XXX\underset{q_5}{Y}YY
22XX1XYBq6XX1XY\underset{q_6}{B}50X1XXXq7YYYX1XX\underset{q_7}{X}YYY
23XX1XYqb2YXX1X\underset{q_{b2}}{Y}Y51X1q7000YYYX\underset{q_7}{1}000YYY
24XX1Xqb2YYXX1\underset{q_{b2}}{X}YY52Xq81000YYY\underset{q_8}X1000YYY
25XX1XYq5YXX1X\underset{q_5}{Y}Y53X1q3000YYYX\underset{q_3}{1}000YYY
26XX1Xq7YYXX1\underset{q_7}{X}YY54X10qe00YYYX1\underset{q_{e}}{0}00YYY
27XX1q70YYXX\underset{q_7}{1}0YY55X1000YYYqbX1000YY\underset{q_{b}}{Y}
28XXq810YYX\underset{q_8}{X}10YY
29XX1q30YYXX\underset{q_3}{1}0YY
30XX10qeYYXX1\underset{q_{e}}{0}YY
31XX10YYqbXX10Y\underset{q_{b}}{Y}
32XX10Yqb0XX10\underset{q_{b}}{Y}0

可以看到复杂度还是很高的。主要是不能随机跳转。实际上 Y 可以用 1 代替,不过为了过程的清晰还是没有复用。