[编译原理随记]正则表达式转为NFA状态图(Thompsion构造法)

上级文章

[编译原理随记]正则表达式记号和状态图:https://blog.csdn.net/qq_28033719/article/details/107067798

[编译原理随记]NFA转DFA子集构造算法:https://blog.csdn.net/qq_28033719/article/details/107068996


准备知识

语法制导:

解析(parse)输入的字符串时,在特定位置执行指定的动作。

语法制导算法:

其实就本文内容,解析字符串是什么状态(像>=通常弄个状态图,如果是关键字弄个表暂存比较适合)来说,这个算法相当于对字符串每一个字符进行下一个状态判断,就是 switch(string.charAt(i))这样逐个处理。

NFA状态数 = 正则表达式的 (符号 + 操作符数) * 2:

正则里面常有的运算符 *,| ,连接符

1、输入 ε 和 a(符号),NFA状态图为:

状态数都是 2 = 1(符号数为1) * 2

2、输入 s|t ,NFA状态图为:

状态数是 6 = 3(符号数2 + 操作数1) * 2

3、s*,NFA状态图为:

状态数是 4 = 2(符号数1 + 操作符数1) * 2

所以每多一个符号数或者操作符数,对原图新增的状态数最多2。

出边入边,出度入度:

前后者概念相近,出边入边本文指状态 A 转到下一个状态的边(A\overset{a}{\rightarrow})为出边,进入状态A的边(\overset{a}{\rightarrow}A)为入边.

而后者相对分化一点,就是专指有向图的出边A\overset{a}{\rightarrow} 的出边数量)和入边数(\overset{a}{\rightarrow}A 的入边数量)

NFA三点性质:

1、NFA状态数 = 正则表达式的 (符号 + 操作符数) * 2

2、开始状态无入边,终结状态无出边

3、每个状态有一个[A-Za-z0-9]出边,或者至多两个 ε


Thompsion构造法

例题:将正则表达式  (a|b)*abb 变为 NFA

先将复合正则变为最基本的子表达式,然后进行语法制导(一步一步来)。

1、先处理优先度高的组合 (a | b),首先开始无入边,终结无出边,然后状态有一个字符出边,或者至多两个 ε

2、处理 (a|b)*,一样,根据 NFA 三性质得出,6 -> 1 状态代表循环输入,1 不能作为开始(有入边)6不能作为终结(有出边):

3、再把 abb 拼上去就行了:

3展示的就是 从正则 (a|b)*abb 转的 状态图。


模拟NFA

用双栈模拟:

因为 ε-closure(move(T , a)), T代表一个状态集合 比如T={1,2}

然后S = ε-closure(move(T , a)), S代表T移动a之后的集合 比如 S={3,4},T S都是多个元素,不是一个确定状态(DFA就是一个)。

所以使用两个(存储方面可以理解为数组,只是crud操作不同),存放 S、T 两种状态的转换(因为状态不确定,所以下一个状态是一个集合)

具体算法是:

S := ε-closure(0);
a := nextchar; // 理解为超前标记
while a != eof do begin
    S := ε-closure(move(S , a));
    a := nextchar;
end

// F 为终结集合
if S != F then
    return "no";
return "yes";

当然,将 NFA -> DFA也可以不过,这两者都需要衡量空间复杂度和时间复杂度。


DFA 和 NFA 时间空间衡量

NFA 空间是 <= 正则表达式的 (符号 + 操作符数) * 2

DFA 空间是 <= 2 ^ n (n为(a|b)数量,因为这个或运算使用状态数最多,所以推断任意数量 符号 + 操作符数)

NFA 时间复杂度 = 正则表达式的 (符号 + 操作符数) * X(输入的字符串长度,因为每个字符串都匹配)

DFA 时间复杂度 = X(因为每个字符都有一个确定状态,不用遍历)

猜你喜欢

转载自blog.csdn.net/qq_28033719/article/details/107084565