Wir haben drei Methoden kennengelernt, kontextfreie Sprachen zu parsen:
rekursiver Abstieg (mit Demoseite drawManualGrammar.html),
die LL-Parser (die die Mengen berechnen, wie
auf drawFirstComputation.html demonstriert),
und die LR-Parser (die die Teilbäume auf den Stack legen und nach Blüten suchen,
hier die Demoseite drawLR0ParserPrefixArithmetic
für arithmetische Ausdrücke).
Rekursiver Abstieg kann, wenn man nicht vorsichtig ist, in Endlosschleifen landen und
kann im Allgemeinen selbst bei einfachen Grammatiken exponentielle Laufzeit aufweisen.
LL-Parser und LR-Parser funktionieren schlicht nicht für allgemeine kontextfreie Grammatiken.
Standardbeispiel ist die Palindromsprache ohne Kennzeichnung der Mitte:
Weder LL-Parser noch LR-Parser können bei langen Wörtern wie
erkennen, wo die Mitte ist. Das muss man aber wissen,
denn sonst landet man in einer Sackgasse. Es gilt sogar: jeder Kellerautomat für
diese Sprache muss nichtdeterministisch sein (was wir an dieser Stelle weder formal definieren
noch beweisen).
Noch schlimmer steht es mit Grammatiken wie
Diese erzeugt die Sprache
Die Grammatik ist mehrdeutig, insbesondere kann jedes Wort der
Form auf zwei Weisen abgeleitet werden: via und via .
Man kann sogar zeigen, dass jede äquivalente Grammatik , die also
die gleiche Sprache erzeugt, mehrdeutig sein muss; man sagt, die Sprache
ist inhärent mehrdeutig.
Für nichtdeterministische oder gar mehrdeutige Grammatiken / Sprachen sind LL- und LR-Parser
unbrauchbar. Gibt es eine allgemeine Methode, die für alle Grammatiken funktioniert?
Ja, den sogenannten CYK-Algorithmus. Nur leider ist die nicht besonders schnell. Sie hat
kubische Laufzeit , was zwar
in der theoretischen Informatik als effizient durchgeht, in der Praxis leider
meist unbrauchbar ist.
Chomsky-Normalform
Eine kontextfreie Grammatik ist in Chomsky-Normalform, wenn jede Produktion eine
der folgenden Formen hat:
Eine solche Sprache kann offensichtlich nicht das Wort ableiten. Daher lassen wir
als Sonderregel die Produktion
zu, verbieten dann aber, dass das Startsymbol auf der rechten Seite einer Produktion vorkommen
kann.
Theorem 6.10.1
Zu jeder kontextfreien Grammatik gibt es eine äquivalente Grammatik in
Chomsky-Normalform.
Anstatt hier einen formalen Beweis anzugeben (den Sie sich, wenn Sie wollen, im Lehrbuch oder auf
Wikipedia anschauen können),
lasse ich Sie lieber anhand einer Übungsaufgabe die Konstruktion von selbst verstehen:
Übungsaufgabe 6.10.1
Finden Sie zu der folgenden kontextfreien Grammatik
eine äquivalente in Chomsky-Normalform.
Fragen, die Sie sich dabei stellen sollten:
Für welche Nichtterminale gibt es ? Zeichnen Sie ein Bildchen mit all
diesen -Pfeilen.
Von welchen Nichtterminalen können Sie überhaupt Wörter ableiten, also ? Wie
finden Sie das im Allgemeinen heraus?
Welche Nichtterminale können ableiten, also ? Wie
finden Sie das im Allgemeinen heraus?
Wenn nun in Chomsky-Normalform vorliegt und wir für ein gegebenes Eingabewort
eine Ableitung finden wollen (oder feststellen, dass es keine gibt),
so ist die erste Beobachtung, dass eine Linksableitung die Form
haben muss, für . Wenn wir die
Unterteilung von in und kennen würden, so könnten wir rekursiv
fragen, wie man denn und ableitet. Da wir sie
aber nicht kennen, also konkret nicht wissen, wie lange und sind,
können wir alle Möglichkeiten durchprobieren. Da in Chomsky-Normalform vorliegt, wissen
wir, dass und , also . Wir probieren also
alle möglichen Zerlegungen von durch. Wenn wir das rekursiv täten, dann würden
das eine enorme Laufzeit verursachen. Der Trick besteht darin, Zwischenergebnisse systematisch
zu berechnen, um somit Laufzeit zu sparen.
Der CYK-Algorithmus
Die oben skizzierte Idee ist im CYK-Algorithmus konkretisiert (benannt nach John Cocke, Daniel
Younger und Tadao Kasami).
Für die praktische Anwendung ist dieser weniger relevant. Dafür ist er ein wunderbares Beispiel
für einen
Algorithmus, der auf dem Prinzip des Dynamic Programing fußt, welches Sie in der
Vorlesung Algorithmen und Komplexität im dritten Semester
ausführlicher kennenlernen wollen. Wir beschränken uns bei dem folgenden Algorithmus
zunächst darauf, die Frage zu beantworten, ob den überhaupt gilt,
und interessieren uns erst einmal nicht dafür eine solche Ableitung auch zu finden (in der
Algorithmik versteht man das als Entscheidungsproblem, im Gegensatz zu dem
allgemeinerin
Suchproblem).
Der Entwurf eines Dynamic-Programming-Algorithmus beginnt oft mit der folgenden Frage:
Was sind sinnvolle Zwischenergebnisse? In unserem Falle sind Ableitungen der Form
sinnvolle Zwischenergebnisse, wenn ein Teilwort von ist, also
. Konkret schreiben wir und definieren
und
Das ist also die Menge der Nichtterminale, die das Teilwort ableiten können.
Die "Hauptfrage" ist dann: enthält das Startsymbol ?
Der CYK-Algorithmus berechnet nun die Mengen systematisch
für , versucht also, alle Unterwörter der Länge abzuleiten,
beginnend mit , also . Diese Mengen sind leicht
zu bestimmen:
Das gilt nur, weil in Chomsky-Normalform vorliegt und somit Ableitungen mit mehr als einem
Schritt
notwendigerweise Wörter mit mehr als einem Zeichen produzieren würden.
Nun müssen wir uns Gedanken machen, wie wir berechnen, also
das Unterwort , das Länge hat, ableiten, wenn wir bereits wissen,
wie wir kürzere Unterwörter herleiten. Die Kernbeobachtung ist:
, also gilt genau dann,
wenn es eine Produktion gibt, so dass dann
gilt. Das Problem ist, wie bereits oben skizziert, dass wir die "Grenze" nicht kennen. Wir
probieren also alle Grenzen aus, und somit
Dies können wir mit einer Schleife über und einer Schleife über alle
Produktionen
berechnen, da wir die Mengen und ja bereits kennen,
da , diese Bereiche also kleinere Unterwörter darstellen.
Initialisiere für alle die Mengen
for l = 2 .. n
for i = 0 .. n-l
k := i+l# wir betrachten das Interval
w[i:k] der Länge l
Berechne wie in . Konkret heißt das:
Initialisiere
for j = i+1 .. k-1:
for all productions:
füge zu hinzu, falls und
gilt.
end for all productions
end for j
end for k
end for i
return True ifelse return False
Was ist die Laufzeit des Algorithmus? Sei die Anzahl der Produktionen in der
Form . Die innerste Schleife, also Punkt a, wird jedes Mal mal
durchlaufen, und somit
wird Zeile a insgesamt
ausgeführt. Können Sie eine geschlossene Form für die drei Summen angeben? Leichter geht es,
wenn wir erkennen, dass für alle die Ungleichung
gilt,
und jedes solche Tripel auch drankommt. Wie viele solche Tripel gibt es? Genau
viele. Also: die Zeile a wird
mal durchlaufen. Also "ungefähr" und "noch ungefährer" . Also:
Beobachtung 6.10.2
Sei die Länge des Eingabewortes und die Anzahl der Produktionen der Form
. Dann ist die Laufzeit des CYK-Algorithmus proportional zu
.
Achtung: wir haben die Kosten für die allererste Zeile vernachlässigt.
Überlegen
Sie sich, wie viel Laufzeit diese verursachen kann und überzeugen Sie sich, dass dies in den
meisten
Fällen gegenüber dem Term wohl nicht ins Gewicht fallen wird.
Demo 6.10.3
Betrachten wir die Grammatik
und das Eingabewort .
Wenn wir uns zusätzlich zu jedem noch merken, durch
welche Regeln es aufgenommen wurde und
für welches man hat, dann können wir
den Ableitungsbaum leicht rekonstruieren.