Info
Wenn Sie Automaten angeben, tun Sie dies immer in Form eines Zustandsgraphen. Andere Formen der Darstellung (z.B. als Liste von Übergängen) werden nicht gewertet, da sie sehr viel aufwändiger zu korrigieren sind. Vergessen Sie nicht, im Zustandsgraph Start- und Endzustände zu markieren.
FSK3-1 Konstruktion von NFAs (2 Punkte)
Verwenden Sie in dieser Aufgabe nur NFAs ohne ε-Übergänge
a) Geben Sie einen NFA an, der die folgende Sprache über dem Alphabet akzeptiert:
Wörter dürfen nicht auf pfad stehen, nur Buchstaben
graph LR
id-1(( )) --> id0((z0))
id0((z0)) --a,b--> id0((z0))
id0((z0)) --a,b--> id1((z1))
id1((z1)) --bab,aa--> id2(((z2)))
id2(((z2))) --a,b--> id2(((z2)))
b) Viele Programmiersprachen erlauben nur Variablennamen, die Regeln wie diese erfüllen:
Aufgabenstellung
- Ein Variablenname kann Unterstriche, kleine und große Buchstaben (a–z, A–Z) und Ziffern enthalten.
- Ein Variablenname muss mindestens ein Zeichen enthalten.
- Ein Variablenname darf nicht mit einer Ziffer anfangen.
- „_“ ist kein Variablenname.
Geben Sie einen NFA an, der genau die Variablennamen erkennt, die diesen Regeln folgen.
Für die Übersicht führe ich ein paar Variablen hinzu:
graph LR
id-1(( )) --> id0(((z0)))
id0((z0)) --c--> id1(((z1)))
id1(((z1))) --c,z,u--> id1(((z1)))
id0((z0)) --u--> id2((z2))
id2((z2)) --c,z,u--> id3(((z3)))
id3(((z3))) --c,z,u--> id3(((z3)))
- z0 kein Endzustand weil mindestens ein Wort drin sein muss
- Beispielname für z0: ""
- z1 Endzustand Variablenname mit mindestens einem Buchstaben
- Beispielname für z1: “a” und danach rekursiv eventuell : “asdf123”
- z2 kein Endzustand weil nur Unterstrich
- Beispielname für z2: ”_” → nicht erlaubt
- z3 ist ein Endzustand, da unterstrich am Anfang aber danach Zeichen folgt
- Beispielname für z3: “_a” danach rekursiv “_asdf123”
c) Sei eine natürliche Zahl, und
Note
Das heißt, die Sprache enthält genau die Wörter , für die gilt: Es gibt eine Zahl sodass das Wort das Symbol genau -mal enthält.
Z.B. ist , da dieses Wort genau 1-mal das Symbol 1 enthält. Ebenso ist , da dieses Wort genau 2-mal das Symbol 2 enthält. Hingegen ist .
Geben Sie für jedes einen NFA an, der erkennt. Beschreiben Sie ausnahmsweise nicht durch einen Zustandsgraph, sondern geben Sie die Zustandsmenge, Start- und Endzustände und Übergänge (in Abhängigkeit von ) explizit an. Geben Sie außerdem den Zustandsgraph von an.
Gedankengang:
- weil alle jede Ziffer kommt weniger als 2 mal vor
- weil die 2 drei-mal vorkommt
Summary
Definition des NFA
Für die Definition eines Nichtdeterministischen Endlichen Automaten (NFA), der die Sprache erkennt, gehen wir wie folgt vor:
Zustandsmenge:
- Die Zustandsmenge von besteht aus allen möglichen Zählerkonfigurationen für jedes Symbol in , die angeben, wie oft jedes Symbol bis zu einem bestimmten Punkt im Wort erschienen ist. Zusätzlich gibt es einen besonderen Startzustand und Fehlerzustände für unerlaubte Symbolzählungen.
- Jeder Zustand in kann durch ein Tupel repräsentiert werden, wobei die Anzahl des Symbols in ist, das bis jetzt gelesen wurde. Jedes kann Werte von 0 bis annehmen, wobei Werte größer als zu einem Fehlerzustand führen.
Startzustand:
- Der Startzustand ist , was bedeutet, dass noch kein Symbol gelesen wurde.
Akzeptierende Zustände:
- Ein Zustand ist ein akzeptierender Zustand, wenn genau eine der Zählungen ist und alle anderen (für ) kleiner als sind. Das heißt, genau ein Symbol kommt genau -mal vor, und kein anderes Symbol kommt öfter vor, als es seine Ziffer erlaubt.
Übergänge:
- Von jedem Zustand gibt es einen Übergang zum Zustand beim Lesen des Symbols . Wenn , führt der Übergang in einen Fehlerzustand.
Fehlerzustände:
- Fehlerzustände werden erreicht, wenn eine Zählung größer als wird. Alle Übergänge von Fehlerzuständen führen wieder in Fehlerzustände.
Zustandsgraph für
Für den speziellen Fall von erstellen wir einen Zustandsgraphen:
- Zustände: Jeder Zustand ist ein Tupel , wobei jedes von 0 bis 3 reichen kann. Zustände, in denen sind nicht Teil unseres Automaten.
- Akzeptierende Zustände: Zustände wie , und .
- Startzustand:
- Übergänge: Basierend auf der obigen Beschreibung.
Lassen Sie uns eine detaillierte grafische Darstellung des Nichtdeterministischen Endlichen Automaten erstellen, der die Sprache erkennt. Diese Visualisierung wird die Zustandsmenge, akzeptierende Zustände, Startzustand und Übergänge für enthalten. Beachten Sie, dass ich zur Vereinfachung nur relevante Teile des Automaten zeige, da die vollständige Darstellung sehr umfangreich wäre.
Hier ist die grafische Darstellung von :
Zustandsgraph von
Zustände:
- Jeder Zustand ist ein Tupel , wobei die Anzahl der Male darstellt, dass das Symbol gelesen wurde. Nur diejenigen Zustände, bei denen keine die Bedingung erfüllt, sind gültig.
- Fehlerzustände sind solche, bei denen mindestens ein .
Startzustand:
- Der Startzustand ist .
Akzeptierende Zustände:
- : Symbol 1 kommt genau einmal vor.
- : Symbol 2 kommt genau zweimal vor.
- : Symbol 3 kommt genau dreimal vor.
Übergänge:
- Von jedem Zustand gibt es Übergänge zu , , und beim Lesen der entsprechenden Symbole 0, 1, 2, bzw. 3. Übergänge, die zu ungültigen Zuständen führen, werden nicht gezeigt.
FSK3-2 Entfernen von -Übergängen und Potenzmengenkonstruktion (2 Punkte)
a) Sei der folgende NFA über dem Alphabet :
graph LR
start(( )) --> z0((Z0))
z0((Z0)) -->|ε| z1((Z1))
z0((Z0)) -->|a| z2((Z2))
z1((Z1)) -->|b| z2((Z2))
z2((Z2)) -->|c| z1((Z1))
z2((Z2)) -->|ε| z3(((Z3)))
z3(((Z3))) -->|ε| z0((Z0))
Aufgabenstellung
Geben Sie einen NFA ohne -Übergänge mit an. Verwenden Sie den Algorithmus zum Entfernen von -Übergängen aus der Vorlesung. Geben Sie die Zwischenschritte Ihrer Berechnung an. Das erlaubt uns, Ihnen für Folgefehler Teiilpunkte zu geben.
Alle Knoten die von ausgehend mit dem leeren Wort erreicht werden können sind Startzustände und müssen behandelt werden. Dies ist nur bei der Fall.
Muss mit einem Zeichen aus beginnen und dann nur noch Epsilon Ketten. Kann von jedem Knoten Anfangen nicht nur von Startzuständen.
- Übergänge implizieren nicht das in der Sprache ist
Kann vereinfacht werden zu:
Da der Pfad mit mit beginnt, darf zu vereinfacht werden
Nun das gleiche für
Kann vereinfacht werden zu:
Leeren Knoten zu Z0 un Z1 sind Startknoten
graph LR
start(( )) --> z0((Z0))
z0((Z0)) --a-->z2((Z2))
z0((Z0)) --a-->z3(((Z3)))
z0((Z0)) --a-->z0((Z0))
z0((Z0)) --a-->z1((Z0))
start2(( )) --> z1((Z1))
z1((Z1)) --b-->z2((Z2))
z1((Z1)) --b-->z3(((Z3)))
z1((Z1)) --b-->z0((Z0))
z1((Z1)) --b-->z1((Z1))
z2((Z2)) --c-->z1((Z1))
b) Der folgende NFA über einem Alphabet kann verwendet werden, um in einem Text nach den Zeichenfolgen und zu suchen.
graph LR
start(( )) --> z0((Z0))
z0((Z0)) -->|Σ| z0((Z0))
z0((Z0)) -->|a| z1((Z1))
z1((Z1)) -->|e| z2(((Z2)))
z0((Z0)) -->|u| z3((Z3))
z3((Z3)) -->|e| z4(((Z4)))
Aufgabenstellung
Die Suche wird wesentlich beschleunigt, wenn wir in einen DFA umwandeln. Verwenden Sie deshalb die Potenzmengenkonstruktion, um einen DFA mit zu konstruieren. Geben Sie außer dem Zustandsgraph von auch die Rechenschritte an, die Sie bei der Potenzmengenkonstruktion ausgeführt haben. Das erlaubt uns, Ihnen bei Folgefehlern noch Teilpunkte zu geben.
Start | Ziel | |
---|---|---|
graph TD
start(( )) --> q0{{"0"}}
q0 -->|a| q01{{"0, 1"}}
q0 -->|u| q03{{"0, 3"}}
q0 -->|"Σ \ {a, u}"| q0
q01 -->|a| q01
q01 -->|u| q03
q01 -->|e| q02{{"0, 2"}}
q01 -->|"Σ \ {a, u, e}"| q0
q03 -->|a| q01
q03 -->|u| q03
q03 -->|e| q04{{"0, 4"}}
q03 -->|"Σ \ {a, u, e}"| q0
q02 -->|a| q01
q02 -->|u| q03
q02 -->|"Σ \ {a, u}"| q0
q04 -->|a| q01
q04 -->|u| q03
q04 -->|"Σ \ {a, u}"| q0
Ich werde den Inhalt des Bildes, das eine Textseite zum Thema Tokenizer zeigt, in Markdown-Format umwandeln, wobei mathematische Ausdrücke in LaTeX (mit $
für Inline und $$
für Display-Modus) und Code in Code-Blöcke eingefügt werden.
FSK3-3 Tokenizer
Aufgabenstellung
Ein Einsatzgebiet für endliche Automaten sind Tokenizer. Diese werden verwendet, um den Quelltext einer Programmiersprache in syntaktische Einheiten (Tokens) zu zerlegen. Ein Token ist beispielsweise ein Schlüsselwort, ein Bezeichner oder ein Operator.
Zum Beispiel wird das Programm
zerlegt in
In dieser Aufgabe erstellen wir einen Tokenizer, indem wir die möglichen Tokens als reguläre Sprache auffassen.
a) Um alle Schritte sinnvoll per Hand rechnen zu können, arbeiten wir mit einem reduzierten Alphabet ( statt oder {}, weniger Buchstaben aus dem Alphabet, nur eine Ziffer, …):
Σ = \{a, x, o, [, ], -, '\,'\}Um das erste Token aus einem String zu identifizieren, wird A vom Anfang des Strings aus laufen gelassen. Wenn der Lauf nie in einen Endzustand kommt, meldet der Tokenizer einen Fehler. Ansonsten wird die letzte Position, in welcher der Automat in einem Endzustand war, als Token-Ende genommen.
Zum Beispiel ist bei Eingabe der Lauf
Da akzeptierend ist (aber nicht), ist das erkannte Token .
Notieren Sie bei folgenden Strings die Zustände, die A bei Verarbeitung dieser Strings annehmen wird (die Läufe) und geben Sie je die Ausgabe des Tokenizers an. Bezüglich der Ausgabe reicht es, sofern der Tokenizer keinen Fehler zurückgibt, nur das erste erkannte Token anzugeben.
aa==aa:
- Pfad:
- Erkanntes Token:
aa
a[0]:
- Pfad:
- Erkanntes Token:
a
a[[[[]:
- Pfad:
- Erkanntes Token:
a
”a[0]“ax”:
- Pfad:
- Erkanntes Token:
"a[0]"ax"
”a=[0]“ax”:
- Pfad:
- Kein erkannter Token: Fehler
”a[0]“a=x”:
- Pfad:
- Erkannter Token:
"a[0]"
b) Bestimmen Sie asymptotisch (in O-Notation), wie viele Schritte der Tokenizer Automat braucht, um ein Token aus einem String der Länge zu extrahieren
O(n), da die Verarbeitung von einem Zeichen ( O(1) ) ist und zweimal über den String gelaufen werden muss: Einmal zur Verarbeitung des Strings und einmal bei der Suche nach dem letzten Zustand, der ein Endzustand ist. (Man kann sich den jeweils letzten Endzustand natürlich auch merken, dann muss man nur einmal über den String laufen. Das ändert aber an der asymptotischen Laufzeit nichts.)
c) Um mehrere Tokens zu extrahieren, wird das gefundene Token von dem String entfernt und wieder von vorne ein Token gesucht. Wenn der verbleibende String leer ist, ist der Tokenizer fertig.
Beispiel: Bei der oben genannten Eingabe aa[ mit dem ersten Token , ist der Reststring nach dem Entfernen aa[, das zweite Token dann also aa.
Zerlegen Sie mit diesem Algorithmus den String a=“ax0”aa[0]=a in alle Tokens.
String: a:="ax0"aa[0]=a
Automat für a:="ax0"aa[0]=a
- Pfad:
- Token:
a
- Reststring:
"ax0"aa[0]=a
Automat für :="ax0"aa[0]=a
- Pfad:
- Token:
:=
- Reststring:
"ax0"aa[0]=a
Automat für "ax0"
- Pfad:
- Token:
"ax0"
- Reststring:
aa[0]=a
Automat für aa
- Pfad:
- Token:
aa
- Reststring:
[0]=a
Automat für [
- Pfad:
- Token:
[
- Reststring:
0]=a
Weitere Token
-
0
- Pfad:
- Token:
0
- Reststring:
]=a
-
]
- Pfad:
- Token:
]
- Reststring:
=a
-
=
- Pfad:
- Token:
=
- Reststring:
a
Abschluss
a
- Pfad:
- Token:
a
- Reststring:
\epsilon
Liste der Tokens:
"
,=
,"ax0"
,aa
,[
,0
,]
,=
,a
d) Tatsächlich müssen wir den String nicht verändern, sondern, wenn ein Token gefunden wurde, nur den Automaten an der nächsten Position im String starten. Wir „kürzen“ den String also in . Wie viele Schritte brauchen wir dann asymptotisch, um alle Tokens aus einem String der Länge zu finden?
Hinweis
Es ist nicht . Man könnte das Verfahren aber optimieren, um eine Laufzeit von zu erreichen
Um alle Tokens in einem String der Länge zu finden, indem wir einen Automaten verwenden, der an jeder Position im String startet, betrachten wir die folgenden Schritte:
- Initialisierung: Der Automat startet an der Position 0 im String.
- Durchlauf: Für jede Position im String (von 0 bis ) startet der Automaten neu und versucht, ein Token zu erkennen.
- Token-Erkennung: Sobald ein Token erkannt wird, wird der Automat an der nächsten Position im String gestartet. Die Länge des erkannten Tokens kann variieren.
- Ende: Der Prozess endet, wenn der Automat an einer Position kein Token mehr erkennen kann oder das Ende des Strings erreicht ist.
Analyse der Laufzeit:
- In jedem Schritt startet der Automat neu und durchläuft den Teilstring, der beginnt an der aktuellen Position bis zum Ende des Strings. Die maximale Anzahl von Schritten, die der Automat in einem Durchlauf machen kann, ist proportional zur Länge des Strings, also für jeden Startpunkt.
- Da der Automat an jeder von Positionen im String startet, beträgt die Gesamtzahl der Schritte .
Optimierung (Zusatz)
Optimierung: Um die Laufzeit auf zu optimieren, können wir folgende Techniken anwenden:
- Überlappende Durchläufe vermeiden: Anstatt den Automaten jedes Mal von vorne zu starten, können wir den verbleibenden Teil des Strings nach dem Erkennen eines Tokens weiterverarbeiten. Dies bedeutet, dass der Automat direkt an der Stelle fortsetzt, an der das letzte Token endete.
- Vorverarbeitung des Strings: Durch die Verwendung von Datenstrukturen wie Suffixbäumen oder Suffixarrays können wir die Suche nach Tokens effizienter gestalten, da diese Strukturen es ermöglichen, schnell zu überprüfen, ob ein bestimmter Teilstring ein gültiges Token ist.
Durch die Anwendung dieser Optimierungen können wir die Anzahl der benötigten Schritte auf reduzieren, was eine effiziente Lösung für das Problem darstellt.
FSK3-4 Umgedrehte Sprache (0 Punkte)
Aufgabenstellung
Sei die Funktion, die aus einem NFA einen NFA erzeugt, wobei .
a) Automaten berechnen
Gegeben ist der folgende Automat ( A ):
graph LR
start(( ))--> q0((q0))
q0((q0)) --a,b--> q0((q0))
q0((q0)) --a-->q1((q1))
q1((q1)) --a,b-->q2((q2))
q2((q2)) --a,b-->q3(((q3)))
Automat B:
graph LR
start(( ))--> q3((q3))
q0((q0)) --a,b--> q0((q0))
q3((q3)) --a,b-->q2((q2))
q2((q2)) --a,b-->q1((q1))
q1((q1)) --a-->q0(((q0)))
b) Geben Sie einen DFA C mit L(B) = L(C) an. (Sie dürfen die Potenzmengenkonstruktion nutzen, müssen aber nicht.)
Fast wie in b), aber benötigt noch einen ausgehenden Übergang mit . Darum Müllzustand hinzufügen.
graph LR
start(( ))--> q3((q3))
q0((q0)) --a,b--> q0((q0))
q3((q3)) --a,b-->q2((q2))
q2((q2)) --a,b-->q1((q1))
q1((q1)) --a-->q0(((q0)))
q1((q1)) --b-->qm(((qm)))
qm(((qm))) --a,b--> qm(((qm)))
c) Zeigen Sie: Für jeden NFA ist . Dabei steht wie in der Vorlesung für das rückwärts gelesene Wort .
Wir zeigen für alle und alle :
durch Induktion über die Länge . Zur Erinnerung: Für und gilt
Basis:
: Die Definition von liefert sofort
Schritt:
:
Schließlich zeigen wir die Behauptung:
Erklärung
Der gegebene Beweis zeigt die Äquivalenz der Sprachen eines nichtdeterministischen endlichen Automaten (NFA) und seines transformierten Automaten . Die Transformation besteht darin, die Übergangsfunktion so zu ändern, dass sie Wörter rückwärts akzeptiert. Hier wird gezeigt, dass das Rückwärtslesen eines Wortes in dem Vorwärtslesen in entspricht.
Der Beweis nutzt eine Induktion über die Länge eines Wortes und verwendet eine umgekehrte Übergangsfunktion , die im rückwärts transformierten Automaten verwendet wird. Der Hauptansatz besteht darin, die Äquivalenz der Übergänge zwischen den Zuständen in und für alle Zustände und alle Wörter zu zeigen.
Basisfall
Der Basisfall betrachtet das leere Wort . Die Übergangsfunktion eines jeden NFA definiert, dass . Da analog definiert ist, gilt auch . Damit ist der Basisfall bestätigt: Für das leere Wort bleibt man im gleichen Zustand, sowohl in als auch in .
Induktionsschritt
Im Induktionsschritt wird die Behauptung von Wörtern der Länge auf Wörter der Länge erweitert. Wir betrachten ein Wort . Der Beweis geht davon aus, dass der Übergang von einem Zustand zu einem Zustand in mittels des Wortes einem Übergang in entspricht, wenn das Wort betrachtet wird. Durch die Induktionshypothese und die Definition der Übergangsfunktion wird gezeigt, dass diese beiden Übergänge äquivalent sind.
Zusammenfassung des Beweises
Der Beweis schließt mit dem Nachweis, dass ein Wort von akzeptiert wird, wenn und nur wenn das rückwärts gelesene Wort von akzeptiert wird. Dies geschieht durch den Zusammenhang der anfänglichen und endgültigen Zustände und deren Erreichbarkeit über die Übergangsfunktionen und .
Durch diesen Beweis wird etabliert, dass gilt. Dies bedeutet, dass die Sprache von genau aus den umgekehrten Wörtern der Sprache von besteht, was zeigt, dass das Rückwärtslesen der Wörter in äquivalent zum Vorwärtslesen in $T(A) ist.