Wie konvertiere ich endliche Automaten in reguläre Ausdrücke?
On Februar 16, 2021 by adminDas Konvertieren regulärer Ausdrücke in (minimale) NFA, die dieselbe Sprache akzeptieren, ist mit Standardalgorithmen, z. Thompsons Algorithmus . Die andere Richtung scheint jedoch langwieriger zu sein, und manchmal sind die resultierenden Ausdrücke chaotisch.
Was sind Algorithmen? Gibt es Vorteile hinsichtlich der Zeitkomplexität oder der Ergebnisgröße?
Dies soll eine Referenzfrage sein. Bitte fügen Sie eine allgemeine Beschreibung Ihrer Methode sowie eine Nicht triviales Beispiel.
Kommentare
- Beachten Sie eine ähnliche Frage bei cstheory.SE , das wahrscheinlich nicht für unser Publikum geeignet ist.
- Alle Antworten verwenden formale Techniken, um RE aus DFA zu schreiben. Ich glaube, meine Analysetechnik ist vergleichsweise einfach und objektiv, was ich in meiner Antwort demonstriere : Was ist die Sprache dieser deterministischen endlichen Automaten? Ich denke, es wäre irgendwann hilfreich. Ja, natürlich verwende ich manchmal selbst eine formale Methode (Arden-Theorem) RE zu schreiben ist Die Frage ist komplex wie in diesem Beispiel angegeben: So schreiben Sie einen regulären Ausdruck für einen DFA
Antwort
Es gibt verschiedene Methoden, um die Konvertierung von endlichen Automaten in reguläre Ausdrücke durchzuführen. Hier werde ich die beschreiben, die normalerweise in der Schule unterrichtet wird und die sehr visuell ist. Ich glaube, es wird in der Praxis am häufigsten verwendet. Das Schreiben des Algorithmus ist jedoch keine so gute Idee.
Zustandsentfernungsmethode
Bei diesem Algorithmus geht es um die Handhabung des Graphen des Automaten und ist daher für Algorithmen nicht sehr geeignet, da dies erforderlich ist Graphprimitive wie … Zustandsentfernung. Ich werde es mit übergeordneten Grundelementen beschreiben.
Die Schlüsselidee
Die Idee besteht darin, reguläre Ausdrücke an Kanten zu berücksichtigen und dann Zwischenzustände zu entfernen, während die Kantenbeschriftungen konsistent bleiben.
Das Hauptmuster ist im Folgenden zu den Abbildungen zu sehen. Die erste hat Beschriftungen zwischen $ p, q, r $, die reguläre Ausdrücke $ e, f, g, h, i $ sind, und wir möchten $ q $ entfernen.
Nach dem Entfernen setzen wir $ e, f, g, h, i $ zusammen (wobei die anderen Kanten zwischen $ p $ und $ r $ erhalten bleiben, dies wird jedoch nicht angezeigt dazu):
Beispiel
Verwenden Sie dasselbe Beispiel wie in Raphaels Antwort :
Wir entfernen nacheinander $ q_2 $:
und dann $ q_3 $:
Dann müssen wir noch einen Stern auf den Ausdruck von $ q_1 $ bis $ q_1 $ anwenden. In diesem Fall ist der Endzustand auch initial, also müssen wir wirklich nur einen Stern hinzufügen:
$$ (ab + (b + aa) (ba) ^ * (a + bb)) ^ * $$
Der Algorithmus
L[i,j]
ist der reguläre Ausdruck der Sprache von $ q_i $ bis $ q_j $. Zuerst entfernen wir alle Mul ti-Kanten:
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
Nun die Statusentfernung. Angenommen, wir möchten den Status $ q_k $ entfernen:
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]
Beachten Sie, dass Sie sowohl mit einem Bleistift als auch mit einem Algorithmus Ausdrücke wie star(ε)=ε
, e.ε=e
, ∅+e=e
, ∅.e=∅
(By Hand Sie schreiben einfach nicht die Kante, wenn es nicht $ ∅ $ oder sogar $ ε $ für eine Selbstschleife ist, und Sie ignorieren, wenn es keinen Übergang zwischen $ q_i $ und $ q_k $ oder $ q_j $ und $ gibt q_k $)
Wie wird nun remove(k)
verwendet? Sie sollten End- oder Anfangszustände nicht leichtfertig entfernen, da Sie sonst Teile der Sprache übersehen.
for i = 1 to n: if not(final(i)) and not(initial(i)): remove(i)
Wenn Sie nur einen Endzustand $ q_f $ und einen haben Anfangszustand $ q_s $, dann lautet der endgültige Ausdruck:
e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])
Wenn Sie mehrere Endzustände (oder sogar Anfangszustände) haben, gibt es keine einfache Möglichkeit zum Zusammenführen diese, abgesehen von der Anwendung der transitiven Verschlussmethode. Normalerweise ist dies kein Problem von Hand, aber dies ist beim Schreiben des Algorithmus umständlich. Eine viel einfachere Problemumgehung besteht darin, alle Paare $ (s, f) $ aufzulisten und den Algorithmus im (bereits vom Status entfernten) Diagramm auszuführen, um alle Ausdrücke $ e_ {s, f} $ zu erhalten, vorausgesetzt, $ s $ ist der einzige Anfangszustand und $ f $ ist der einzige Endzustand, der dann die Vereinigung aller $ e_ {s, f} $ durchführt.
Dies und die Tatsache, dass dies Sprachen dynamischer modifiziert als die erste Methode, machen es möglich fehleranfälliger beim Programmieren. Ich schlage vor, eine andere Methode zu verwenden.
Nachteile
Dieser Algorithmus enthält viele Fälle, z. B. die Auswahl des zu entfernenden Knotens und die Anzahl der Endzustände am Ende , die Tatsache, dass ein Endzustand auch initial sein kann usw.
Beachten Sie, dass dies jetzt, da der Algorithmus geschrieben ist, der transitiven Schließmethode sehr ähnlich ist.Nur der Kontext der Verwendung ist unterschiedlich. Ich empfehle nicht, den Algorithmus zu implementieren, aber es ist eine gute Idee, die Methode zu verwenden, um dies von Hand zu tun.
Kommentare
- Im Beispiel 2. Bild nach Entfernen des Knotens “ 2 „, es fehlt eine Kante – Schleifenkante (ab) in Knoten A.
- @Kabamaru: Behoben. Aber jetzt denke ich, dass das $ \ varepsilon $ im 3. Bild auch
ab
sein sollte, und vielleicht auch im endgültigen regulären Ausdruck. - Sie können den Algorithmus erstellen Arbeiten Sie für eine beliebige Anzahl von Anfangs- und Endzuständen, indem Sie einen neuen Anfangszustand $ q ^ + $ und einen neuen Endzustand $ q ^ – $ hinzufügen und diese über $ \ varepsilon $ -edges mit den ursprünglichen Anfangs- und Endzuständen verbinden. Entfernen Sie nun alle die ursprünglichen Zustände. Der Ausdruck wird dann an der einzelnen verbleibenden Kante von $ q ^ + $ bis $ q _- $ gefunden. Die Konstruktion gibt keine Schleifen bei $ q ^ + $ oder $ q _- $, da diese Zustände keine eingehenden resp. ausgehende Kanten. Wenn Sie streng sind, haben sie Beschriftungen, die die leere Menge darstellen.
- Es gibt immer noch ein Problem mit dem zweiten Beispiel: Vor der Vereinfachung akzeptieren die Automaten “ ba „, (1, 3, 1), aber nach Vereinfachung nicht ‚ t.
Antwort
Methode
Die schönste Methode, die ich je gesehen habe, ist eine, die den Automaten als Gleichungssystem von (regulären) Sprachen ausdrückt, die dies können gelöst werden. Es ist besonders schön, da es präzisere Ausdrücke als andere Methoden zu liefern scheint.
Sei $ A = (Q, \ Sigma, \ delta, q_0, F) $ eine NFA ohne $ \ varepsilon $ – Übergänge. Erstellen Sie für jeden Zustand $ q_i $ die Gleichung
$ \ qquad \ displaystyle Q_i = \ bigcup \ limit_ {q_i \ overset {a} {\ to} q_j} aQ_j \ cup \ begin {case} \ {\ varepsilon \} &, \ q_i \ in F \\ \ Emptyset &, \ text {else} \ end {case} $
wobei $ F $ die Menge der Endzustände ist und $ q_i \ overset {a} {\ to} q_j $ bedeutet, dass es einen Übergang von $ q_i $ zu $ q_j $ gibt, der mit $ a $ gekennzeichnet ist . Wenn Sie $ \ cup $ als $ + $ oder $ \ mid $ lesen (abhängig von Ihrer Definition für reguläre Ausdrücke), sehen Sie, dass dies eine Gleichung für reguläre Ausdrücke ist.
Zum Lösen des Systems benötigen Sie Assoziativität und Verteilbarkeit von $ \ cup $ und $ \ cdot $ (String-Verkettung), Kommutativität von $ \ cup $ und Ardens Lemma ¹:
Lassen Sie $ L, U, V \ subseteq \ Sigma ^ * $ reguläre Sprachen mit $ \ varepsilon \ notin U $. Dann
$ \ qquad \ displaystyle L = UL \ cup V \ quad \ Longleftrightarrow \ quad L = U ^ * V $
Die Lösung besteht aus einer Reihe regulärer Ausdrücke $ Q_i $, eines für jeden Zustand $ q_i $. $ Q_i $ beschreibt genau die Wörter, die von $ A $ akzeptiert werden können, wenn in $ q_i $ begonnen wird; daher ist $ Q_0 $ (wenn $ q_0 $ der Anfangszustand ist) das gewünschter Ausdruck.
Beispiel
Der Klarheit halber bezeichnen wir Singleton-Mengen mit ihrem Element, dh $ a = \ {a \} $ Beispiel ist Georg Zetzsche.
Betrachten Sie t seine NFA:
[ Quelle ]
Das entsprechende Gleichungssystem lautet:
$ \ qquad \ begin {align} Q_0 & = aQ_1 \ cup bQ_2 \ cup \ varepsilon \\ Q_1 & = bQ_0 \ cup aQ_2 \\ Q_2 & = aQ_0 \ cup bQ_1 \ end {align} $
Stecken Sie nun die dritte Gleichung in die zweite:
$ \ qquad \ begin {align} Q_1 & = bQ_0 \ cup a (aQ_0 \ cup bQ_1) \\ & = abQ_1 \ cup (b \ cup aa) Q_0 \\ & = (ab) ^ * ( b \ cup aa) Q_0 \ end {align} $
Für den letzten Schritt wenden wir Ardens Lemma mit $ L = Q_1 $, $ U = ab $ und $ V = (b \ cup) an aa) \ cdot Q_0 $. Beachten Sie, dass alle drei Sprachen regulär und $ \ varepsilon \ notin U = \ {ab \} $ sind, sodass wir das Lemma anwenden können. Jetzt fügen wir dieses Ergebnis in die erste Gleichung ein:
$ \ qquad \ begin {align} Q_0 & = a (ab) ^ * (b \ cup aa ) Q_0 \ cup baQ_0 \ cup bb (ab) ^ * (b \ cup aa) Q_0 \ cup \ varepsilon \\ & = ((a \ cup bb) (ab) ^ * (b \ cup aa) \ cup ba) Q_0 \ cup \ varepsilon \\ & = ((a \ cup bb) (ab) ^ * (b \ cup aa) \ cup ba) ^ * \ qquad \ text {(von Ardens Lemma)} \ end {align} $
Somit haben wir einen regulären Ausdruck für die vom obigen Automaten akzeptierte Sprache gefunden, nämlich
$ \ qquad \ displaystyle ((a + bb) (ab) ^ * (b + aa) + ba) ^ *. $
Beachten Sie, dass es ziemlich prägnant ist (vergleiche mit dem Ergebnis anderer Methoden), aber nicht eindeutig bestimmt; das Lösen des Gleichungssystems mit einer anderen Folge von Manipulationen führt zu anderen – äquivalenten! – Ausdrücken.
- Für einen Beweis von Arden “ s Lemma, siehe hier .
Kommentare
- Was ist die zeitliche Komplexität dieses Algorithmus? Gibt es eine Grenze für die Größe des produzierten Ausdrucks?
- @jmite: Ich habe keine Ahnung. Ich ‚ glaube nicht, dass ich ‚ versuchen würde, dies zu implementieren (andere Methoden scheinen in dieser Hinsicht praktikabler zu sein), sondern verwende es als eine Stift-Papier-Methode.
- Hier ‚ ist eine Prolog-Implementierung dieses Algorithmus: github.com / wvxvw / Einführung in die Automatentheorie / blob / master / automata / … , aber das Prädikat
maybe_union/2
könnte verwendet werden mehr Arbeit (insbesondere das Entfernen des allgemeinen Präfixes), um ordentlichere reguläre Ausdrücke zu erstellen. Eine andere Möglichkeit, diese Methode zu sehen, besteht darin, sie als Übersetzung von Regex in die rechtslineare Grammatik zu verstehen, wobei Sprachen mit Prolog-ähnlicher Vereinheitlichung oder ML-ähnlicher Musterübereinstimmung für sehr gute Wandler sorgen, also ‚ ist nicht nur ein Stift-Papier-Algorithmus 🙂 - Nur eine Frage. Das ε in der ersten Gleichung ist, weil Qo ein Startzustand ist oder weil es ‚ ein Endzustand ist? Der gleiche Weg, wenn ich zwei Endzustände habe, gilt?
- @PAOK Überprüfen Sie die Definition von $ Q_i $ oben (die Zeile); es ‚ ist, weil $ q_0 $ ein Endzustand ist.
Antwort
Brzozowski algebraische Methode
Dies ist die gleiche Methode wie die in Raphaels Antwort beschriebene, jedoch von einem Punkt aus Ansicht eines systematischen Algorithmus und dann tatsächlich des Algorithmus. Es stellt sich als einfach und natürlich heraus, ihn zu implementieren, sobald Sie wissen, wo Sie anfangen sollen. Außerdem kann es von Hand einfacher sein, wenn das Zeichnen aller Automaten aus irgendeinem Grund unpraktisch ist.
Wenn Sie einen Algorithmus schreiben, müssen Sie daran denken, dass die Gleichungen immer linear sein müssen, damit Sie eine gute abstrakte Darstellung der Gleichungen haben, was Sie vergessen können, wenn Sie von Hand lösen.
Die Idee des Algorithmus
Ich werde nicht beschreiben, wie es funktioniert, da es in der Antwort von Raphael , die ich habe, gut gemacht ist Schlagen Sie vor, vorher zu lesen. Stattdessen konzentriere ich mich darauf, in welcher Reihenfolge Sie die Gleichungen lösen sollten, ohne zu viele zusätzliche Co zu machen mputationen oder zusätzliche Fälle.
Ausgehend von Ardens Regel „s geniale Lösung $ X = A ^ * B $ für die Sprachgleichung $ X = AX∪B $ Wir können den Automaten als einen Satz von Gleichungen der Form betrachten:
$$ X_i = B_i + A_ {i, 1} X_1 +… + A_ {i, n} X_n $$
Wir können dies durch Induktion auf $ n $ lösen, indem wir die Arrays $ A_ {i, j} $ und $ B_ {i, j} $ entsprechend aktualisieren. Im Schritt $ n $ haben wir:
$$ X_n = B_n + A_ {n, 1} X_1 +… + A_ {n, n} X_n $$
und Ardens Regel gibt uns:
$$ X_n = A_ {n, n} ^ * (B_n + A_ {n, 1} X_1 +… + A_ {n, n-1} X_ {n -1}) $$
und durch Setzen von $ B „_n = A_ {n, n} ^ * B_n $ und $ A“ _ {n, i} = A_ {n, n} ^ * A_ {n, i} $ erhalten wir:
$$ X_n = B „_n + A“ _ {n, 1} X_1 +… + A „_ {n, n-1} X_ {n -1} $$
und wir können dann alle Anforderungen von $ X_n $ im System entfernen, indem wir für $ i j < n $:
$$ B „_i = B_i + A_ {i, n} B“ _n $$ $$ A „_ {i, j} = A_ {i, j} + A_ {i, n} A. „_ {n, j} $$
Wenn wir $ X_n $ gelöst haben, wenn $ n = 1 $, erhalten wir eine Gleichung wie folgt:
$$ X_1 = B“ _1 $$
ohne $ A „_ {1, i} $. So haben wir unseren regulären Ausdruck erhalten.
Der Algorithmus
Dank dessen können wir den Algorithmus erstellen. Um die gleiche Konvention wie in der obigen Induktion zu haben, werden wir sagen, dass der Anfangszustand $ q_1 $ ist und dass die Anzahl der Zustände $ m $ ist. Zuerst die Initialisierung zum Füllen von $ B $:
for i = 1 to m: if final(i): B[i] := ε else: B[i] := ∅
und $ 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] := ∅
und dann die Lösung:
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]
Der endgültige Ausdruck lautet dann:
e := B[1]
Implementierung
Auch wenn es sich um ein Gleichungssystem handelt, das für einen Algorithmus zu symbolisch erscheint, ist dieses System für eine Implementierung gut geeignet. Hier ist eine Implementierung dieses Algorithmus in Ocaml (defekter Link) . Beachten Sie, dass außer der Funktion brzozowski
alles gedruckt oder für Raphaels Beispiel verwendet werden muss. Beachten Sie, dass es eine überraschend effiziente Funktion zur Vereinfachung regulärer Ausdrücke gibt simple_re
.
Kommentare
- Link ist tot …
- Implementierung in Javascript: github.com/devongovett/regexgen/blob/master/src/regex.js
- Vielen Dank für diese großartige Erklärung. Wenn ich das richtig verstehe, Ihr Initialisierungspseudocode geht davon aus, dass es für gegebenes i und j höchstens einen a gibt, so dass (i, a, j) ein Übergang ist. Dies ist richtig, wenn wir zustimmen, diesen Übergang mit dem regulären Ausdruck zu kennzeichnen, der allen Buchstaben in entspricht Σ diese Bezeichnung Übergänge von i nach j im Buchstabenautomaten, aber dann ist Ihre Notation a in Σ etwas seltsam, da es sich nicht wirklich um einen Buchstaben handelt. Wenn wir Buchstabe für Buchstabe gehen, können wir mehrere Übergänge von i nach j haben und müssen Machen Sie die Vereinigung ihrer Labels im Loop-Body.
Antwort
Transitive Verschlussmethode
Diese Methode ist einfach in ein Formular zu schreiben eines Algorithmus, erzeugt aber absurd große reguläre Ausdrücke und ist unpraktisch, wenn Sie es von Hand tun, vor allem, weil dies zu systematisch ist. Es ist jedoch eine gute und einfache Lösung für einen Algorithmus.
Die Schlüsselidee
Lassen Sie $ R ^ k_ {i, j} $ den regulären Ausdruck für die Zeichenfolgen darstellen, die von $ ausgehen q_i $ bis $ q_j $ unter Verwendung der Zustände $ \ {q_1,…, q_k \} $. Sei $ n $ die Anzahl der Zustände des Automaten.
Angenommen, Sie kennen bereits den regulären Ausdruck $ R_ {i, j} $ von $ q_i $ bis $ q_j $ ohne den Zwischenzustand $ q_k $ (außer für Extremitäten), für alle $ i, j $. Dann können Sie erraten, wie sich das Hinzufügen eines weiteren Status auf den neuen regulären Ausdruck $ R „_ {i, j} $ auswirkt: Er ändert sich nur, wenn Sie direkte Übergänge zu $ q_k $ haben, und er kann folgendermaßen ausgedrückt werden:
$$ R „_ {i, j} = R_ {i, j} + R_ {i, k}. R_ {k, k} ^ *. R_ {k, j} $$
($ R $ ist $ R ^ {k-1} $ und $ R „$ ist $ R ^ k $.)
Beispiel
Wir verwenden dasselbe Beispiel wie in Raphaels Antwort . Zunächst können Sie nur die direkten Übergänge verwenden.
Hier ist der erste Schritt (beachten Sie, dass eine Selbstschleife mit der Bezeichnung $ a $ das erste $ ε $ in $ (ε + a) umgewandelt hätte. $.
$$ R ^ 0 = \ begin {bmatrix} ε & a & b \\ b & ε & a \\ a & b & ε \ end {bmatrix} $$
Im zweiten Schritt können wir $ q_0 $ verwenden (das für uns in $ q_1 $ umbenannt wird, da $ R ^ 0 $ bereits für verwendet wird den Zweck oben). Wir werden sehen, wie $ R ^ 1 $ funktioniert.
Von $ q_2 $ zu $ q_2 $: $ R ^ 1_ {2,2} = R ^ 0_ {2,2} + R ^ 0_ {2,1} {R ^ 0_ {1,1}} ^ * R ^ 0_ {1,2} = ε + b ε ^ * a = ε + ba $.
Warum ist das so? Es liegt daran, dass Sie von $ q_2 $ zu $ q_2 $ wechseln können, indem Sie nur $ q_1 $ als Zwischenzustand verwenden, indem Sie hier bleiben ($ ε $) oder zu $ q_1 $ ($ a $) gehen und sich dort wiederholen ($ ε ^ * $) und zurückkommen ($ b $).
$$ R ^ 1 = \ begin {bmatrix} ε & a & b \\ b & ε + ba & a + bb \\ a & b + aa & ε + ab \ end {bmatrix} $$
Sie können auch $ R ^ 2 $ und $ R ^ 3 $ und $ R ^ 3_ {1,1 so berechnen } $ gibt Ihnen den endgültigen Ausdruck, da $ 1 $ sowohl initial als auch final ist. Beachten Sie, dass hier viele Vereinfachungen der Ausdrücke vorgenommen wurden. Andernfalls wäre das erste $ a $ von $ R ^ 0 $ $ (∅ + a) $ und das erste $ a $ von $ R ^ 1 $ wäre $ ((∅ + a) + ε (ε) ^ * a ) $.
Algorithmus
Initialisierung:
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
Transitiver Abschluss:
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)
Dann lautet der endgültige Ausdruck (vorausgesetzt, $ q_s $ ist der Anfangszustand):
e := ∅ for i = 1 to n: if final(i): e := e + R[s,i,n]
Aber Sie können sich vorstellen es erzeugt hässliche reguläre Ausdrücke. Sie können tatsächlich Dinge wie $ (∅) ^ * + (a + (∅) ^ *) (ε) ^ * (a + ∅) $ erwarten, die dieselbe Sprache wie $ aa $ darstellen. Beachten Sie, dass die Vereinfachung eines regulären Ausdrucks in der Praxis nützlich ist.
Schreibe einen Kommentar