NFA转化DFA
NFA既然和DFA等价,那么,它们之间就存在对应关系,DFA到NFA的转化是自明的:没有空转移,把返回的单个state编程仅包含一个state的集合,就是一个形式上的NFA。但是,NFA到DFA的转化就不是那么简单了,实际上,在计算理论中,它属于ExpSpace问题,是一类比NP问题更难的问题。
往简单了说,因为NFA的转移函数的返回值是个state集合,如果NFA的state数目为n,那么这个state集合的集合,就是整数[0,n)的幂集,这个幂集的元素数目是 2^n ,不错,在最坏情况下,包含n个状态的NFA对应的DFA有 2^n 个状态。虽然这个 让人很悲观,但是在实际情况中,大部分(除了那些精心构造的)NFA对应的DFA远远小于 2^n,并且往往只是O(n)。
具体的转化算法,叫做“子集构造法”,其实翻译成“幂集构造法”应该更合适。在实现该算法的过程中,有两点值得注意:
- ε闭包,在把一个新的NFA状态加入子集时,不是只要加入这个NFA状态,而是要加入该状态的整个ε闭包(需要用DFS或者BFS去计算)
- 对每个NFA状态子集按状态号排序,得到一个有序数组,再去重,然后把该数组作为Key,创建一个子集的集合(set of subset),即幂集(power set)
我实现该算法时,使用了adjacent difference 技术,即整个power set 是一个大数组,然后一个下标数组,每个下标指向一个subset的起始位置,两个下标想减即是subset的尺寸,这样内存用量更少。进一步的优化可以对subset使用差分编码(因为是有序的),即除了第一个状态号,后面的整数仅保存和前一项的差,再使用变长整数编码。
和 MinADFA_onfly一样,在这个算法的实现中用到了gold_hash_tab,不过这里对对gold_hash_map的使用非常trick,但的确同时优化了memory和speed!有兴趣的可以看一下代码。