编译原理——用C#实现正则表达式到最小DFA的转换

源码已公布在GitHub上,本来是写了这个功能给记事本提供正则表达式的搜索和替换功能,但是记事本那边暂时有一点点bug所以没放在一起。
如果想知道构造出来的自动机的结构,我写的代码支持用graphviz进行显示。代码里会告诉你怎么画出跟下面一样的图:编译原理——用C#实现正则表达式到最小DFA的转换
接下来我大致讲一下整个代码的结构,而具体的实现请参考GitHub里的代码,关键步骤都打了注释(应该)。

正则表达式转NFA

Thompson算法

Thompson算法是经典的NFA构造算法,以递归方式将正则表达式划分为若干子表达式,得到每个子表达式所对应的子NFA,再根据子表达式之间的运算关系构造完整的NFA。下图为4种基本子表达式(单独、连接、或、Kleene闭包)的NFA构造示例,其中:m1和m2分别表示2个状态机;ε表示空串匹配;s为初始状态;f为接受状态。
编译原理——用C#实现正则表达式到最小DFA的转换

正则表达式插入连接符号

因为输入的正则表达式都是长这样子’aabb’,是忽略了连接符号的结果,所以代码中要给他加入连接符号,即变为’a.a.b.b’。

转后缀式

如果要使用Thompson算法的话要把表达式转为后缀式才方便处理,即把上一节中的表达式’a.a.b.b’变为’aa.b.b.’

构造过程

每次读入后缀式的一个字符,如果是字母表上的就使用单字符匹配函数,如果是某种操作就调用该操作对应的函数即可。

NFA转DFA

NFA转DFA算法我采用的是子集构造(subset construction)算法。接下来在表1中给出子集构造算法中所需要的一些函数的定义。这些函数描述了一些需要在这个算法中执行的N的状态集上的基本操作。S表示N的单个状态,而T代表N的一个状态集。
编译原理——用C#实现正则表达式到最小DFA的转换
本来在word里写的,格式不好转就直接截图过来了。

注意

NFA转DFA的时候,经常需要在DFA集合中加入一个死状态。所谓死状态就是任何输入下都指向自己的状态。如果进入死状态就代表输入字符串已不符合该正则表达式的规则。

最小化DFA的状态数

DFA状态最小化算法的工作原理是将一个DFA的状态集合划分为多个组,每个组中的各个状态之间相互不可区分。然后,将每个组的状态中的状态合并成状态最少DFA的一个状态。算法在执行过程中维护了状态集合的一个划分,划分中的每个组内的各个状态尚不能区分,但是来自不同组的任意两个状态是可区分的。当任意一个组都不能再被分解为更小的组时,这个划分就不能再进一步精化,此时我们就得到了状态最少的DFA。
最初,该划分包含两个组:接收状态组和非接受状态组。算法的基本步骤是从当前划分中取一个状态组,比如A={s1, s2, …, sk},并选定某个输入符号a,检查a是否可以用于区分A中的某些状态。我们检查s1, s2, …, sk在a上的转换,如果这些转换到达的状态落入当前划分的两个或多个组中,我们就将A分割成为多个组,使得si和sj在同一组中当且仅当它们在a上的转换都到达同一个组的状态。我们重复这个分割过程,直到无法根据某个输入符号对任意个组进行分割位置。这个思想体现在代码2里。
编译原理——用C#实现正则表达式到最小DFA的转换
重复代码2,直到没有新的划分产生。然后每个组都选取一个代表。最小化DFA的开始状态是包含D的开始状态的组的代表。最小化DFA的接受状态是包含D的接受状态的组的代表。