当前位置: 代码迷 >> 综合 >> 词法分析——DFA 的最小化:Hopcroft 算法
  详细解决方案

词法分析——DFA 的最小化:Hopcroft 算法

热度:6   发布时间:2023-10-31 01:34:57.0

通过前面对于词法自动生成部分的学习,我们已经掌握了如何从源码生成到 DFA

那么为什么要对 DFA 进行最小化处理呢?

下面给出一个例子:

如下是我们之前写出的 a(b|c)* 的 NFA:

它可以对应转换成如下的 DFA:

在上面的 DFA 中非接受状态和接受状态是不能够合并的,因为如果合并,就会接受一个?\epsilon?串,这是明显不正确的。但是如果同样是接受状态或者同样是非接受状态的话就有可能。例如可以将q2q_2q2?q3q_3q3?进行合并,得到一个新的接受状态q4q_4q4?。得到新的 DFA,如下:

之后,我们还可以再对q1q_1q1?q5q_5q5?进行融合得到q5q_5q5?

这就是最终的状态最少的 DFA。

最小化得到状态最少的 DFA 的好处在于,因为 DFA 最后的代码实现是在作为内部的一个数据结构表示,如果状态和边越少,则它占用的计算资源(内存、cache)会更少 ,可以提高算法的运行效率和速度。

Hopcroft 算法

//基于等价类的思想
split(S)foreach(character c)if(c can split s)split s into T1, ..., Tkhopcroft()split all nodes into N, Awhile(set is still changes)split(s)


q1,q2,q3q_1,q_2,q_3q1?,q2?,q3?都有对状态 a 的转移,但是q1q_1q1?q2q_2q2?转移到了同样的一个状态 S2, q3q_3q3? 转移到了 S3。所以q1q_1q1?,q2q_2q2?可以看做一组,因为它们对 a 的行为是一致的,都到了 S2。q3q_3q3?单独一组。所以 a 这个字符将 S1 切为了两个子集。这就是等价类的思想。

Hopsroft 算法就是先根据非终结状态与非终结状态将所有的节点分为 N 和 A 两大类。 N 为非终结状态,A 为终结状态,之后再对每一组运用基于等价类实现的切割算法。

举个例子


对于之前给出的 DFA 的例子,我们首先将其切分为 N 和 A, N 是 q0q_0q0?, A 是 { q1q_1q1?,q2q_2q2?,q3q_3q3?}。

在 A 中,字符b,c的状态转移,每个节点最后得到的都还是 A 这个状态,无法对q1,q2,q3q_1,q_2,q_3q1?,q2?,q3?进行区分。所以就将这三个节点融合为一个新的节点q4q_4q4?

再给一个例子


N : { q0,q1,q2,q4q_0,q_1,q_2,q_4q0?,q1?,q2?,q4?}
A : { q3,q5q_3,q_5q3?,q5?}

在 N 中q0q_0q0?q1q_1q1?在接受 e 的条件下最终得到的状态还是在 N 的内部,但是q3q_3q3?q4q_4q4?接受e的条件下得到的状态是A。所以可以将其根据 e 拆分成 { q0,q1q_0,q_1q0?,q1?},{ q2,q4q_2,q_4q2?,q4?},{ q3,q5q_3, q_5q3?,q5?}

对于q2q_2q2?q4q_4q4?都可以接受e,而且最终达到的状态一致,所以不能再进行切分。

q0q_0q0?q1q_1q1?,在接受 e 的时候,q0q_0q0?最终得到还是在 { q0,q1q_0, q_1q0?,q1?}这个状态的结合中,q1q_1q1?却会落在{ q2,q4q_2,q_4q2?,q4?} 的状态中,所以可以将q0q_0q0?q1q_1q1?分为{ q0q_0q0?},{ q1q_1q1?}。