5.2 Kontextfreie Grammatiken und Kellerautomaten

Theorem 5.2.1 Zu jeder kontextfreien Grammatik G=(Σ,N,S,P) gibt es einen Kellerautomaten M=(Σ,Q,Γ,qstart,δ), der die gleiche Sprache akzeptiert, also L(G)=L(M).
Beweis. Der Beweis ist vergleichsweise einfach, weil wir die Grammatik eins zu eins in Automatentransitionen übersetzen können. Die Idee ist, dass die Symbole auf dem Stack, von unten nach oben gelesen, zu jedem Zeitpunkt eine Wortform bilden, aus der der noch nicht gelesene Teil des Restwortes ableitbar ist. Wenn oben auf dem Stack also ein Terminalsymbol x liegt und x auch das nächste Inputzeichen ist, dann poppen wir x vom Stack und lesen x, d.h. schieben den Lesekopf um ein Zeichen nach rechts; der Stack ist von xα auf α geschruft und das Restwort von xw auf w; wenn also xαxw galt, so gilt nun immer noch αw. Wenn ein Nichtterminal X oben auf dem Stack liegt, dieser also die Form Xα hat, und w das Restwort ist, dann gilt also Xαw. Der Automat rät nun nichtdeterministisch die erste in der Ableitung angewandte Produktion Xβ, löscht X vom Stack und pusht β. Der Stack ist nun βα, und βαw. Entscheidend ist der Nichtdeterminismus: es gibt eine korrekte Produktion Xβ, die schlussendlich zu w führt; der Automat kann also diese Transition anwenden.
Formaler: Die Zustände des Automaten sind Q={qstart,q1,qend}, das Stackalphabet ist Γ=ΣN{$} und die Transitionsregeln sind (q1,x)x(q1,ϵ)für jedes xΣ(q1,A)ϵ(q1,β)für jede Produktion Aβ(qstart,ϵ)ϵ(q1,S$)(q1,$)ϵ(qend,ϵ) Auf einen formalen Beweis, dass L(M)=L(G) ist, verzichte ich an dieser Stelle. Besser als Sipser in seinem Lehrbuch Introduction to the Theory of Computing könnte ich das eh nicht.
Beispiel 5.2.2 Betrachten wir die Grammatik SA | B | C | ϵA{B}SB[C]SC(A)S | ()S Ein Wort in der erzeugten Sprache wäre zum Beispiel {[()()]()}()

Schreiben wir nun einen Kellerautomaten, der diese Sprache akzeptiert. Die Idee ist, dass wir Terminalsymbole und Nichtterminalsymbole auf den Stack legen. Ein [ oben auf dem Stack bedeutet dann ich will jetzt sofort ein [ lesen; ein Nichtterminal wie A oben auf dem Stack bedeutet, dass wir als nächstes ein von A ableitbares Wort, also ein AwΣ lesen wollen. Um ein w mit Aw lesen zu können, müssen wir sofort ein { lesen, dann ein Wort v mit Bv, dann ein } und so weiter. Wir können das also im Automaten implementieren, indem wir A vom Stack löschen und durch {B}S auf den Stack legen, mit dem linkesten Symbol zuoberst. Wenn wir für ein Nichtterminal mehrere Regeln haben, also z.B. Xα und Xβ, dann können ein X auf dem Stack sowohl durch α als auch durch β ersetzen. Hierfür benötigen wir den Nichtdeterminismus. Beachten Sie, dass wir ein Nichtterminal grundsätzlich immer durch die entsprechende rechte Seite ersetzen können, egal, was das nächste Zeichen ist; es wird im Automaten also ein ϵ-Übergang sein.

Konkret also bauen wir für obige Grammatik die folgenden Automatentransitionen: (q1,S)ϵ(q1,A)(q1,S)ϵ(q1,B)(q1,S)ϵ(q1,C)(q1,S)ϵ(q1,ϵ)(q1,A)ϵ(q1,{B}S)(q1,B)ϵ(q1,[C]S)(q1,C)ϵ(q1,(A)S)(q1,C)ϵ(q1,()S)(q1,{){(q1,ϵ)(q1,})}(q1,ϵ)(q1,[)[(q1,ϵ)(q1,])](q1,ϵ)(q1,()((q1,ϵ)(q1,)))(q1,ϵ) Dies ist völlig mechanisch und benötigt kein Nachdenken. Wie fangen wir an? Wir legen anfangs ein S auf den leeren Stack. Wenn dieses S abgearbeitet ist und das Wort zu Ende ist, akzeptieren wir, und nur dann. Um festzustellen, dass wir wirklich den Stack ganz leer gemacht haben, brauchen wir die Markierung $. Also: (qstart,ϵ)ϵ(q1,S$)(q1,$)ϵ(qend,ϵ) Unsere Maschine hat also nur drei Zustände: qstart, q1 und qend, welches der akeptierende Endzustand ist. Beachten Sie, dass es von qend aus keine ausgehenden Transitionen gibt; sollte es also nach Erreichen von qend noch weitere Zeichen im Eingabewort geben, so kann der Automat keine weiteren Schritte durchführen, was einem reject entspricht. Erreichen des Zustandes qend führt also nur dann zu einem accept, wenn dies am Ende des Wortes geschieht.

Die Gegenrichtung ist schwieriger.

Theorem. 5.2.3Zu jedem Kellerautomaten gibt es eine kontextfreie Grammatik mit .

Ich folge hier im Wesentlichen dem Beweis aus Sipsers Kapitel 2.

Beweis. Sei der Kellerautomat. Als erstes führen wir drei Schönheitsoperation durch:
  1. Der Automat hat einen einzigen akzeptierenden Zustand . Dies können wir einfach durch -Übergänge erreichen.
  2. Der Automat leert den Stack, bevor er akzeptiert. Dies können wir z.B. dadurch erreichen, dass wir anfangs ein auf den Stack legen und am Ende den Stack poppen, bis wir gepoppt haben.
  3. Für jede Transition ist genau eines von leer (und das andere ist genau ein Stacksymbol aus ). Eine Transition der Form nennen wir eine Push-Operation, eine der Form nennen wir eine Pop-Operation. Der Automat kann in diese Form gebracht werden, indem wir Zwischenzustände einführen: Im zweiten Falle pushen wir also pro Forma ein ansonsten irrelevantes Symbol auf den Stack, um es gleich darauf runterzupoppen.

Wenn nun der Automat die eben beschriebene Form hat, so ist die Idee, dass wir für jedes Paar von Zuständen ein Nichtterminalsymbol einführen, dass genau die Wörter ableiten kann, für die gilt, die also den Automaten von nach bringen können, wobei der Stack am Anfang und am Ende leer ist. Dies kann auf zwei Weisen geschehen:

  1. Fall 1: in der Konfigurationsfolge von wird der Stack zwischendurch auch mal leer, und zwar nachdem der Präfix des Wortes gelesen ist, und der Automat ist zu diesem Zeitpunkt im Zustand . Also: Es sollte also (wenn unsere Konstruktion erfolgreich ist) gelten, dass und . Wir führen daher die Grammatikproduktion ein.
  2. Fall 2: in der Konfigurationsfolge von ist der Stack zwischendurch nie leer. Das heißt wiederum, dass das im ersten Schritt gepushte Stacksymbol am Ende gepoppt wird, also mit Hier ist wichtig zu wissen, dass die erste Operation eine Push-Operation sein muss: Pop kann sie eh nicht sein, und die Möglichkeit haben wir durch unsere Schönheitsoperationen ausgeschlossen. Des weiteren ist zu beachten, dass in der Konfigurationsfolge von der Stack nie leer wird. Keine Transition "liest" also das unterste Zeichen -- denn dann müsste nacha Schönheitsoperation das ja gepoppt werden und der Stack würde vollständig geleert. In anderen Worten: die Konfigurationsfolge wäre auch dann gültig, wenn Anfangs- und Endstack nicht sondern , also der leere Stack wäre: Wenn unsere Konstruktion Erfolg hat, hieße das also und somit ist eine gültige Regel.

Schlussendlich führen wir noch die offensichtlich korrekten Regeln ein, da ja offensichtlich gilt (da ja auch die aus null Übergangen bestehende Konfigurationsfolge erlaubt).

Zusammenfassend gesagt besteht unsere Grammatik aus drei unterschiedlichen Typen von Regeln:

Das Startsymbol der Grammatik ist natürlich .

Auch hier verzichte ich auf einen formalen Beweis, dass gilt. Der Beweis verwendet (wenig überraschend) Induktion über die Länge der Ableitung bzw. Induktion über die Länge der Konfigurationsfolge und ist in Sipsers Lehrbuch im Detail erklärt.
Beispiel 5.2.4 Wir betrachten die Sprache Diese ist nicht regulär, da der Präfix und der Suffix aus die gleiche Länge haben müssen. Wir bauen zuerst einen Kellerautomaten und übersetzen den dann entsprechend dem Schema aus dem letzten Theorem in eine Grammatik. Dann vergleichen wir die entstandene Grammatik mit der, die wir per Hand aus der obigen Sprachbeschreibung bauen würden.

Unser Automat hat einen Zustand , in welchem er führenden 's auf den Stack legt und einen Zustand , in welchem er die 's am Ende liest und vom Stack poppt. Zwischen den beiden 's in der Mitte ist er in einem Zustand , in welchem er die Eingabesymbole mehr oder weniger ignoriert. Der Automat muss nichtdeterministisch sein, da er "erraten" muss, welches das letzte ist, weil er dann mit dem Stack-Poppen beginnen muss.

Die beiden rot gefärbten Transitionen benötigen eine Schönheitsoperation:

Unsere Zustandsmenge ist also . Rein theoretisch müssten wir nun für alle Tripel von Zuständen eine Produktion einführen, also allein hierfür schon Produktionen. Wir können das reduzieren, indem wir die im Beweis beschriebene mechanische Prozedur mit ein wenig selbständigem Denken kombinieren.

Die erste Beobachtung ist, dass wir gar nicht von jedem Zustand zu jedem kommen können, sondern nur "vorwärts", also wie in diesem Diagramm:

Nichtterminale wie führen wir also gar nicht erst ein. Auch schreiben wir statt und verwenden die Indizes 0 für und 4 für . Als zweite Beobachtung sehen wir, dass man mit leerem Stapel beispielsweise gar nicht von nach kommt: auf dem Weg dorthin wird ein gepusht und nie gepoppt; gleiches gilt für viele weitere Zustandspaare. Das erweiterte Diagramm hier gibt eine Orientierung. Neben jeden Zustand haben wir schematisch den Stack gemalt:

Die einzigen Nichtterminale sind also die "trivialen" der Form und darüberhinaus Da man von zu sich selber und von zu sich selber nur mit dem leeren Wort gelant, ersetzen wir und sofort durch . Als Produktionen der Form erhalten wir also: Schon deutlich besser als die 125 Produktionen, die wir bekämen, wenn wir stur an der Methode im Beweis festhalten würden. Wir brauchen noch Produktionen der Form . Dafür müssen wir nach Push-Pop-Paaren , suchen. Stack-Symbole sind . Suchen wir also zuerst nach Paaren von Transitionen, die pushen und poppen. Das waren die einzigen Transitionen, in denen gepusht und gepoppt wird. Betrachten wir als nächstes das Stacksymbol : Nun zu den Transitionen mit Stacksymbol . Da gibt es zwei Push-Transitionen und eine Pop-Transition, also zwei Paare: Und schlussendlich die mit Stacksymbol $. Da gibt es eine Push- und eine Pop-Transition: Sammeln wir nun alle Produktionen: wobei das Startsymbol ist. Wir haben diejenigen Nichtterminale und Produktionen rot markiert, aus denen nur hervorgehen kann. Wir können die Grammatik nun entsprechend vereinfachen. Zusätzlich löschen wir und ernennen zu unserem Startsymbol:

Überlegen wir nun, ausgehend von dieser Grammatik, was wir aus dem Startsymbol ableiten können. Die einzigen zwei Produktionen mit sind und , also gilt Aus können wir per Produktion erst einmal alle Wortformem der Form ableiten; per dann alle Wortform . Also: Die Grammatik erzeugt also die gewünschte Sprache.