Come convertire gli automi finiti in espressioni regolari?


115

La conversione di espressioni regolari in NFA (minimo) che accetta la stessa lingua è facile con algoritmi standard, ad esempio l'algoritmo di Thompson . L'altra direzione sembra essere più noiosa, tuttavia, e talvolta le espressioni risultanti sono disordinate.

Quali algoritmi esistono per convertire NFA in espressioni regolari equivalenti? Ci sono vantaggi in termini di complessità temporale o dimensioni dei risultati?

Questa dovrebbe essere una domanda di riferimento. Si prega di includere una descrizione generale del metodo e un esempio non banale.


2
Nota una domanda simile su cstheory.SE che probabilmente non è adatta al nostro pubblico.
Raffaello

tutte le risposte usano la tecnica formale per scrivere RE da DFA. Credo che la mia tecnica per analisi sia relativamente semplice e obiettivo che dimostro nella mia risposta: qual è il linguaggio di questi automi finiti deterministici? Sento che sarebbe utile qualche volta. Sì, certo che a volte io stesso uso il metodo formale (teorema di Arden) per scrivere RE è una domanda complessa come quella fornita in questo esempio: Come scrivere un'espressione regolare per un DFA
Grijesh Chauhan,

Risposte:


94

Esistono diversi metodi per eseguire la conversione da automi finiti a espressioni regolari. Qui descriverò quello che di solito viene insegnato a scuola, che è molto visivo. Credo che sia il più usato in pratica. Tuttavia, scrivere l'algoritmo non è una buona idea.

Metodo di rimozione dello stato

Questo algoritmo riguarda la gestione del grafico dell'automa e non è quindi molto adatto agli algoritmi poiché ha bisogno di primitive grafiche come ... rimozione dello stato. Lo descriverò usando primitivi di livello superiore.

L'idea chiave

L'idea è quella di considerare le espressioni regolari sui bordi e quindi rimuovere gli stati intermedi mantenendo coerenti le etichette dei bordi.

Lo schema principale può essere visto nelle figure seguenti. Il primo ha etichette tra che sono espressioni regolari e , f , g , h , i e vogliamo rimuovere q .p,q,re,f,g,h,iq

automa pqr

Una volta rimosso, componiamo insieme (preservando gli altri bordi tra p e r ma questo non viene visualizzato su questo):e,f,g,h,ipr

inserisci qui la descrizione dell'immagine

Esempio

Usando lo stesso esempio della risposta di Raffaello :

1-2-3 automa

rimuoviamo successivamente :q2

Automa 1-3

e quindi :q3

1 automa

allora dobbiamo ancora applicare una stella sull'espressione da a q 1 . In questo caso, anche lo stato finale è iniziale, quindi è sufficiente aggiungere una stella:q1q1

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

Algoritmo

L[i,j]è la regexp della lingua da a q j . Innanzitutto, rimuoviamo tutti i bordi multipli: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

Ora, la rimozione dello stato. Supponiamo di voler rimuovere lo stato :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 k q j q kqiqkqjqk

Ora, come si usa remove(k)? Non dovresti rimuovere leggermente gli stati finali o iniziali, altrimenti perderai parti della lingua.

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

Se hai solo uno stato finale e uno stato iniziale l'espressione finale è:q sqfqs

e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])

Se hai diversi stati finali (o addirittura stati iniziali), non esiste un modo semplice di unire questi, se non quello di applicare il metodo di chiusura transitiva. Di solito questo non è un problema a mano ma questo è imbarazzante quando si scrive l'algoritmo. Una soluzione molto più semplice consiste nell'enumerare tutte le coppie ed eseguire l'algoritmo sul grafico (già rimosso dallo stato) per ottenere tutte le espressioni supponendo che sia l'unico stato iniziale e sia l'unico finale stato, quindi facendo l'unione di tutti .e s , f s f e s , f(s,f)es,fsfes,f

Questo e il fatto che questo sta modificando i linguaggi in modo più dinamico rispetto al primo metodo lo rendono più soggetto a errori durante la programmazione. Suggerisco di usare qualsiasi altro metodo.

Contro

Ci sono molti casi in questo algoritmo, ad esempio per scegliere quale nodo dovremmo rimuovere, il numero di stati finali alla fine, il fatto che anche uno stato finale può essere iniziale, ecc.

Si noti che ora che l'algoritmo è stato scritto, è molto simile al metodo di chiusura transitiva. Solo il contesto dell'uso è diverso. Non consiglio di implementare l'algoritmo, ma usare il metodo per farlo manualmente è una buona idea.


1
Nell'esempio, seconda immagine, dopo aver rimosso il nodo "2", c'è un bordo mancante - bordo del loop (ab) nel nodo A.
Panos Kal.

@Kabamaru: risolto. Ma ora penso che dovrebbe essere anche il nella terza immagine , e allo stesso modo forse nell'espressione regolare finale. εab
Wandering Logic,

Puoi far funzionare l'algoritmo per qualsiasi numero di stati iniziali e finali aggiungendo un nuovo iniziale e un nuovo stato finale e collegandoli agli stati iniziali e finali originali con -edges. Ora rimuovi tutti gli stati originali. L'espressione viene quindi trovata sul singolo bordo rimanente da a . La costruzione non darà loop in o poiché questi stati non hanno resp in entrata. bordi in uscita. O se sei severo, avranno etichette che rappresentano l'insieme vuoto. q - ε q + q - q + q -q+qεq+qq+q
Hendrik,

1
C'è ancora un problema con il secondo esempio: prima della semplificazione gli automi accettano "ba", (1, 3, 1) ma dopo la semplificazione non lo fanno.
wvxvw,

50

Metodo

Il metodo più bello che ho visto è quello che esprime l'automa come sistema di equazione di linguaggi (regolari) che può essere risolto. È particolarmente bello in quanto sembra produrre espressioni più concise rispetto ad altri metodi.

Sia un NFA senza -transitions. Per ogni stato , crea l'equazioneA=(Q,Σ,δ,q0,F)εqi

Qi=qiaqjaQj{{ε}, qiF, else

dove è l'insieme degli stati finali e significa che c'è una transizione da a etichettata con . Se leggi come o (a seconda della definizione delle tue espressioni regolari), vedi che questa è un'equazione di espressioni regolari.Fqiaqjqiqja+

Per risolvere il sistema sono necessari associatività e distributività di e (concatenazione di stringhe), commutatività di e Lemma di Arden ¹:

Let linguaggi regolari con . Poi,L,U,VΣεU

L=ULVL=UV

La soluzione è un insieme di espressioni regolari , una per ogni stato . descrive esattamente quelle parole che possono essere accettate da quando avviato in ; pertanto (se è lo stato iniziale) è l'espressione desiderata.QiqiQiAqiQ0q0


Esempio

Per motivi di chiarezza, denotiamo insiemi singleton per il loro elemento, ovvero . L'esempio è dovuto a Georg Zetzsche.a={a}

Considera questo NFA:

esempio nfa
[ fonte ]

Il sistema di equazione corrispondente è:

Q0=aQ1bQ2εQ1=bQ0aQ2Q2=aQ0bQ1

Ora collega la terza equazione nella seconda:

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

Per l'ultimo passaggio, applichiamo il Lemma di Arden con , e . Nota che tutte e tre le lingue sono regolari e , permettendoci di applicare il lemma. Ora inseriamo questo risultato nella prima equazione: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)

Pertanto, abbiamo trovato un'espressione regolare per la lingua accettata dall'automa sopra, vale a dire

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

Si noti che è abbastanza sintetico (confrontare con il risultato di altri metodi) ma non determinato in modo univoco; risolvere il sistema di equazioni con una diversa sequenza di manipolazioni porta ad altri - equivalenti! - espressioni.


  1. Per una prova del Lemma di Arden, vedi qui .

1
Qual è la complessità temporale di questo algoritmo? C'è un limite alla dimensione dell'espressione prodotta?
jmite,

@jmite: non ne ho idea. Non credo che proverei a implementarlo (altri metodi sembrano essere più fattibili in questo senso) ma lo userò come un metodo a penna e carta.
Raffaello

1
Ecco un'implementazione Prolog di questo algoritmo: github.com/wvxvw/intro-to-automata-theory/blob/master/automata/… ma il suo maybe_union/2predicato potrebbe usare più lavoro (specialmente wrt eliminando il prefisso comune) per rendere le espressioni regolari più ordinate. Un altro modo di vedere questo metodo è comprenderlo come traduzione dalla regex alla grammatica lineare destra, in cui le lingue con unificazione simile a Prolog o la corrispondenza di modelli ML-like rendono un trasduttore molto buono, quindi non è solo una penna e carta algoritmo :)
wvxvw,

Solo una domanda. La ε nella prima equazione è perché Qo è uno stato iniziale o perché è uno stato finale? Allo stesso modo se ho due stati finali si applica?
Georgio3,

@PAOK Controlla la definizione di sopra (la linea); è perché è uno stato finale. Qiq0
Raffaello

28

Metodo algebrico di Brzozowski

Questo è lo stesso metodo descritto nella risposta di Raffaello , ma dal punto di vista di un algoritmo sistematico e quindi, in effetti, dell'algoritmo. Risulta facile e naturale da implementare una volta che sai da dove cominciare. Inoltre, potrebbe essere più semplice a mano se il disegno di tutti gli automi è impraticabile per qualche motivo.

Quando scrivi un algoritmo devi ricordare che le equazioni devono essere sempre lineari in modo da avere una buona rappresentazione astratta delle equazioni, cosa che puoi dimenticare quando risolvi a mano.

L'idea dell'algoritmo

Non descriverò come funziona poiché è ben fatto nella risposta di Raffaello che suggerisco di leggere prima. Invece, mi concentro su in quale ordine dovresti risolvere le equazioni senza fare troppi calcoli o casi extra.

Partendo dalla regola di Arden soluzione ingegnosa s' l'equazione lingua possiamo considerare l'automa come un insieme di equazioni della forma:X=ABX=AXB

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

possiamo risolverlo inducendo su aggiornando di conseguenza le matrici e . Al passaggio , abbiamo:nAi,jBi,jn

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

e la regola di Arden ci dà:

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

e impostando e otteniamo:Bn=An,nBnAn,i=An,nAn,i

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

e possiamo quindi rimuovere tutte le esigenze di nel sistema impostando, per :Xni,j<n

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

Quando abbiamo risolto quando , otteniamo un'equazione come questa:Xnn=1

X1=B1

senza . Così abbiamo ottenuto la nostra espressione regolare.A1,i

L'algoritmo

Grazie a questo, possiamo costruire l'algoritmo. Per avere la stessa convenzione rispetto all'induzione precedente, diremo che lo stato iniziale è e che il numero di stato è . Innanzitutto, l'inizializzazione per riempire :q1mB

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

e :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] := ∅

e poi la risoluzione:

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]

l'espressione finale è quindi:

e := B[1]

Implementazione

Anche se può sembrare un sistema di equazioni che sembra troppo simbolico per un algoritmo, questo è adatto per un'implementazione. Ecco un'implementazione di questo algoritmo in Ocaml (link non funzionante) . Nota che, a parte la funzione brzozowski, tutto è da stampare o da usare per l'esempio di Raffaello. Si noti che esiste una funzione sorprendentemente efficiente di semplificazione delle espressioni regolari simple_re.


4
Link is dead ...
Columbo,


24

Metodo di chiusura transitiva

Questo metodo è facile da scrivere in una forma di algoritmo, ma genera espressioni regolari assurdamente grandi ed è poco pratico se lo fai a mano, soprattutto perché è troppo sistematico. È una buona e semplice soluzione per un algoritmo però.

L'idea chiave

Lascia che rappresenti l'espressione regolare per le stringhe che vanno da a usando gli stati . Sia il numero di stati dell'automa.Ri,jkqiqj{q1,,qk}n

Supponiamo di conoscere già l'espressione regolare da a senza lo stato intermedio (eccetto le estremità), per tutti . Quindi puoi indovinare come l'aggiunta di un altro stato influirà sulla nuova espressione regolare : cambia solo se hai transizioni dirette a e può essere espresso in questo modo:Ri,jqiqjqki,jRi,jqk

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

( è e è .)RRk1RRk

Esempio

Useremo lo stesso esempio della risposta di Raffaello . All'inizio, puoi usare solo le transizioni dirette.

Ecco il primo passo (nota che un self loop con un'etichetta avrebbe trasformato il primo in .aε(ε+a)

R0=[εabbεaabε]

Al secondo passo possiamo usare (che è stato rinominato in per noi, perché è già utilizzato per lo scopo sopra). Vedremo come funziona .q0q1R0R1

Da a : .q2q2R2,21=R2,20+R2,10R1,10R1,20=ε+bεa=ε+ba

Perché? È perché andare da a usando solo come stato intermedio può essere fatto rimanendo qui ( ) o andando a ( ), eseguendo il ciclo lì ( ) e ritornando ( ).q2q2q1εq1aεb

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

Puoi calcolare anche e , e ti darà l'espressione finale perché è sia iniziale che finale. Nota che qui è stata fatta molta semplificazione delle espressioni. Altrimenti la prima di sarebbe e la prima di sarebbe .R2R3R1,131aR0(+a)aR1((+a)+ε(ε)a)

Algoritmo

Inizializzazione:

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

Chiusura transitiva:

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)

Quindi l'espressione finale è (supponendo che sia lo stato iniziale):qs

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

Ma puoi immaginare che generi brutte espressioni regolari. Puoi davvero aspettarti cose come che rappresenta la stessa lingua di . Si noti che semplificare un'espressione regolare è utile nella pratica.a a()+(a+())(ε)(a+)aa

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.