如何将有限自动机转换为正则表达式?


115

使用标准算法(例如汤普森算法),可以很容易地将正则表达式转换为接受相同语言的(最小)NFA 。但是,另一个方向似乎更乏味,有时结果表达式是混乱的。

有什么算法可以将NFA转换为等效的正则表达式?时间复杂度或结果大小是否有优势?

这应该是一个参考问题。请包括您的方法的一般说明以及不重要​​的示例。


2
请注意cstheory.SE中类似问题,问题可能不适合我们的受众。
拉斐尔

所有答案都使用形式技术来编写DFA中的RE。我相信我的分析技术相对容易,而且客观,我在回答中展示了这种确定性有限自动机的语言是什么?我觉得这会有所帮助。是的,当然,我有时会使用形式化方法(Arden定理)来编写RE,这个问题很复杂,如本例所示:如何为DFA编写正则表达式
Grijesh Chauhan 2014年

Answers:


94

有几种方法可以将有限自动机转换为正则表达式。在这里,我将描述通常在学校教授的一种非常视觉化的方法。我相信它是实践中使用最多的。但是,编写算法并不是一个好主意。

状态删除方法

此算法与处理自动机的图有关,因此由于它需要图原语(例如...状态移除),因此不适用于算法。我将使用更高级别的原语对其进行描述。

关键思想

这个想法是考虑边缘上的正则表达式,然后在保持边缘标签一致的同时删除中间状态。

在以下附图中可以看到主要模式。第一个在之间具有正则表达式标签,我们要删除。e f g h i qp,q,re,f,g,h,iq

pqr自动机

删除后,我们将一起组成(同时保留和之间的其他边,但这未显示在该边上):p re,f,g,h,ipr

在此处输入图片说明

使用与Raphael的答案相同的示例:

1-2-3自动机

我们相继删除:q2

1-3自动机

然后:q3

1个自动机

那么我们仍然必须在从到的表达式上加星号。在这种情况下,最终状态也是初始状态,因此我们只需要添加一个星星即可:q 1q1q1

(ab+(b+aa)(ba)(a+bb))

算法

L[i,j]是从到的语言的正则表达式。首先,我们删除所有多边:q Ĵqiqj

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

现在,状态删除。假设我们要删除状态:qk

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 ķqiqkqjqk

现在,如何使用remove(k)?您不应轻易删除最终状态或初始状态,否则会错过部分语言。

for i = 1 to n:
  if not(final(i)) and not(initial(i)):
    remove(i)

如果只有一个最终状态和一个初始状态则最终表达式为:q 小号qfqs

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(s,f)es,fsfes,f

这以及与第一种方法相比,它更动态地修改语言的事实使它在编程时更容易出错。我建议使用任何其他方法。

缺点

此算法有很多情况,例如,选择我们应删除的节点,最后的最终状态数,最终状态也可以是初始状态等。

请注意,既然已经编写了算法,这很像传递闭包方法。仅用法的上下文不同。我不建议实现该算法,但是使用该方法手动完成此操作是一个好主意。


1
在该实例中,第二图像,去除节点后“2”,有一个边缘缺失-在节点A环边缘(AB)
帕诺斯卡尔。

@Kabamaru:固定。但是现在我认为第三张图像中的也应该是,并且类似地在最终的正则表达式中也是如此。εab
Wandering Logic

通过添加新的初始和新的最终状态并通过 -edges 将它们连接到原始的初始和最终状态,可以使该算法适用于任意数量的初始和最终状态。现在删除所有原始状态。然后在从到的单个剩余边找到表达式。构造不会在或处给出循环,因为这些状态没有。向外的边缘。或者,如果您很严格,他们将有代表空集的标签。q - ε q + q - q + q -q+qεq+qq+q
亨德里克

1
第二个示例仍然存在问题:在简化之前,自动机接受“ ba”(1、3、1),但是在简化之后,它不接受。
wvxvw

50

方法

我见过的最好的方法是将自动机表达为可以解决的(常规)语言的方程组。它特别好,因为它似乎比其他方法产生更简洁的表达式。

令一个没有 -transitions 的NFA 。对于每个状态,创建等式ε q A=(Q,Σ,δ,q0,F)εqi

Qi=qiaqjaQj{{ε}, qiF, else

其中是最终状态的集合,表示从到标有过渡。如果将读为或(取决于正则表达式定义),则会看到这是一个正则表达式的等式。q q Ĵ q q Ĵ一个+ |Fqiaqjqiqja+

为了解决该系统,您需要和(字符串串联)的关联性和分布性,和Arden引理 ¹的可交换性:·&

让带有常规语言。然后,L,U,VΣεU

L=ULVL=UV

解决方案是一组正则表达式,每个状态。准确地描述了在启动时可以接受的单词;因此,(如果是初始状态)是所需的表达式。QiqiQiAqiQ0q0


为了清楚起见,我们用单元素集的元素(即。该示例归因于Georg Zetzsche。a={a}

考虑以下NFA:

示例nfa
[ 来源 ]

相应的方程组是:

Q0=aQ1bQ2εQ1=bQ0aQ2Q2=aQ0bQ1

现在将第三个方程式插入第二个方程式:

Q1=bQ0a(aQ0bQ1)=abQ1(baa)Q0=(ab)(baa)Q0

最后一步,我们应用,和。注意,所有三种语言都是常规语言,并且,使我们能够应用引理。现在,我们将此结果插入第一个方程式:L=Q1U=abV=(baa)Q0εU={ab}

Q0=a(ab)(baa)Q0baQ0bb(ab)(baa)Q0ε=((abb)(ab)(baa)ba)Q0ε=((abb)(ab)(baa)ba)(by Arden's Lemma)

因此,我们找到了上述自动机接受的语言的正则表达式,即

((a+bb)(ab)(b+aa)+ba).

请注意,它非常简洁(与其他方法的结果相比),但不是唯一确定的。用不同的操作序列求解方程组会导致其他结果-等效!-表情。


  1. 有关Arden引理的证明,请参见此处

1
该算法的时间复杂度是多少?产生的表达式的大小是否有界限?
jmite 2014年

@jmite:我不知道。我认为我不会尝试实现这一点(在这方面其他方法似乎更可行),但可以将其用作笔-纸方法。
拉斐尔

1
这是该算法的Prolog实现:github.com/wvxvw/intro-to-automata-theory/blob/master/automata/…,但其maybe_union/2谓词可能需要更多的工作(特别是wrt消除了公共前缀)来制作更整齐的正则表达式。看到这种方法的另一种方式是将其理解为从正则表达式转换为线性语法,在这种语言中,具有Prolog式统一或ML式模式匹配的语言是一个很好的转换器,因此它不仅是笔和纸算法:)
wvxvw

只是一个问题。第一个方程中的ε是因为Qo是起始状态还是因为它是最终状态?如果我有两个最终状态,是否采用同样的方法?
Georgio3 '16

@PAOK检查的定义上述(行); 这是因为是最终状态。q 0Qiq0
拉斐尔

28

Brzozowski代数方法

这与Raphael的答案中描述的方法相同,但是从系统算法的角度来看,然后从算法的角度来看。一旦您知道从哪里开始,事实证明,实施起来很容易,也很自然。如果由于某种原因绘制所有自动机不切实际,则可能会更容易手动完成。

在编写算法时,您必须记住方程必须始终是线性的,以便对方程具有良好的抽象表示,这是您在手工求解时会忘记的事情。

算法的思想

我不会描述它是如何工作的,因为我建议先阅读Raphael的回答,因为它做得很好。相反,我关注的是您应该按什么顺序求解方程式,而不需要进行过多的计算或特殊的情况。

Arden规则的巧妙解到语言方程我们可以将自动机视为以下形式的一组方程:X = X X=ABX=AXB

Xi=Bi+Ai,1X1++Ai,nXn

我们可以通过对进行归纳来解决此问题,方法是相应地更新数组和。在步骤,我们有:A i j B i j nnAi,jBi,jn

Xn=Bn+An,1X1++An,nXn

雅顿的规则给我们:

Xn=An,n(Bn+An,1X1++An,n1Xn1)

通过设置和我们得到:Bn=An,nBnAn,i=An,nAn,i

Xn=Bn+An,1X1++An,n1Xn1

然后我们可以通过设置来消除系统中所有需求:Xni,j<n

Bi=Bi+Ai,nBn
Ai,j=Ai,j+Ai,nAn,j

当我们在求解时,我们得到如下方程:Xnn=1

X1=B1

没有。这样我们得到了正则表达式。A1,i

算法

因此,我们可以构建算法。为了具有与上述归纳相同的约定,我们将说初始状态为,状态数为。首先,初始化以填充:q1mB

for i = 1 to m:
  if final(i):
    B[i] := ε
  else:
    B[i] := ∅

和:A

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


4
链接已死...
Columbo


24

传递闭合法

这种方法很容易以算法的形式编写,但是会生成非常大的正则表达式,并且如果您手动进行操作则不切实际,主要是因为这过于系统化。但是,对于算法而言,这是一个很好且简单的解决方案。

关键思想

令表示使用状态从到的字符串的正则表达式。令为自动机的状态数。Ri,jkqiqj{q1,,qk}n

假设对于所有,您已经知道从到的正则表达式,而没有中间状态(四肢除外)。然后,您可以猜测添加另一个状态将如何影响新的正则表达式:仅当您直接转换到,它才会,并且可以这样表示:Ri,jqiqjqki,jRi,jqk

Ri,j=Ri,j+Ri,k.Rk,k.Rk,j

(为,为。)RRk1RRk

我们将使用与Raphael的答案相同的示例。首先,您只能使用直接转换。

这是第一步(请注意,带有标签的自循环会将第一个转换为。aε(ε+a)

R0=[εabbεaabε]

在第二步中,我们可以使用(因为我们已经将用于上面的目的,所以我们将其重命名为)。我们将看到是如何工作的。q0q1R0R1

从到:。q2q2R2,21=R2,20+R2,10R1,10R1,20=ε+bεa=ε+ba

这是为什么?这是因为仅使用作为中间状态就可以从到方法是待在这里()或转到(),在此处循环()然后返回()。q2q2q1εq1aεb

R1=[εabbε+baa+bbab+aaε+ab]

您也可以像这样计算和,并且会为您提供最终表达式,因为既是初始的,也是最终的。注意,这里已经做了很多表达式的简化。否则,的第将是,而的第将是。R2R3R1,131aR0(+a)aR1((+a)+ε(ε)a)

算法

初始化:

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)

然后,最终表达式为(假设是初始状态):qs

e := ∅
for i = 1 to n:
  if final(i):
    e := e + R[s,i,n]

但是您可以想象它会生成难看的正则表达式。您确实可以期望像表示与相同的语言。注意,简化正则表达式在实践中很有用。一个一个()+(a+())(ε)(a+)aa

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.