欢迎光临散文网 会员登陆 & 注册

【算法】直接由正则表达式构造DFA的算法

2020-05-04 08:20 作者:GC_CH  | 我要投稿

    首先说一下正则表达式的本质,顾名思义,正则表达式是一种表达式,同算术表达式一样是有基本单元(因子)加上运算符构成的一种运算系统。

    算数表达式的因子是 ,而正则表达式的因子是 字符。算术表达式的基本四则运算是 +-*/;而正则表达式的基本运算是 (连接)|(并)*(闭包)。由于连接省略了运算符,不太方便表示,所以以下用  表示连接。

    算术表达式的结果是一个数,而正则表达式的结果是一个字符串。


抽象语法树

    我们用抽象语法树来描述一个正则表达式,以便能够直观地看到该正则表达式的结构。关于抽象语法树的知识,需要的话可以另说。

    简单的说,抽象语法树就是用 因子 作为叶子节点,而用运算符作为内部节点来连接叶子节点的一种树。

    比如,(a|b)*abb的抽象语法树为:

    上图中的数字是给字符的编号。该图应该不难看懂,看不懂的可以提出来,篇幅所限,就不多说了。

算法的推导

    要构造DFA,则我们必须知道:

(1)开始时可以出现哪些符号,由于符号是有重复的,如上图中的a,而且上图中的a是不等价的,所以只能给它们赋予不同的编号来加以区分,我们也使用编号的集和来表示DFA的一个状态;

(2)接下来可以出现哪些编号;

(3)接下来的接下来可以出现哪些编号;

(4)接下来的接下来的接下来可以出现哪些编号......

(5)最后可以出现哪些编号。

OK,知道了这些,实际上就知道了DFA的所有状态,DFA的状态对应一个编号集和,以下统称 状态

计算过程

    开始状态就是抽象语法的根节点的开始编号集和,结束状态就是根节点的结束编号集和,而中间状态则是紧跟着某个状态的编号集和。

    这些信息只需要一次对抽象语法树的遍历就可以求出来了。

    以下称开始编号集为firstpos集,结束编号集为lastpos集,用nullable来表示一个节点是否可空,可空的意思就是会产生空串,比如正则表达式  a? 就是可空的,它可以什么都不匹配。还有一个集和是followpos集,它是编号的后跟编号的集和,比如 正则表达式 ab,由于b紧跟在a后面,所以b的编号在 followpos(a) 中。

    以下是计算过程。

对于任意节点 n:

(1)如果它是 编号为 i 的叶子节点,那么

nullable(n) = false, 

firstpos(n) = {i},

lastpos(n) = {i} ;

(2)如果它是一个 并节点,也就是 n = n1 | n2,那么

nullable(n) = nullable(n1) || nullable(n2),

firstpos(n) = firstpos(n1) ∪ firstpos(n2),

lastpos(n) = lastpos(n1) ∪ lastpos(n2);

(3)如果它是一个 连接节点,也就是 n = n1 n2,那么

nullable(n) = nullable(n1) && nullable(n2),

firstpos(n) = nullable(n1) ? firstpos(n1) ∪ firstpos(n2) : firstpos(n1),

lastpos(n) = nullable(n2) ? lastpos(n1) ∪ lastpos(n2) : lastpos(n2);

(3)如果它是一个 闭包节点,也就是 n = n1 *,那么

nullable(n) = true,

firstpos(n) = firstpos(n1),

lastpos(n) = lastpos(n1)。

    最后是followpos的计算,这个很简单,只有两种情况会使一个编号在另一个编号后面:

(1)如果n是一个连接节点,也就是 n = n1 n2,则

所有 p ∈ lastpos(n1),有 followpos(p) = followpos(p) ∪ firstpos(n2);

(2)如果n是一个闭包节点,也就是 n = n1 *,则

所有 p ∈ lastpos(n1),有 followpos(p) = followpos(p) ∪ firstpos(n1)。

    

    值得指出的是,我们需要的只有 抽象语法树的根节点的 firstpos集 以及 所有编号的follopos集,其他的都是可以舍去的临时信息。

    举例分析,上图中,只有 * 节点是可空的;其他信息如下图,节点左边红色的信息是它的firstpos集,右边蓝色的信息是它的lastpos集:

    它们的followpos集是:

编号                            followpos

1                                 {1, 2, 3}

2                                 {1, 2, 3}

3                                {4}

4                                {5}

5                                Φ

构造DFA

    我们使用一个状态集Dstates来保存所有的状态(也就是编号集),使用一个表Dtran来表示转换,算法如下:

(1)初始化Dstates,使之只包含未标记的状态 firstpos(n0),n0是抽象语法的根节点,未标记就是未处理过该状态的转换;

(2)

while(Dstates中存在未标记的状态S)

{

    标记S;

    for(每个输入符号a)

    {

        求出 编号集U = S中所有与a对应的编号的followpos集的并集;

        if( U 不在 Dstates中)

            将U作为未标记的状态加入到Dstates中;

        Dtran[S, a] = U;

    }

}

举例

    上述例子中的开始编号集为 {1,2,3},对应的DFA的开始状态 0 ,首先将其加入到Dstates中。

    接下来,由于它未标记,所以取出它,并分析它的转换,S = {1,2,3};

    标记S;

    对于符号a,S中有编号1,3和a对应,并且followpos(1) = {1, 2, 3}, followpos(3) = {4},则

S在a上的转换 U = followpos(1) ∪ followpos(3) = {1, 2, 3, 4};

    U不在Dstates中,故将U加入Dstates,因此U就是1号状态了。

    设置 Dstran[S, a] = U,也就是 Dstran[0, a] = 1。

    对于符号b,S中有编号2与其对应,并且followpos(2) = {1, ,2 3},则S在b上的转换 U = {1, 2, 3},U已经在Dstates中了,所以不需要加入了,只需要设置 Dtran[S, b] = U,也就是

Dtran[0, b] = 0 就行了。


    依次类推可以得出DFA转换表为:

状态        a        b        其他

0             1        0

1             1        2

2             1        3

3             1        0         接受


其他说明

    (1)抽象语法树是给人看的,实际上并不需要构造出来就可以求出所需的各种集和;

    (2)可以给正则表达式连接上一个结束符来表示接受,比如说连接上一个 #,则所有在#上有转换的状态都是接受状态;

    (3)参考 《编译原理》第二版(龙书)109页到114页;

    (3)还请支持一下,非常感谢!



【算法】直接由正则表达式构造DFA的算法的评论 (共 条)

分享到微博请遵守国家法律