编译原理_NFA->DFA 子集法
文章目录
NFA等价的DFA子集法求解
NFA
一个不确定的有穷自动机 M 是一个五元组:
M = ( K , Σ , f , S , Z ) M=(K, \Sigma, f, S, Z) M=(K,Σ,f,S,Z)
其中:
(1)
K
K
K 是一个有穷集, 它的每个元素称为一个状态。
(2)
Σ
\Sigma
Σ 是一个有穷字母表, 它的每个元素称为一个输人符号。
(3) f f f 是一个从 K × Σ ∗ K \times \Sigma^{*} K×Σ∗到 K K K 的全体子集的映像, 即 K × Σ ∗ → 2 K K \times \Sigma^{*} \rightarrow 2^{K} K×Σ∗→2K , 其中 2 K 2^{K} 2K 表示 K K K 的 幂集。
(4)
S
⊆
K
S \subseteq K
S⊆K , 是一个非空初态集。
(5)
Z
⊆
K
Z \subseteq K
Z⊆K , 是一个终态集。
一个含有m个状态和n 个输人符号的NFA可表示成一张状态转换图,
这张图含有m个状态结点,每个结点可射出若干条箭弧与别的结点相连接,
每条弧用 ∑ ∗ \sum^* ∑∗中的一个串作标记,整个图至少含有一个初态结点以及若干个终态结点。DFA(NFA的特例)
( 1 ) K 是 一 个 有 穷 集 , 它 的 每 个 元 素 称 为 一 个 状 态 。 ( 2 ) Σ 是 一 个 有 穷 字 母 表 , 它 的 每 个 元 素 称 为 一 个 输 人 符 号 , 所 以 也 称 Σ 为 输 人 符 号 表 。 ( 3 ) f 是 转 换 函 数 , 是 K × Σ → K 上 的 映 像 。 例 如 , f ( k i , a ) = k j ( k i ∈ K , k j ∈ K ) , 就 意 味 着 , 当 前 状 态 为 k i 、 输 人 字 符 为 a 时 , 将 转 换 到 下 一 状 态 k 1 , 把 k j 称 作 k i 的 一 个 后 继 状 态 。 ( 4 ) S ∈ K , 是 唯 一 的 一 个 初 态 。 ( 5 ) Z ⊆ K , 是 一 个 终 态 集 , 终 态 也 称 可 接 受 状 态 或 结 束 状 态 。 (1) K 是一个有穷集, 它的每个元素称为一个状态。\\ (2) \Sigma 是一个有穷字母表,它的每个元素称为一个输人符号, 所以也称 \Sigma 为输人符 号表。\\ (3) f 是转换函数, 是 K \times \Sigma \rightarrow K 上的映像。\\例如, f\left(k_{i}, a\right)=k_{j}\left(k_{i} \in K, k_{j} \in K\right) , \\ 就意味 着, 当前状态为 k_{i} 、输人字符为 a 时, 将转换到下一状态 k_{1} , 把 k_{j} 称作 k_{i} 的一个后继状态。\\ (4) S \in K , 是唯一的一个初态。\\ (5) Z \subseteq K , 是一个终态集, 终态也称可接受状态或结束状态。\\ (1)K是一个有穷集,它的每个元素称为一个状态。(2)Σ是一个有穷字母表,它的每个元素称为一个输人符号,所以也称Σ为输人符号表。(3)f是转换函数,是K×Σ→K上的映像。例如,f(ki,a)=kj(ki∈K,kj∈K),就意味着,当前状态为ki、输人字符为a时,将转换到下一状态k1,把kj称作ki的一个后继状态。(4)S∈K,是唯一的一个初态。(5)Z⊆K,是一个终态集,终态也称可接受状态或结束状态。
正则表达式构造NFA
基础对应关系
ε对应的NFA
字母表Σ中符号a对应的NFA
连接&或&幂运算对应的NFA
- 对于一个长的正则表达式,先进行分解
- 从串联的角度进行分解,上述正则表达式可以分解为4个部分
- 其中第一个部分可以继续分解
- 最后,将或运算分解
求解五元组
欲求NFA N等价的DFA M,需要求出对应的DFA M的五元组
两种运算
ε − c l o s u r e 运 算 \varepsilon-closure运算 ε−closure运算和 m o v e move move运算
-
(1)状态集合Ⅰ的*** ε − 闭 包 \varepsilon-闭包 ε−闭包***,表示为 ε − c l o s u r e ( I ) \varepsilon-closure(I) ε−closure(I)
- 该闭包定义为一个状态集,是状态集Ⅰ中的任何状态S经任意条 ε \varepsilon ε弧而能到达的状态的集合。
- 如输入符号是空串,则自动机仍停留在原来的状态上,显然,状态集合Ⅰ的任何状态S都属于** ε − c l o s u r e ( I ) \varepsilon-closure(I) ε−closure(I)**。
-
(2)状态集合Ⅰ的α弧转换,表示为 m o v e ( I , a ) move(I,a) move(I,a),
- 定义为状态集合J,其中J是所有那些可从Ⅰ中的某一状态经过一条α弧而到达的状态的全体。
我们把下文用到的符号捋一下:
- K , K 0 , K t K,K_0,K_t K,K0,Kt分别作为NFA的有穷状态集合和初始状态以及终止状态
- S , S 0 , S t S,S_0,S_t S,S0,St分别作为DFA的有穷状态集合和初始状态以及终止状态
- 主要部分是求解M的状态集S(其又由NFA N的状态机K的一些子集组成)
- 问题有转成求解K的子集
- 我们使用 S i S_i Si数组表示待求状态集S中的元素(状态元素)
- DFA的状态是NFA的状态集的子集
算法伪代码
- 注意到,这里说
S
i
S_i
Si是有序的,S是一个集合其内部元素是无序的(书写的时候不体现顺序).
- 转换函数D(S,a)=R(此处a代表输入字符集合
∑
\sum
∑中的任意元素);
- S,R是状态集合(NFA的状态子集);S,R作为DFA的状态
- 转换函数D(S,a)=R(此处a代表输入字符集合
∑
\sum
∑中的任意元素);
本图中,不确定有穷状态机N的有限状态集K包括了0,1,…10 这11种状态.;
且,状态 K 0 K_0 K0是状态0
子集族C要表达的意思和S相近,C可能强调顺序
回顾求解子集算法
算法为二重循环
- 内层循环for比较确定
- 外层循环while的终止依赖于内循环for的计算结果
中的内层循环(for)是对输入符号集合(字母表)做遍历
- 算法伪代码中的U就是下面所说的子集 T i T_i Ti
- 结合本例题,这个被遍历的输入集合是 ∑ = { ε , a , b } \sum=\{\varepsilon,a,b\} ∑={ε,a,b}
- 内部的两个抽象运算也比较简单
-
ε
−
c
l
o
s
u
r
e
(
S
t
a
t
e
S
e
t
)
\varepsilon-closure(StateSet)
ε−closure(StateSet)
- 运行一次 ε − c l o s u r e ( ) \varepsilon-closure() ε−closure()运算,可以得到一个子集族中的元素 T i T_i Ti
- 准确的说,是下一个子集 T j T_j Tj的候选集合是经过 ε − c l o s u r e \varepsilon-closure ε−closure和move的嵌套(复合)运算得到的,当这个候选集合是想对于集合族是全新的集合时,它就成为了 T j T_j Tj
- 经过一次for循环的遍历,可能产生超过一个的新增子集加入到子集族 C C C
- 同一个for循环还没走完之前,使用的都是同一个 T i T_i Ti(它就是while循环开头所作的被新标记的子集T)来计算新的候选子集
- m o v e ( S t a t e S e t , a ) move(StateSet,a) move(StateSet,a)
- 都是找出某一出边(弧)的过程前者是找 ε \varepsilon ε(可以连续多次的);后者是找输入符号a(不可连续)
- 手工计算的时候,可以使用树形分叉记法(习惯看表格的话叶可以将状态转移图转化成状态转移表,然后再画树状分支,注意 ε − c l o s u r e \varepsilon-closure ε−closure运算包含起点本身)
-
ε
−
c
l
o
s
u
r
e
(
S
t
a
t
e
S
e
t
)
\varepsilon-closure(StateSet)
ε−closure(StateSet)
视频讲解
- 国防科技大学:编译原理Mooc系列
- Bilibili查看(相关章节)
DFA和NFA的等价性
- 对任何非确定的有穷自动机N ,存在定义同一语言的确定的有穷自动机D
- 对任何确定的有穷自动机D ,存在定义同一语言的非确定的有穷自动机N
NFA&DFA&FA&正则
-
-
NFA比DFA更加直观(对于人类而言)
-
另一方面,DFA在计算机实现上比NFA更容易
带有ε边的NFA
带不带空边(ε边)的NFA间具有等价性
- 注意,后面的状态对于前面的状态具有累计效果
- 以及,各个状态是否为终态
根据RE(正则表达式)构造NFA
ε对应的NFA
字母表Σ中符号a对应的NFA
- 输入串联
- 输入并联
- 方幂:输入循环
案例:
- 对于一个长的正则表达式,先进行分解
- 从串联的角度进行分解,上述正则表达式可以分解为4个部分
- 其中第一个部分可以继续分解
- 最后,将或运算分解
NFA转换为DFA
NFA与DFA的对比
DFA与NFA的等价性
-
获得状态集之间的转换关系
-
ε-closure(I)运算
- 我们可以用集合I来描述ε运算的结果
-
move运算(和字母表(输入字符)有关)
- 我们可以用集合J来描述(接收)move运算的计算结果
- J a , J b J_a,J_b Ja,Jb
- 千万记住,move(I,a)运算的结果 J a J_a Ja只是一个中间结果,想要得到转换表中的 I a I_a Ia,还必须要再次执行ε运算
- 对于其他字母表中的字母(例如b,c,d,…),也是类似的流程
- 我们可以用集合J来描述(接收)move运算的计算结果
-
-
字母状态转换表中的Ia,Ib,Ic,…取决于文法的字母表总符号的个数
-
-
一个容易出错/疏漏的地方在于,被执行ε运算的状态(集)本身也要加入到ε计算的结果集合中,
或者说,本身集合中的元素至少要加入到ε-closure的结果集合中)(因为,我们允许经过的ε的弧数为0)
状态转换表
转换表实例1
DFA是NFA的特例
例子 2
没有ε边的NFA2DFA
-
(从NFA初步转换后的)DFA的每个状态都是一个由NFA中的状态构成的集合,即NFA状态集合的一个子集
-
-
对于没有ε边的状态转换表,比较简单
- 这种情况下,只需要执行move运算,而不需要考虑ε-closure运算.
-
-
当然,新的状态集合来自于状态转换表中的非空状态集,可以单独将他们整理出来,得到以下的DFA状态转换图
从带有ε-边的NFA到DFA的转换
-
这种带有ε边的情况下,就需要先算完move运算(作为中间结果J),然后计算J的ε-closure闭包
-
- 上面例题配置的状态转换表格形式比较简约,状态列依然采用NFA中的状态,
- 但是在根据该表格绘制DFA的时候,确定DFA的状态的时候,需要考察表格内的非空集合
- 并且,由于该形式的转换表不是太彻底(和例题1中的状态转换表的直观度要差一些),因此需要再稍微计算一下
- 例题1中的转换表比较直观,但是要得到该层次的转换表,需要做的过程计算相应的要多上一些
- 具体可以参看
哈工大(编译原理)
相关章节
-
需要注意的是,终止状态的判断,转换为DFA的每次新产生的一个状态都需要判断该状态(集合)中是否含有NFA的终止态(之一)
例子3: 识别无符号数的DFA
-
注意到,转换为DFA后,各个状态中,包含原来NFA的终止态的新状态(集合),将要作为DFA的终止态
-
转换过程中,主要是找到每个状态集合T(可以由 ε − c l o s u r e \varepsilon-closure ε−closure 计算得到状态集合(该运算的参数也是状态集合,特别的,状态集合可以只包含一个状态,譬如初始推导的时候))的所有可能输入(NFA中所指示的那样,但是不包括ε边)
- 譬如,(1,3,6)构成的状态集(元素个数x=3),的所有可能输入是单独看待NFA中1,3,6状态的所有可能的输入符号的并集(假设有y个互不相同的元素)
-
分析完输入符号集后,将状态集合中的每个状态分别尝试这些可能的输入,在最多的情况下,产生的新状态集可达到x*y个
- 但是通常不会那么多,因为,状态集中的某些状态是不接受输入符号集中的某些符号
- 另一方面,还可能发生回环,复用已有的状态
-
容易出错的两个方面
- DFA中的对终止态检测(遗忘该步骤)
- 对于NFA中的ε边的疏漏
例子4
- 构造下列正规式相应的 DFA.
1(0|1)*101
例子5
可以围绕这FA的各个状态节点,将出边标出
- T i T_i Ti是通过 ε − c l o s u r e \varepsilon-closure ε−closure计算得到
回顾转移函数的定义
- 我们将 T 1 到 T 4 T_1到T_4 T1到T4集合分别视为一个个整体作为互不相同的状态(DFA)的状态;并可以进一步简写为1,2,3,4
- 该定义还是基于NFA
- 转换函数 D ( S , a ) = R D(S,a)=R D(S,a)=R(此处a代表输入字符集合 ∑ \sum ∑中的任意元素);
- 最终,确定下来的转换函数D可以有前面计算并确定DFA状态集S(各个子集
T
i
Ti
Ti)时得出的结论分别收集记录下来;便可方便的得到DFA的
状态转移图
收获五元组
在计算子集的时候,可以将过程用表格的形式记录,这会方便于整理转换函数的总结
更多例子
构造下列正则表达式的DFA
- 1 ( 0 ∣ 1 ) ∗ 101 1(0|1)^*101 1(0∣1)∗101
- 1 ( 101 0 ∗ ∣ 1 ( 010 ) ∗ 1 ) ∗ 0 1(1010^*|1(010)^*1)^*0 1(1010∗∣1(010)∗1)∗0
- a ( ( a ∣ b ) ∗ ∣ a b ∗ a ) ∗ b a((a|b)^*|ab^*a)^*b a((a∣b)∗∣ab∗a)∗b
- b ( ( a b ) ∗ ∣ b b ) ∗ a b b((ab)^*|bb)^*ab b((ab)∗∣bb)∗ab
利用图表整理计算过程
下方例子中出现的符号说名
- 子集 I I I相当于前面说的子集族 C C C,收集各不相同的状态集,其数字别名,作为DFA的状态.
- I 0 , I 1 I_0,I_1 I0,I1作为算法内部的for循环的遍历结果(候选子集,候选子集也标记上相应的数字标号)
- 从表中可以方便的得到DFA的转换函数
-
同一行内的三个数据:例如第3行,可以解读出的转换函数(注意算法中转换函数的定义), - D(2,0)=2;
- D(2,1)=3
-
上面 I 1 I_1 I1列的{AF}应该是印刷错误,应该是{A}1
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了