通过NFA转化而成的DFA不一定是最简的,也就是说,有多余的状态可以被删除,对于每一个正规定义,我们一定可以得到一个唯一的最简的DFA
我们回顾一下Move函数,DFA的move函数:
move : (state, symbol) -> S
注意,这里(state, symbol)表示的是一个集合,这里规范的数学表达应该是:
move : { (state, symbol) | 所有属于DFA的state和symbol } -> S 或者
move : S × Σ -> S
假如一个DFA的move函数不是全函数,那么必须引入死状态。假如某个DFA的move函数是全函数,那么每个状态在所有input symbol下都有出边,比如:
这个DFA每个状态都可以接受所有的input symbol,这里是a,b。而下面的DFA:
先不要看红色部分,那么这个DFA的状态c,d,它们无法通过input symbol b 进入下一个状态,我们可以加上红色的部分,把这个move函数,转化成为一个全函数,并且,经过转化操作之后,新的DFA与原DFA等价。这个红色部分标识的状态,被叫做死状态
死状态:
假如出现DFA的move函数不是全函数,我们可以引入一个死状态S(仅仅引入一个方可),这个状态包括所有input symbol对自身的转换,所有的其他状态假如不接受某个input symbol a,那么,我们建立这个状态到S且input symbol为 a 的边。
状态的区别:
假如一个状态s,通过input string w,可以转换到某个状态,而某个状态t,通过w,转化到了一个与s通过w转化到的状态不同的状态,那么我们就可以通过w来区别状态s,t,如果这样的w不存在,那么s,t这2个状态是无法区别的。
每个接受状态都可以通过ε和非接受状态进行区别。
化简算法,极小化DFA的思想:
极小化DFA算法,它把状态分成一些不相交的子集,每一个子集中的所有状态都是不可区别的,而不同子集中的每个状态两两都是可区别的,最后我们把每个子集中的所有状态合成一个状态。
1) 划分状态集
首先把所有状态划分成为2个集合,一个集合是接受状态的集合,一个集合是非接受状态的集合,他们通过ε来区别。然后看每个集合中的状态时候还可以区别,例如一个集合通过input symbol a,转换后得到的状态落入当前划分的不同集合,那么说明通过input symbol a,是可以区别这个集合中的状态的(这里要强调的是,对于一个而不是多个input symbol,假如转换到的状态落入不同的划分中那么这些状态就是可以区别的)。我们假定有一个状态集合{s1,s2},s1通过a到达状态集合t1,s2通过a到达状态集合t2,t1,t2分别是当前划分的状态集合,那么,集合{s1,s2}就可以分成2个集合{s1},{s2}
2) 构造最简的DFA
我们可以重复1)的步骤,最后得到一些子集合,我们从每个子集合中取一个状态,通过它们可以得到最简的DFA,但具体需要按一定规则去构建
极小化DFA状态数的算法:
Input : 一个DFA M,它的状态集是S,输入符号集合Σ,move : S × Σ -> S,开始状态为s0,接受状态的集合为F
Output : 一个DFA N,它和DFA M等价,并为最简
Method :
1) 初始化: 假如move函数不是全函数,那么加入死状态,构造划分X:把S分成2个子集合,包括接受状态集合F和非接受状态集合S-F(F集合的补集)
2) Xnew是一个划分
for( X 中的每个集合G ){
G中状态每次通过Σ中的symbol转化到的状态如果属于X的不同子集,那么把集合G分成子集,每个symbol都可能划分G,划分之后,使用下一个symbol进行操作,一直到遍历完所有的input symbol
更新Xnew,用G的划分代替G
}
3) 如果Xnew == X,那么定义 Xfinal = X,执行4),否则进行赋值操作 X = Xnew,进行2)
4) Xfinal中每个子集合中选择一个状态来代表这个状态集合,包含s0的状态集合,就是表示开始状态的集合。通过DFA M来构造DFA N,规则是这样的:假如某状态p通过某input symbol a,通过DFA M的move函数转到另外一个状态q,我们就用q所在的集合的代表状态来表示q,并把这个转换过程的边,input symbol,集合的代表状态,加入DFA N中。我们需要遍历DFA M,然后按规则构建DFA N。化简的DFA中,可能有多个接受状态。
5) 如果N中有死状态(终态不是死状态),去掉它,有开始状态无法到达的状态,也去掉它。注意,在DFA N中有可能出现死状态,也就是通过所有的input symbol都回到自己的状态,前面说过,添加一个死状态得到的新的DFA与原DFA等价,那么我们这里也自然可以删除它。
在真正的实现上面算法的时候,是灵活的,因为出于时间复杂度的考虑,可能并不需要完全照搬上面的算法,把握主要的思想是很重要的。
1) 每个input symbol都可能划分一次集合
2) 每个集合都中的状态被看成是不可区别的,即使在计算过程中某些集合中的状态是可以区别的
3) 一定要确保每个集合都无法在分