📜  从RE构造FA(1)

📅  最后修改于: 2023-12-03 15:06:33.209000             🧑  作者: Mango

从RE构造FA

正则表达式(RE)是描述字符串模式的一种强大工具,但计算机更善于处理有限状态自动机(FA),因此将正则表达式转换为有限状态自动机是一个常见的操作。在本文中,我们将介绍如何从正则表达式构造有限状态自动机,并给出相应的代码片段。

正则表达式

正则表达式是一种字符串模式描述工具,它使用一组特殊字符和操作符来指定要匹配的文本。常见的操作符包括:

  • . 匹配任何单个字符。
  • * 匹配前一个元素零次或多次。
  • + 匹配前一个元素一次或多次。
  • ? 匹配前一个元素零次或一次。
  • | 用来表示"或"关系,匹配其中一个。
  • () 用来分组,将多个操作符包含在一起。

例如,正则表达式 ba(na)* 匹配字符串 ba 后面跟随任意多个 na

有限状态自动机

有限状态自动机(FA)是一种有限状态和转换的图形表示,它可以接受或拒绝一组字符串。FA包括以下几个部分:

  • 一组状态,通常由一个开始状态和一个或多个接受状态组成。
  • 转移函数,它将当前状态和输入字符作为参数,并返回下一个状态。
  • 输入字母表,它表示可能的输入字符集。

例如,下面是一个非确定有限状态自动机(NFA),它接受所有以 0 开头且以 1 结尾的字符串:

NFA

在这个NFA中,三个状态分别为 $q_0$、$q_1$ 和 $q_2$ 表示状态, 01 是输入字符。当 NFA 接受输入 "011" 并从状态 $q_0$ 开始时,它通过两条边转移到状态 $q_1$,然后由状态 $q_1$ 上的边到达状态 $q_2$,字符串输入完成,因此 NFA 接受输入字符串 "011"。

但是,对于计算机而言,FA要比NFA更合适,同样以 "011" 为例,NFA中可能会有多条路线去到 $q_2$,但都是合法的,但计算机不善于找最优选择,提交到计算机的数据需要满足开始节点为唯一的,接受状态只有一种。为此,我们需要将 NFA 转换为 FA。

从RE构造FA
构造NFA

首先,我们需要根据正则表达式构建一个非确定有限状态自动机(NFA)。我们可以使用 Thompson 构造算法。该算法通过递归地分解表达式,构建一个由状态和转换组成的 NFA。

例如,考虑正则表达式 ab+c.(其中 + 表示一次或多次重复, . 表示任何单个字符)。我们可以按照以下步骤构建NFA:

  1. 构建字符 a 的NFA,这是一个有两个状态 $1$$2$,其中 $1$ 是起始状态,$2$ 是接受状态。从 $1$$2$ 有一条标记为 a 的边。

  2. 连接一个 b+ 运算符,将创建一个新的起始节点 $0$ 和一个新的接受节点 $3$。从节点 $0$ 分别有两条空状态边,一条连接到节点 $1$,另一条连接到节点 $3$。从节点 $2$ 分别有两条空状态边,一条连接到节点 $1$,另一条连接到节点 $3$

  3. 构建字符 c 的NFA,这是一个有两个状态 $4$$5$,其中 $4$ 是起始状态,$5$ 是接受状态。从 $4$$5$ 有一条标记为 c 的边。

  4. 现在我们可以组合第一步和第三步的结果,根据 . 运算符将两个子表达式串联起来。由于 $2$$3$ 是“b+”运算符的接受状态,因此使用空边将 $2$ 连接到 $4$

完整的NFA如下所示:

graph LR;
0(( )) --> 1((q1));
0 --> 3((q3));
1 --> |a|2((q2));
3 --> |c|4((q4));
2 --> |ε|4;
4 --> |ε|5((q5));
将NFA转换为DFA

接下来,我们将 NFA 转换为更容易处理的确定有限状态自动机(DFA)。确定性有限状态自动机(DFA)是一个没有空转移的 NFA。

我们可以使用子集构造算法来完成此转换。该算法根据当前状态和输入字符构建一个状态集合,该集合由 NFA 的状态通过空边直接或间接到达的状态组成。我们将该集合作为 DFA 的一个状态,并使用图形表示形式表示 DFA。

例如,考虑上面的 NFA。下面是通过子集构造算法获得的等效 DFA:

graph LR;
0(( ))-->00((q1,q3));
00-->10((q2,q4));
00-->01((q3));
10-->11((q2,q4,q5));
01-->11;

我们可以看到,DFA 包含四个状态 0、00、01 和 10。状态 00 包括 NFA 的第一个和第三个状态。每个输入字符对应一个从当前状态到下一个状态的转换。例如,当输入为字符 a 且当前状态为 0 时,我们将转换到状态 00,这是 NFA 中状态 1 和 3 对应的状态集合。

最后一个状态11是 DFA 的接受状态,它被连接到 NFA 的接受状态 5。

DFA 的代码实现

对于 DFA,我们可以使用状态转移矩阵表示,其中矩阵的行表示 DFA 的状态,列表示输入符号。 DFA 的起始状态是状态0,接受状态为状态11。

# DFA矩阵表示
states = {'0': {'0': '00', '1': '01'},
        '00': {'a': '10', 'c': '01'},
        '01': {'c': '11'},
        '10': {'a': '10', 'c': '11'},
        '11': {'0': '01', '1': '01', 'a': '01', 'c': '01'}}

# DFA运行函数
def run_DFA(D, s):
    state = '0'
    for c in s:
        state = D[state].get(c)
        if state is None:
            return False
    return state == '11'

print(run_DFA(states, 'ac')) # True
print(run_DFA(states, 'accc')) # True
print(run_DFA(states, 'abc')) # False

以上代码片段实现了根据状态矩阵运行DFA,将其应用于上面的 NFA,可以接受以 0 开头且以 1 结尾的字符串。

总结

本文介绍了如何将正则表达式转换为确定有限状态自动机(DFA)。我们首先使用 Thompson 构造算法将正则表达式转换为非确定有限状态自动机(NFA),然后使用子集构造算法将 NFA 转换为 DFA,最后使用状态转移矩阵表示 DFA。

虽然 Thompson 和子集构造算法的复杂性我们并没有详细讲解,但本文提供了一个完整的例子,展示了如何从正则表达式构建 DFA。我们希望此文对读者有所帮助,以后也可以运用正则表达式更高效正确的处理自然语言。