NFA转DFA的子集构造(subset construction)算法
之前学习编译原理的时候老师有讲过子集构造法,当时我以为自己听懂了,信心满满。可是这两天我做了一些题目,发现自己实际上还是太嫩了,学习浮于表面。之后又重新看了龙书和虎书,对子集构造法有了更深层次的了解。特此发出一篇文章分享我的经验。
1 概念
概念是我们学习编译原理的重中之重,虽然他很晦涩难懂,但我有必要将其放在最开始。
1.1 虎书概念
虎书的概念更偏向于理论化,我当时看的时候一头雾水,但是不要担心,之后会一点一点解释的。
首先,我们形式化定义闭包如下:
-
:状态
沿着标有
的边可到达的所有NFA状态的集合;
-
: 对于状态集合
,从
出发,只通过
边可以达到的状态集合;
- 这种经过
边的概念可以用数学方法表述,即
是满足如下条件的最小集合
:
- 我们可以用迭代法来算出
:
- 这种经过
解释一下:当我们位于一个状态集合
,
里任意状态经过若干
能够到达的状态,都将包含在
里。
- 龙书里将这个操作定义为
(
为状态集合)。
现在,假设我们位于由NFA状态组成的集合
中。从
中的状态出发,输入符号
,将到达NFA新的状态集;我们称这个状态集为
:
解释一下:将遍历集合
中的所有状态,得到
关于
的状态集,并对
求
,得到的即为
。简而言之,就是从一个状态集,经过一个输入到达的状态集为
。
利用能更形式化地写出NFA模拟算法。如果初态是
,输入字符串是
,则算法为:
有了和
算法,就能构造出DFA,DFA的状态
就是
。抽象而言,如果
则存在一条从
到
的标记为
的边。令
是字母表:
解释一下:
代表了最终DFA的一个状态所对应的NFA状态集,
为初始状态,
代表了初始状态
的闭包。上文中的代码实际上和龙书的代码一个意思,龙书的代码更加简单直白,所以这里可以跳过。等看完下面的龙书再回头来看
1.2 龙书概念
个人认为龙书的概念更加通俗易懂,但是由于没有数学公式的归纳,导致理论基础不扎实,有点慌。所以推荐两本书一起看。
首先,是概念:
- 子集构造法的基本思想是让构造得到的DFA的每个状态对应NFA的一个状态集合。DFA在读入
之后到达的状态应该对应于相应的NFA从开始状态出发,沿着以
为边的路径能达到的状态的集合。
解释一下:概念很直观哈,我就不解释了^_^
接着,是算法:
- 输入:一个NFA N
- 输出:一个接受同样语言的DFA D
- 方法:我们为算法 D 构造一个转换表
。D的每个状态是一个NFA集合,构造
,使得 D “并行地”模拟 N 在遇到一个给定输入串可能执行的所有动作。下面我们给出一些函数的定义:
| 操作 | 描述 |
|---|---|
| 能够从NFA状态 |
|
| 能够从 |
|
| 能够从 |
- 在读入第一个符号之前,N可以位于集合
中的任何状态上 ,其中,
是 N 的开始状态。
-
下面进行回归纳:假定N在读入输入串
之后可以位于集合T的任意状态上。如果下一个输入符号是
,那么N可以立即移动到集合
中的任何状态。然而,N 可以读入
之后再执行几个
转换,因此 N 在读入
之后可以位于
中的任意状态上。接着我们可以构造出转换函数
:
- 一开始,
是
的唯一状态,且它未标记(请注意,“标记”是非常重要的概念);
- 一开始,
解释一下:这部分代码和虎书上的代码意思相近,这个更好理解。算法里的
每个
都可能是DFA的一个状态。
2 举个例子解释
-
题目:给定一个正则表达式
的NFA,我们使用子集构造法构造DFA。
NFA -
解法:首先,我们分析得出,NFA的初始为状态0。因而初始状态集
。
-
被加上标记,对于输入符号
,分别求出:
-
都没有被标记,因而将
依次加上标记,对于输入符号
,分别求出:
- 现在只剩
没有加标记,因而给
加上标记,对于输入符号
,分别求出:
- 还剩一个
没有标记,因而给
加上标记,对于输入符号
,分别求出:
- 所有构造出来的集合都已经被标记,构造完成!
为五个不同状态:
-
接着就是根据状态来画图了,最好先画好状态表:
子集构造状态表
-
解释一下:由此可知,
通过
,连到
,以此类推。就可以做出DFA图了^_^
转换后的DFA状态图
3 如何最小化DFA的状态数量
很简单,如果开始于的机器接收字符串
,始于
的和始于与
接收的串相同,并到达相同状态,且两个状态集同为终态或者非终态,那么
是等价的。我们可以把指向
的连线全部指向
,并删除
,反之亦然。
-
举个书上的例子:
书上的NFA转DFA示例图 - 图中的
是等价的,还有
也是等价的。
Tips:在判断是否等价前,我们要先判断是否为死状态哦(1.不能到达终态 2.从开始没有路径指向这个状态)。
4 总结
NFA转DFA知识总结就到这里,有什么问题请留言,有错误请批评指正,非常感谢您的阅读。