形式语言与自动机学习复述笔记

本文说明

本文内容仅仅是稻云麦花根据自己所学的记忆,尽量不看书或其它的资料,仅仅凭借闹钟记忆和理解,尝试复述的笔记。写的目的是为了检验自己是否真正掌握所学内容。因此,有些东西可能我自觉已经是先修课的知识或者是此课程内容但是过于简单基础无需赘述。
至于为何不直接说而是以写下来的方式,是因为没有找到一个听众,感觉自言自语很神经病,并且在自习室说话估计会被打死。另外写下来方便自己检查。同时,有时候说实际上会比较模糊,自己不知道自己在说什么。
至于为什么不用笔写,我只想说可能是字太多了,手要写断。好吧,真相是我上次动笔写字貌似是考试的时候,emmmm…
理直气壮 表情
其实下次可以带个笔纸,在湖边边说边写,这样应该就不会被打扰自习室或者图书馆的人了,说只要是文字,笔写关键部分的表达式或者图就好。
溜了溜了 表情

文法

G=<V,T,P,S>
V是变元的集合。
T是终结符的集合,也就是字母表。
P是产生式,产生式都是左部推导出右部的形式。产生式都是左部推导出右部的形式,左部右部都是由变元和终结符并起来的集合中任意选元素组合而成。左部要求至少包含一个变元。
S是变元中的一个特殊元素,开始推导变元,指定从什么变元开始。前三个集合不变,更换S会导致语言推出来的句子发生改变。

文法的乔姆斯基分类

文法的乔姆斯基分类是根据产生是的形式对文法进行分类。

  1. 左部右部不施加额外限制就是0型文法,又称短语结构文法(PSG)。推导出的文法就是短语结构语言(PSL)。
  2. 左部长度不超过右部,就是1型文法、上下文有关文法。CSG CSL 上下文有关语言。
  3. 左部是单个的变元,右部不限制,就是2型文法、上下文无关文法, CFG CFL 上下文无关语言
  4. 左部是单个的变元,右部是单个的字母或者单个字母连接一个变元,就是3型文法、正则文法。RG RL 正则语言。

有穷自动机 正则语言 正则文法

关系

正则语言的集合和有穷自动机所能表示的集合是同样的。即正则语言和有穷自动机可以互换,是等价的。而有穷自动机中有确定性有穷自动机DFA、非确定型有穷自动机NFA、带空转移的非确定型有穷自动机 ϵ \epsilon ϵ-NFA。但是他们只是表现和描述形式不一样,任意两个之间都是“等价”的。它们所能表示的语言集合是同一个集合。引入后面两种自动机是为了方便人构造和描述自动机,而DFA是为了方便证明即计算机自动实现。
正则语言和正则表达式可以互换。

正则表达式

正则表达式,实际上正则表达式就是集合的操作而已。只是没有写成集合的形式而已。
空集是正则表达式。空串 ϵ \epsilon ϵ或者单独一个字母表中的字母a都是表示包含一个元素的集合,是正则表达式。
三种操作,优先级从高到低是闭包运算(*)、连接运算(省略不写)、并运算(+)。优先级相同时自左向右算。
连接运算是两个集合A与B的相乘,只是得到的结果集合C的元素不是用<a,b>的序偶表示,而是直接ab直接连接起来,即字符串拼接。
容易知道并(+)可交换可结合,连接不可交换可结合。

正则表达式RE-> ϵ \epsilon ϵ-NFA

基础情况,空集和单点集容易构造有穷自动机。
对于并操作,A+B,只需要分别构造A和B的有穷自动机,然后增加一个初始状态和接受状态。初始状态给A和B的初始状态各自一个空转移,A和B的接受状态给都给新增的接受状态一个空转移即可。 看起来就是“并联”电路而已。
对于连接操作符, 只需要A的接受状态连一条空转移边到B的初始状态即可。然后初始状态设为A的初始状态,接受状态设为B的接受状态即可。 看起来就是“串联”电路而已。
闭包运算 A*,只需要新增一个初始状态和一个接受状态。初始到A初始,A接受到接受都有一条空转移边。对于原本的A的接受状态连一条空转移边到A的初始状态,让自动机形成一个环可以不断打转实现任意正指数幂的功能。由于还有一个指数为0的空串的情况,所以还需要初始到接受增加一条空转移边。

ϵ \epsilon ϵ-NFA->NFA(消除空转移边)

先说空闭包概念。


空闭包运算 ϵ \epsilon ϵ-闭包运算 ϵ <mtext> -ENCLOSE </mtext> ( q ) \epsilon \text{-ENCLOSE}(q) ϵ-ENCLOSE(q) ϵ <mtext> -ENCLOSE </mtext> ( A ) \epsilon \text{-ENCLOSE}(A) ϵ-ENCLOSE(A)
ϵ <mtext> -ENCLOSE </mtext> ( q ) \epsilon \text{-ENCLOSE}(q) ϵ-ENCLOSE(q)

ϵ <mtext> -ENCLOSE </mtext> ( q ) \epsilon \text{-ENCLOSE}(q) ϵ-ENCLOSE(q)函数的自变量是一个状态,结果是一个状态的集合。如何计算求解呢?从图上直观来讲,就是从q状态出发,只能沿着空转移边走,所能走到的所有点(状态)就是这个集合中的元素,并且只有这些。当然,出发的点q肯定是可以走到的。

ϵ <mtext> -ENCLOSE </mtext> ( A ) \epsilon \text{-ENCLOSE}(A) ϵ-ENCLOSE(A)

和上面这个不同的是,它的自变量是一个状态的集合。运算结果依旧是一个状态的集合。
运算方式就是A集合中的每一个状态分别求 ϵ \epsilon ϵ-闭包,然后将这些结果并起来就是状态集合A求空闭包运算的结果。
当然,你也可以从这些A集合中的点出发沿着空转移边去染色,染上颜色的点就是A这个集合的空闭包运算结果。


消除空转移边得到NFA。
状态集合不需要变更,字母表不需要变更。初始状态也不变。
变的只是转移函数(也就是画图中的边)和可能需要做调整的接受状态。
先看初始状态 q 0 q_0 q0的空闭包有没有接受状态,也就是说 q 0 q_0 q0沿着空转移边能不能到达接受状态。如果有,那说明去掉空转移边之后, q 0 q_0 q0也要接受,加到接受状态的集合里即可。
然后就是转移函数的变更了,非常简单的理解就是,原本 ϵ \epsilon ϵ-NFA中 q i q_i qi沿着一条字母值为a的边到达的状态的集合是A,而A的空闭包是B。那么在NFA中我就需要 q i q_i qi到B中的每一个状态都连一条a边。原因很简单,原本的有空转移的自动机里面 q i q_i qi沿着a边到达了某个点,这个点接着一直沿着空转移边可以走到B中的每一个状态那里,现在把空转移边去掉了,自然需要补a边了。
如此, ϵ \epsilon ϵ-NFA消除了空转移边变成了NFA.

NFA->DFA

NFA是读入一个字符的时候,一个点出发可能有多条相同字母值的边去往不同点。每一次的结果都是一个状态的集合。初始的结果是 { q 0 } \{q_0\} {q0}.读入一个字符a,当前的结果的每一个点都沿着a边往后走,只要有就加入到新结果的状态集合中去。所有字符读完了,只要结果的状态集合中包含接受状态就是接受这个字符串。
NFA转换成DFA的朴素想法就是,NFA的状态集合Q有n个状态,那么“结果的状态集合”不就是Q的子集吗?只需要把Q的每一个子集(共 2 n 2^n 2n个)都当作DFA的一个状态,然后根据每一个子集读不同的字母a在NFA中走出来的新的结果的状态在DFA中连边即可。
这个是从理论上说明了NFA一定可以变成DFA。
于是就出现了填表法。


上面的方法可行,但是会做很多不必要的工作,如有些子集可能在NFA的“行走”中根本不可能出现……
为了避免搞很多多余不必要的状态,不必要的边。
肯定不多余的子集是 { q 0 } \{q_0\} {q0}
对于一个不多余的子集,我们要顺着NFA去走不同字母的边(如果有的话),去得到子集。这样通过非多余的子集走出来子集肯定不是多余的。
如果最后所有的不多余的子集都走完了。没有新的子集了,那么DFA就构造完毕了。

虽然最坏情况下,可能还是要出现 2 n 2^n 2n级别个状态,但是,实际应用的时候,很多情况下DFA的状态数并不是增长太多。

DFA->正则表达式

一种是类似于FLOYD的思想(我一下也不知道哪个先出的,我只知道我接触的是FLOYD)。限制中间点不能超过k,然后逐步将k变大,当k到达n只是就是没有限制了。
DFA的状态从1编号到n.
R ( k ) ( i , j ) R^{(k)}(i,j) R(k)(i,j)是一个正则表达式。这个正则表达式表示的语言是:从自动机的状态i出发,到达状态j,并且中间(不包含i,j)经过的状态的编号都不超过k。

初始化(边界情况是k=0的时候 R ( 0 ) ( i , j ) R^{(0)}(i,j) R(0)(i,j)):

i j i \neq j i̸=j

  1. i到j没有边,表达式是 \empty
  2. i到j只有一条边,字母值为a的边,则表达式是 a a a
  3. i到j只有多条边,则表达式是这些边的并 a 1 + a 2 + a 3 + . . . + a m a_1+a_2+a_3+...+a_m a1+a2+a3+...+am
    i = j i=j i=j,则i到j的字符串还可以是空串,因此,上面三种情况都需要并上空串 ϵ \epsilon ϵ
迭代方式(转移方程)

R ( k ) ( i , j ) = R ( k 1 ) ( i , j ) + R ( k 1 ) ( i , k ) ( R ( k 1 ) ( k , k ) ) R ( k 1 ) ( k , j ) R^{(k)}(i,j)=R^{(k-1)}(i,j) + R^{(k-1)}(i,k)(R^{(k-1)}(k,k))^*R^{(k-1)}(k,j) R(k)(i,j)=R(k1)(i,j)+R(k1)(i,k)(R(k1)(k,k))R(k1)(k,j)
解释:

  1. 中间点根本就不经过k,那么就是k-1限制下所表示的 R ( k 1 ) ( i , j ) R^{(k-1)}(i,j) R(k1)(i,j)
  2. 中间点经过了k,那么就是三段,前面一段是i到k(没有经过k,故事k-1限制下的),后面一段是k到j。中间是k到k的重复若干次(0,1,2,3,…次),故就是闭包。三段连接起来即可。

DFA->正则表达式的状态消除法

大体就是边上不在写字母,而是写正则表达式。
画图比较容易说明。

消除单个状态

消除一个状态并消除影响(补偿)的方法。
因此只需要考虑前驱,自身(自环),后继的问题。 拆分成三段,前驱到自身,自身自环转转转(闭包),自身到后继 这个用正则表达式表示出来并到前驱到后继里面即可。

整体消除方法

目标,仅仅保留初始状态和接受状态。(如果初始状态刚好就是接受状态,就只剩下一个状态了)。
对于消除到最后的自动机就好表示成正则表达式了。

先不断消除既非初始状态也非接受状态的状态。 因为这样无需分类,消除了它,没有影响。
接着要消除接受状态了,这个必须分支。 例如消除s,虽然每个前驱s到后继q的路径补偿了,但是消除掉s之后,初始状态经s到其它接受状态的是可以表示,但是只走到s的理论上也要接受,但是s消除了无法接受。因此,要分支。
怎么分支或者说消除的顺序?
例如剩余m个接受状态p,q,r,s,t,…

  • 将p视作唯一接受状态,不断消除“非接受状态”,因此我们得到了仅仅到p的接受状态的自动机;
  • 消除p的时候,我们丢失了仅仅到p应该接受的表达式。但是到其它的状态没有丢失,至此,我们的需要求解的“接受状态的数目”缩小了1。
    最后我们会得到m个简单的自动机。分别是仅仅接受到状态p,q,r,s,t…的自动机
    它们的表达式取并即可。

DFA最小化

DFA最小化就是将任意的DFA转换为等价的最小化的DFA(状态数最少).
最小化的DFA中任意两个状态都是可区别的。

状态可区别 可区别状态对 等价状态

两个状态q和p。
如果存在某一个字符串,分别从p和q出发,p和q走出来的结果(接受或拒绝)不一样,那么p和q就是可去别的状态对。
与之相对的概念是状态等价。即无论什么字符串,从p和q出发,走出来的结果都是一样的,要么都是接受,要么都是拒绝。对于等价状态,显然直观上我们想让它们合并让状态数变少。

如何确定哪些状态两两可区分

当然,我们可以直接自己构造一个字符串,让两个状态走出来的结果不一样说明它们不可区分;但是,如果以下没有构造出来可以走出不同的字符串,那么就无法确定了;更糟糕的是是如果两个状态是等价的,那么通过构造字符串,我们肯定是得到结果是一样的,但是我们有不能直接肯定任意字符串,得到结果都是一样的,那么它们到底是不是可区分的就解决不了了。
因此,我们需要一定的方法。


  1. 接受状态和非接受状态肯定是不等价的,即可区分的。
  2. 如果对于同一个字母边,p和q走出的状态不等价,那么p和q不等价。要断言p和q不等价,要p和q沿着某一个字母值的边到达的状态中有两个不等价就可以了;如果要断言等价,就要各个字母的边边走到的状态都是等价的才行。

有了这两条之后,我们就可以运用填表法来确定了。√ × ?(待定)
一轮一轮的扫描,如果某一轮之后没有新增的可区分对,那么说明所有的可区分对都找出来了。
然后把等价的状态合并成一个,等价状态内部的边变成自环边,入边出边进行整合合并,over。

正则语言的性质

泵引理

其实泵引理就是根据鸽笼原理,对于一个状态数为n的DFA,读完长度大于等于n的字符串w之后,必然至少有一个状态被访问了两次,换句话说,出现了一个环。然后对于那个环,可以不走,走1次,走2次,走3次……
因此,可以把这个环所对应的字符子串v去掉或重复若干次,得到的新字符串依旧和原本的结果是一样的(接受或拒绝)。
L n w ( w L w n ) 即存在只依赖于正则语言L的常数n,\forall w(w \in L \wedge |w| \geq n) Lnw(wLwn), x , y , z ( w = x y z y ϵ x y n ( k ( k 0 ) , x y k z L ) ) \exist x,y,z(w=xyz \wedge y \neq \epsilon \wedge |xy| \leq n \wedge (\forall k(k\geq 0),有xy^kz \in L)) x,y,z(w=xyzy̸=ϵxyn(k(k0),xykzL)).

正则语言的泵引理叙述的是正则语言的性质。它的作用是,对于一个语言L,常常通过正则语言的泵引理来推导出矛盾(某个不应该接受的字符串被接受了),从而说明这个语言L不是正则语言。但是,通过正则语言的泵引理是无法证明一个语言是正则语言的,毕竟泵引理只是正则语言的必要条件(性质),而非充分条件。

正则语言的判定
  1. 正则语言的判定,即断言一个语言是正则语言,通常是构造一个 ϵ \epsilon ϵ-NFA(或NFA、DFA)接受这个语言或者构造正则表达式(基于的理论是已经证明了这些都是等价的……)。
  2. 根据已知的正则语言(可以通过第1条迅速证明)的正则语言及正则语言的封闭性(并、交、差、补、反转、闭包、连接、同态、逆同态)来做运算得到要证明的语言L是正则语言。

上下文无关文法与下推自动机

TODO

图灵机导引

TODO

二〇一九年六月六日
稻云麦花