使用标准算法(例如汤普森算法),可以很容易地将正则表达式转换为接受相同语言的(最小)NFA 。但是,另一个方向似乎更乏味,有时结果表达式是混乱的。
有什么算法可以将NFA转换为等效的正则表达式?时间复杂度或结果大小是否有优势?
这应该是一个参考问题。请包括您的方法的一般说明以及不重要的示例。
使用标准算法(例如汤普森算法),可以很容易地将正则表达式转换为接受相同语言的(最小)NFA 。但是,另一个方向似乎更乏味,有时结果表达式是混乱的。
有什么算法可以将NFA转换为等效的正则表达式?时间复杂度或结果大小是否有优势?
这应该是一个参考问题。请包括您的方法的一般说明以及不重要的示例。
Answers:
有几种方法可以将有限自动机转换为正则表达式。在这里,我将描述通常在学校教授的一种非常视觉化的方法。我相信它是实践中使用最多的。但是,编写算法并不是一个好主意。
此算法与处理自动机的图有关,因此由于它需要图原语(例如...状态移除),因此不适用于算法。我将使用更高级别的原语对其进行描述。
这个想法是考虑边缘上的正则表达式,然后在保持边缘标签一致的同时删除中间状态。
在以下附图中可以看到主要模式。第一个在之间具有正则表达式标签,我们要删除。e ,f ,g ,h ,i q
删除后,我们将一起组成(同时保留和之间的其他边,但这未显示在该边上):p r
使用与Raphael的答案相同的示例:
我们相继删除:
然后:
那么我们仍然必须在从到的表达式上加星号。在这种情况下,最终状态也是初始状态,因此我们只需要添加一个星星即可:q 1
L[i,j]
是从到的语言的正则表达式。首先,我们删除所有多边:q Ĵ
for i = 1 to n:
for j = 1 to n:
if i == j then:
L[i,j] := ε
else:
L[i,j] := ∅
for a in Σ:
if trans(i, a, j):
L[i,j] := L[i,j] + a
现在,状态删除。假设我们要删除状态:
remove(k):
for i = 1 to n:
for j = 1 to n:
L[i,i] += L[i,k] . star(L[k,k]) . L[k,i]
L[j,j] += L[j,k] . star(L[k,k]) . L[k,j]
L[i,j] += L[i,k] . star(L[k,k]) . L[k,j]
L[j,i] += L[j,k] . star(L[k,k]) . L[k,i]
需要注意的是都与纸一支铅笔,一个算法,你应该简化般的表情star(ε)=ε
,e.ε=e
,∅+e=e
,∅.e=∅
(用手你不写时,它不是边缘,甚至的自我循环和你忽略存在时和或和之间没有过渡)εq ķ q Ĵ q ķ
现在,如何使用remove(k)
?您不应轻易删除最终状态或初始状态,否则会错过部分语言。
for i = 1 to n:
if not(final(i)) and not(initial(i)):
remove(i)
如果只有一个最终状态和一个初始状态则最终表达式为:q 小号
e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])
如果您有几个最终状态(甚至是初始状态),那么除了应用传递闭包方法之外,没有简单的方法来合并这些状态。通常,这不是手工问题,但是编写算法时很麻烦。一个更简单的解决方法是枚举所有对并在(已移除状态)图上运行算法以获取所有表达式假设是唯一的初始状态,而是唯一的最终状态状态,然后对所有。e s ,f s f e s ,f
这以及与第一种方法相比,它更动态地修改语言的事实使它在编程时更容易出错。我建议使用任何其他方法。
此算法有很多情况,例如,选择我们应删除的节点,最后的最终状态数,最终状态也可以是初始状态等。
请注意,既然已经编写了算法,这很像传递闭包方法。仅用法的上下文不同。我不建议实现该算法,但是使用该方法手动完成此操作是一个好主意。
ab
我见过的最好的方法是将自动机表达为可以解决的(常规)语言的方程组。它特别好,因为它似乎比其他方法产生更简洁的表达式。
令一个没有 -transitions 的NFA 。对于每个状态,创建等式ε q 我
其中是最终状态的集合,表示从到标有过渡。如果将读为或(取决于正则表达式定义),则会看到这是一个正则表达式的等式。q 我一→ q Ĵ q 我q Ĵ一个∪ + |
为了解决该系统,您需要和(字符串串联)的关联性和分布性,和Arden引理 ¹的可交换性:·&∪
让带有常规语言。然后,
解决方案是一组正则表达式,每个状态。准确地描述了在启动时可以接受的单词;因此,(如果是初始状态)是所需的表达式。
为了清楚起见,我们用单元素集的元素(即。该示例归因于Georg Zetzsche。
考虑以下NFA:
[ 来源 ]
相应的方程组是:
现在将第三个方程式插入第二个方程式:
最后一步,我们应用,和。注意,所有三种语言都是常规语言,并且,使我们能够应用引理。现在,我们将此结果插入第一个方程式:
因此,我们找到了上述自动机接受的语言的正则表达式,即
请注意,它非常简洁(与其他方法的结果相比),但不是唯一确定的。用不同的操作序列求解方程组会导致其他结果-等效!-表情。
maybe_union/2
谓词可能需要更多的工作(特别是wrt消除了公共前缀)来制作更整齐的正则表达式。看到这种方法的另一种方式是将其理解为从正则表达式转换为线性语法,在这种语言中,具有Prolog式统一或ML式模式匹配的语言是一个很好的转换器,因此它不仅是笔和纸算法:)
这与Raphael的答案中描述的方法相同,但是从系统算法的角度来看,然后从算法的角度来看。一旦您知道从哪里开始,事实证明,实施起来很容易,也很自然。如果由于某种原因绘制所有自动机不切实际,则可能会更容易手动完成。
在编写算法时,您必须记住方程必须始终是线性的,以便对方程具有良好的抽象表示,这是您在手工求解时会忘记的事情。
我不会描述它是如何工作的,因为我建议先阅读Raphael的回答,因为它做得很好。相反,我关注的是您应该按什么顺序求解方程式,而不需要进行过多的计算或特殊的情况。
从Arden规则的巧妙解到语言方程我们可以将自动机视为以下形式的一组方程:X = 甲X ∪ 乙
我们可以通过对进行归纳来解决此问题,方法是相应地更新数组和。在步骤,我们有:A i ,j B i ,j n
雅顿的规则给我们:
通过设置和我们得到:
然后我们可以通过设置来消除系统中所有需求:
当我们在求解时,我们得到如下方程:
没有。这样我们得到了正则表达式。
因此,我们可以构建算法。为了具有与上述归纳相同的约定,我们将说初始状态为,状态数为。首先,初始化以填充:
for i = 1 to m:
if final(i):
B[i] := ε
else:
B[i] := ∅
和:
for i = 1 to m:
for j = 1 to m:
for a in Σ:
if trans(i, a, j):
A[i,j] := a
else:
A[i,j] := ∅
然后解决:
for n = m decreasing to 1:
B[n] := star(A[n,n]) . B[n]
for j = 1 to n:
A[n,j] := star(A[n,n]) . A[n,j];
for i = 1 to n:
B[i] += A[i,n] . B[n]
for j = 1 to n:
A[i,j] += A[i,n] . A[n,j]
最终的表达式是:
e := B[1]
即使看起来方程组对于算法来说似乎太符号化,但该系统非常适合于实现。这是此算法在Ocaml中的实现(断开链接)。请注意,除了函数之外brzozowski
,所有内容都可以打印或用于Raphael的示例。注意,有一个令人惊讶的有效的正则表达式简化功能simple_re
。
这种方法很容易以算法的形式编写,但是会生成非常大的正则表达式,并且如果您手动进行操作则不切实际,主要是因为这过于系统化。但是,对于算法而言,这是一个很好且简单的解决方案。
令表示使用状态从到的字符串的正则表达式。令为自动机的状态数。
假设对于所有,您已经知道从到的正则表达式,而没有中间状态(四肢除外)。然后,您可以猜测添加另一个状态将如何影响新的正则表达式:仅当您直接转换到,它才会,并且可以这样表示:
(为,为。)
我们将使用与Raphael的答案相同的示例。首先,您只能使用直接转换。
这是第一步(请注意,带有标签的自循环会将第一个转换为。
在第二步中,我们可以使用(因为我们已经将用于上面的目的,所以我们将其重命名为)。我们将看到是如何工作的。
从到:。
这是为什么?这是因为仅使用作为中间状态就可以从到方法是待在这里()或转到(),在此处循环()然后返回()。
您也可以像这样计算和,并且会为您提供最终表达式,因为既是初始的,也是最终的。注意,这里已经做了很多表达式的简化。否则,的第将是,而的第将是。
初始化:
for i = 1 to n:
for j = 1 to n:
if i == j:
R[i,j,0] := ε
else:
R[i,j,0] := ∅
for a in Σ:
if trans(i, a, j):
R[i,j,0] := R[i,j,0] + a
传递闭包:
for k = 1 to n:
for i = 1 to n:
for j = 1 to n:
R[i,j,k] := R[i,j,k-1] + R[i,k,k-1] . star(R[k,k,k-1]) . R(k,j,k-1)
然后,最终表达式为(假设是初始状态):
e := ∅
for i = 1 to n:
if final(i):
e := e + R[s,i,n]
但是您可以想象它会生成难看的正则表达式。您确实可以期望像表示与相同的语言。注意,简化正则表达式在实践中很有用。一个一个