DiscoverTheoretische Informatik
Theoretische Informatik
Claim Ownership

Theoretische Informatik

Author: Till Tantau

Subscribed: 4Played: 5
Share

Description


Diese Vorlesung wurde von Prof. Dr. Till Tantau an
der Universität zu Lübeck im Wintersemester 2009
gehalten.
27 Episodes
Reverse
26. P-Vollständigkeit

26. P-Vollständigkeit

2010-02-1401:02:00

Im vorigen Jahrhundert hatte die Parallelverarbeitung bereits große praktische Bedeutung~-- aber eben nur im Bereich der doch eher seltenen Supercomputer, die ihrerseits in der Regel nur für sehr spezielle numerische Probleme eingesetzt wurden. Es ergab damals keinen Sinn, Feld-Wald-und-Wiesen-Software für Parallelrechner fit zu machen -- es gab einfach keine auf den Schreibtischen der Kunden. Umgekehrt versuchten Hardware-Hersteller auch nicht, mehrere Prozessoren in die Computer einzubauen, da diese von Standardsoftware nicht genutzt wurde. Ein klassisches Henne-Ei-Problem lag vor und die Parallelverarbeitung verfiel in einen etwas unruhigen Dornröschenschlaf. Aus diesem Schlaf ist sie irgendwann kurz nach der Jahrtausendwende wachgeküsst worden -- allerdings nicht von einem Prinzen, sondern von Multi-Core-Prozessoren. Heute kann man beim Entwurf auch eines einfachen Standardprogramms davon ausgehen, dass in der Regel mehrere Rechenkern zur Verfügung stehen werden. Damit drängelt sich sowohl in der Praxis wie auch in der Theorie ein Problem in den Vordergrund, das bis jetzt doch eher im Abseits gestanden hat: Wie gut lassen sich Standardprobleme eigentlich parallelisieren? Mit anderen Worten: Wenn ich 16 Kerne zur Verfügung habe, kann ich das Problem dann auch 16 Mal so schnell lösen? Die Frage, welche Probleme wie gut parallelisierbar sind, wird in diesem Kapitel nicht beantwortet werden -- schließlich gibt es eine eigene Vorlesung »Parallelverarbeitung« in der es um nichts anderes geht. Jedoch können wir jetzt schon festhalten, dass man parallele Programme immer in sequentielle Programme umwandeln kann, indem parallelen Anweisungen einfach nacheinander ausgeführt werden. Deshalb können parallele Programme auf einem Rechner mit $p$ Kernen höchsten um den Faktor $p$ schneller sein als sequentielle Programme. In der Praxis ist $p$ eine kleine Zahl, irgendetwas zwischen $2$ und vielleicht $100$ -- ein Parallelrechner mit einer Million Kernen ist doch eher die Ausnahme. Dies bedeutet aber, dass man mit Parallelrechnern »auch nur« Probleme in $\Class P$ effizient wird lösen können. Der Geschwindigkeitsvorteil, den eine Parallelisierung bringt, wird bei einem Exponentialzeit-Problem schon durch eine kleine Erhöhung der Eingabegröße sofort zu Nichte gemacht. Die Frage, welche Probleme gut parallelisierbar sind, ist also die Frage, welche Problem in~$\Class P$ gut parallelisierbar sind. Wenn man etwas darüber nachdenkt, so stellt man schnell fest, dass fast alle Probleme, die einem so spontan einfallen, irgendwie ganz gut parallelisierbar scheinen. Da darf die Frage erlaubt sein, ob nicht vielleicht einfach alle Probleme in~$\Class P$ sich auf einem Parallelrechner viel schneller lösen lassen? Die Beantwortung einer solchen Frage scheint wieder nicht ganz einfach, da es ja doch recht viele und noch dazu sehr unterschiedliche Probleme in der Klasse~$\Class P$ gibt. An dieser Stelle kommt nun der Titel dieses Kapitels ins Spiel: Statt uns mit unendlich vielen komischen Problemen herumzuschlagen, könnten wir uns auf die Parallelisierbarkeit nur eines Problems konzentrieren, wenn wir ein vollständiges Problem für~$\Class P$ finden. Ein solches Problem gibt es: Das Circuit-Value-Problem, welches im Folgenden eingehend vorgestellt wird. Was weiß man nun über die Parallelisierbarkeit des Circuit-Value-Problems? Trotz jahrelanger Forschung ist es leider weder gelungen zu zeigen, dass sich dieses Problem gut parallelisieren lässt (und damit auch alle anderen Problem in $\Class P$ aufgrund der Vollständigkeit), noch dass es sich nicht gut parallelisieren lässt. Nach der $\Class P$-$\Class{NP}$-Frage, der ja das gesamte Kapitel~\ref{lecture-p-np} gewidmet ist, ist die Frage, wie gut sich das Circuit-Value-Problem parallelisieren lässt, wohl die zweitwichtigste Frage der Theoretischen Informatik.
27. NP-Vollständigkeit

27. NP-Vollständigkeit

2010-02-1401:20:40

Ist es moralisch verwerflich, zu betrügen? Betrug wird im Strafrecht als ein doch eher schweres Delikt angesehen und gegebenenfalls hart bestraft. Mensch sollten nicht betrügen. Bei Maschinen ist die Sachlage komplexer: Es ist nicht unmoralisch, wenn Maschinen sich ein wenig »helfen« lassen -- es führt nur zu Berechnungsmodellen, die uns in der Praxis nicht viel nützen. Jeder möge nun für sich selbst entscheiden, was schlimmer ist (dies ist natürlich eine moralische Frage). Wie können Maschinen nun schummeln? Ein besonders raffiniertes Verfahren ist die Benutzung von »nichtdeterministischen Hinweisen«. Die Situation ist ähnlich wie bei einem Studenten, der eine Klausur schreibt und dem kleine Zettelchen zugeflogen kommen, auf denen Dinge stehen wie »addiere erst die dritte und vierte Zahl« oder »schaue auf \label{this-page}Seite~\pageref{this-page} im Skript nach, da steht die Lösung«. Das Problem ist nur, dass diese Hinweise nicht sonderlich verlässlich sind: Es kann sein (nichts genaues weiß man nicht), dass der ominöse Zettelproduzent dummerweise eine andere Klausur schreibt. Dann laufen die Hinweise natürlich ins Leere. In diesem Fall muss die schummelnde Maschine in der Lage sein zu erkennen, dass hier etwas nicht mit rechten Dingen zugeht: Formal soll sie (schnell) die korrekte Klausurantwort produzieren bei geeigneten Hinweisen; hingegen soll sie (schnell) verwerfen, wenn die Hinweise sie nicht zur korrekten Lösung führen werden. Solche nichtdeterministischen Hinweis sind etwas zwielichtig, schwer zu definieren und schwer zu motivieren. Trotzdem benötigen wir sie, um die wohl wichtigste und bestuntersuchte Klasse der Komplexitätstheorie zu definieren: $\Class{NP}$. Von dieser Klasse haben selbst Nichtinformatiker oft schon im Rahmen des $\Class P$-$\Class{NP}$-Problems gehört. Bei diesem Problem geht es letztendlich um die Frage, ob man jede Berechnung mit Schummelei in eine genauso effiziente Berechnung ohne Schummeln umwandeln kann. Lug und Betrug bei Berechnungen kann man übrigens auch noch doller treiben (was wir in dieser Vorlesung aber nicht machen werden, dies ist dem Master-Studium vorbehalten, wo Sie auch moralisch hoffentlich schon gefestigter sind): Statt nur rätselhafte und in der Regel falsche Hinweise zu bekommen für eine Berechnung, kann man auch gleich ein mächtiges Orakel fragen. Solche Orakel kann die Maschine beliebig oft befragen, ob verschiedene Worte Element einer bestimmten Sprache sind -- und das beste ist, dass die Frage nichts kosten. Offenbar werden viele Problem sehr einfach zu beantworten, wann man ein mächtiges Orakel befragen darf: So ist das Problem $\Lang{sat}$ nun wirklich leicht zu entscheiden, wenn man ein »$\Lang{sat}$-Orakel« befragen darf. Wenn man aber etwas darüber nachdenkt, so stellt man schnell fest, dass bestimmte Probleme selbst dann noch schwer sind, wenn man ein Orakel befragen darf. So ist es ein wichtiges offenes Problem, ob man beispielsweise einen effizienten Algorithmus für das Brettspiel »Go« programmieren kann, wenn man ein $\Lang{sat}$-Orakel zur Verfügung hat.
28. Das P-NP-Problem

28. Das P-NP-Problem

2010-02-1401:24:00

\includemovie[label=song]{0pt}{0pt}{\bodydir/media/Longest-Path-pd-Dan-Barrett-compressed.mp3}% \begin{verse} \centerline{\itshape Find the Longest Path} \smallskip \centerline{by Dan Barrett} \smallskip \centerline{(Original The Longest Time} by Billy Joel) \bigskip \bigskip \bigskip \small% \begin{minipage}[t]{.5\linewidth}\parskip1em Oh, oh, oh, oh,\\ find the longest path.\\ Oh, oh, oh,\\ find the longest path. If you said P is NP tonight,\\ there would still be papers left to write.\\ I have a weakness:\\ I'm addicted to completeness\\ and I keep searching for the longest path. The algorithm I would like to see\\ is of polynomial degree.\\ But it's elusive,\\ nobody has found conclusive\\ evidence that we can find the longest path. I have been hard\\ working for so long.\\ I swear it's right,\\ and he marks it wrong.\\ Somehow I'll feel sorry when it's done:\\ gpa 2.1\\ is more than I hope for. \medskip \end{minipage}\hskip5mm\begin{minipage}[t]{.5\linewidth-5mm}\parskip1em Garey, Johnson, Karp and other men\\\hbox{}\hfill (and women)\\ try to make it order $n \log n$.\\ Am I a math fool\\ if I spend my life in grad school\\ forever following the longest path? Oh, oh, oh, oh,\\ find the longest path.\\ Oh, oh, oh,\\ find the longest path.\\ Oh, oh, oh, oh,\\ find the longest path\dots \vskip1cm \hfill \movieref[play]{song}{\strutAnhören}\ \ |\ \ \movieref[stop]{song}{\strutStopp} \end{minipage}% \end{verse}
25. NL-Vollständigkeit

25. NL-Vollständigkeit

2010-02-1401:35:42

Die Untersuchung von kleinen Platzklassen erscheint auf den ersten Blick reichlich »akademisch«, »mainly of scholary interest«, kurz gesagt: überflüssig. Speicherplatz ist (im Gegensatz zu Zeit) in aller Regel wahrlich nicht unser Problem. Wenn eine Ingenieurin ein Gigabyte an Eingabedaten hat, dann wird sie sicherlich auch irgendwo noch Platz für ein Gigabyte weiteren Speicher finden. Man könnte einwenden, dass kleine Systeme wie Smartcards oder eingebettete Systeme wie Waschmaschinensteuerungen nicht mit übermäßigem Speicherplatz gesegnet seien und es sich sehr wohl lohne, sparsam damit umzugehen. Wohl wahr -- jedoch erscheint es schon von dagobertduckhaftem Geiz zu zeugen, nur logarithmisch viel Platz vorzusehen. Hat man beispielsweise ein Gigabyte an Eingabedaten, so dürfte man bei einem erlaubten Platz von $\log_2 n$ Bits gerademal 33 Bits an Speicher verbrauchen, also großzügig gerundet zwei 32-Bit-Register. Wächst die Eingabemenge auf 1000 Terabyte, so hätte man immerhin 53 Bits zur Verfügung, also immernoch nicht mehr als zwei Register. Bei der größten in diesem Universum überhaupt denkbaren Eingabe ($10^{80}$, denn so viele Atome hat das Universum, plus/minus ein paar Zehnerpotenzen), hätte man immerhin zehn 32-Bit-Register zur Verfügung. In diesem Kapitel soll nun logarithmischer Platz noch mit Nichtdeterminismus gepaart werden, was uns zur Klasse $\Class{NL}$ führen wird. Diese Paarung mag Theoretiker beglücken, dem Praktiker stehen aber zunächst die Haare zu Berge: Da wird ein unrealistisches Modell (lächerlich wenig Platz) mit einem noch viel unrealistischeren Modell (nichtdeterministische Turing-Maschinen) zusammengebracht; das Resultat sind Maschinen, die man nicht bauen kann, und wenn man sie bauen könnte, sinnlosen Einschränkungen unterliegen. Warum lohnt es sich trotzdem, nichtdeterministischen logarithmischen Platz zu untersuchen? Ich möchte folgende Gründe anführen: \begin{enumerate} \item Erstaunlicherweise sind nichtdeterministischer logarithmischer Platz und die Parallelisierbarkeit von Problemen aufs Engste verwoben; die Details hierzu übersteigen zwar den Rahmen dieser Vorlesung, sie werden aber in den Vorlesungen »Parallelverarbeitung« und »Komplexitätstheorie« genauer untersucht. \item Das »Wesen des Nichtdeterminismus« zu verstehen hat sich zu einer der Hauptaufgaben der Theoretischen Informatik gemausert. Wir haben in den Kapiteln \ref{lecture-nfa} und~\ref{lecture-regex} schon gesehen, dass Nichtdeterminismus zumindest innerhalb der Theorie ein sehr nützliches Hilfsmittel darstellt. Es hat sich gezeigt, dass Nichtdeterminismus bei Platzklassen einfacher zu verstehen und zu untersuchen ist, als dies bei Zeitklassen der Fall ist (die uns ja eigentlich mehr interessieren). Ein Beispiel für den Erfolg dieser theoretischen Untersuchungen ist eine Konsequenz des Satzes von Immerman--Szelepcsényi: Die Klasse der kontextsensitiven Sprachen (siehe Definition~\ref{def-csl) ist abgeschlossen unter Komplementbildung.} Diesem Resultat sieht man wahrlich nicht an, dass hier viel Theorie über nichtdeterministischen logarithmischen Platz drinsteckt. \item Die Klasse $\Class{NL}$ wird die erste natürliche Klasse sein (zur Frage, wie »natürlich« diese Klasse wirklich ist, siehe oben), für die wir ein (nun wirklich) natürliches vollständiges Problem angeben können: Das Erreichbarkeitsproblem $\Lang{reach}$ (auch $\Lang{gap}$ genannt) ist vollständig für~$\Class{NL}$. Der Beweis ist hier relativ einfach und soll als »Aufwärmübung« für die vertrackteren Beweise in den folgenden Kapiteln dienen. \end{enumerate}
\includegraphicscopyrightborderautowidth{\bodydir/media/Zutaten_caipirinha-ccas-Christian_Horvat.jpg}{Autor Chrisitan Horvat, Creative Commons Attribution Sharealike Lizenz}% Das letzte Kapitel hat Ihnen Reduktion hoffentlich bereits schmackhaft gemacht. Manche Probleme haben nun die Eigenschaft, dass ziemlich viele andere Probleme auf sie reduziert werden können. Wenn Sie eine Caipirinha gemixt bekommen, dann werden Sie auch keine Probleme haben mit einer Caipiroschka, einer Caipirissima oder zur Not halt einer Virgin-Caipi. Man kann guten Gewissens behaupten: Sie werden in der ganzen Sippe der Caipi-Cocktails keinen wirklich »schwierigeren« Cocktail finden. Der Theoretiker würde nun sagen, dass das Problem der Caipirinha-Herstellung vollständig ist für die Klasse aller Caipi-Cocktails (vorausgesetzt, der Theoretiker kann nach der eingehenden Untersuchung der verschiedenen Cocktails noch verständlich kommunizieren). In diesem Kapitel geht es um die Frage, wo man in der Theorie außer bei alkoholischen Getränken noch überall vollständige Probleme antreffen kann. Sprich: Gibt es in bestimmten Komplexitätsklassen vielleicht Probleme, die »unglaublich hilfreich« sind? Gibt es Probleme, so dass man alle Probleme in der Klasse lösen kann, wenn man nur diese Problem lösen kann? Gibt es also Probleme, so dass sich alle Probleme einer Klasse auf dieses eine Problem reduzieren lassen? Die Intuition sagt einem zunächst, dass es solche Probleme nicht geben sollte. Nehmen wir die doch recht große Klasse $\Class P$. Diese versammelt ja alle Probleme, die sich in Zeit $O(n)$ oder in $O(n^2)$ oder in $O(n^3)$ oder in $O(n^4)$ oder so weiter lösen lassen. Man darf deshalb erwarten, dass es zu jedem Problem in $\Class P$ »immer ein noch schwierigeres« Problem in $\Class P$ geben sollte. Die Intuition ist jedoch falsch: Es gibt ein Problem in~$\Class P$, auf das sich alle anderen Probleme in~$\Class P$ reduzieren lassen. Kann man also dieses Problem so-und-so gut lösen, so kann man auch alle anderen Probleme in $\Class P$ prinzipiell auch so-und-so gut lösen. Dieses Problem wird in Kapitel~\ref{lecture-cvp} detaillierter vorgestellt. Die Klasse $\Class P$ ist nicht die einzige Klasse, die ein solches »besonders hilfreiches« Problem enthält. Es hat sich herausgestellt, dass die meisten Komplexitätsklassen solche »vollständigen« Probleme besitzen. In diesem Kapitel werden zunächst die Idee der Vollständigkeit veranschaulicht, bevor die nächsten Kapitel dann für verschiedene Klassen vollständige Probleme vorstellen werden.
Falls Sie jemals vorhaben, die Menschheit eines Tages durch Ihren Nachwuchs zu beglücken, so möchte ich Ihnen dringend davon abraten, Komplexitätstheoretiker nach Namensvorschlägen für Ihr Baby zu befragen. Die Namen, die sich Komplexitätstheoretiker ausdenken, sind -- höflich formuliert -- extrem verwirrlich. Im letzten Kapitel wurden ja schon einige Klassen eingeführt, deren Namen typisch sind: Warum heißt logarithmischer Platz $\Class L$, polynomieller Platz hingegen $\Class{PSPACE}$? Wieso nennt man Probleme, die sich praktisch gar nicht und selbst theoretisch nur mit exorbitantem Aufwand lösen lassen, »elementar«? Und überhaupt, warum nennt man Probleme eigentlich »Sprachen«? Gar nicht erwähnt habe ich vorsichtshalber, dass es neben der Klasse $\Class{EXP}$ auch die Klasse $\Class E$ gibt, die fast genauso definiert ist -- nur eben ein ganz klein bisschen anders. Perfektioniert wird das Chaos dadurch, dass sich die Namen mit den Jahren auch gerne verlängern oder verkürzen, je nachdem was gerade so in Theoretikerkreisen als modisch und besonders hipp gilt. Aus dem Mauerblümchen $\Class{PTIME}$ wurde so das trendy $\Class P$, die fette Klasse $\Class{EXPTIME}$ wurde zu $\Class E$ abgemagert -- logisch wäre zwar $\Class{EXP}$ gewesen, aber das war ja schon anderweitig vergeben. »Was soll's?«, mag man einwenden, andere Disziplinen wie die Biologie oder die Medizin sind auch voll von unsinnigen Namen und Abkürzungen. Sind aber die meisten Klassennamen einfach nur verwirrlich, so sind die drei Bezeichungen »Reduktion«, »Schwere« und »Vollständigkeit« für drei zentrale Ideen der Theorie katastrophale Fehlentscheidungen gewesen: Sie verwirren Anfänger oft dermaßen, dass sie noch während des Vorspiels die Lust an der Materie verlieren. Ein Reduktion macht ein Problem weder kleiner noch einfacher; ein schweres Problem ist nicht etwa schwer, sondern oft sehr leicht zu lösen; ein vollständiges Problem ist nicht das Gegenteil eines Teilproblems. Leider ist es zu spät, die unsinnigen Namen zu ändern, sie haben sich festgesetzt. Wie würden bessere Bezeichungen lauten? Statt von einer »Reduktion zwischen Problemen« sollte man lieber von einem »Vergleich von Problemen« sprechen, denn das macht eine Reduktion in Wirklichkeit: Kann man $A$ auf $B$ reduzieren, dann bedeutet dies eigentlich nur »die Komplexität von $A$ ist höchstens so groß wie die von~$B$«. Statt »schwer für eine Klasse« sollte man eher sprechen von »hilfreich für eine Klasse«: Wenn $A$ schwer ist für eine Klasse, dann kann man alle Probleme in der Klasse lösen, wenn man nur $A$ lösen kann. Schließlich sollte »vollständiges Problem für eine Klasse« am besten in »hilfreiches Problem in einer Klasse« umbenannt werden: Der einzige Unterschied zwischen schweren und vollständigen Problemen ist nämlich, dass letztere eben in der Klasse liegen müssen, erstere hingegen nicht unbedingt. In diesem Kapitel werden wir zunächst Reduktionen genauer untersuchen, in darauffolgenden Kapitel geht es dann mit schweren und vollständigen Problemen weiter. Bleibt die Frage, wen Sie um Rat fragen sollte bezüglich der Benennung Ihrer Kinder. Fragen Sie doch einen Mathematiker! Menschen, die »durch Distributivgesetze miteinander verschränkte Operationen mit zugrundeliegenden Gruppenstrukturen« schlicht »Ringe« nennen, scheinen mehr Gespür für die Schönheit von Worten zu haben. Und wenn dem Ring noch einige weitere Axiome wie Kommutativität der Gruppenstruktur oder Nullteilerfreiheit hinzugefügt werden, so nennen Mathematiker dies nicht etwa »durch Distributivgesetze miteinander verschränkte Operationen mit zugrundeliegenden kommutativen Gruppenstrukturen und Nullteilerfreiheit«, sondern etwas verträumt »Körper«.
21. Die $O$-Notation

21. Die $O$-Notation

2010-01-3101:14:13

Herr Winston Wolf, der Ihnen vielleicht noch aus »Logik für Informatiker« bekannte Leiter des Amts für Problemlösungen, bekommt an einem Montag morgens ein neues Problem auf seinen Aktenstapel: Die Regierung bittet darum, das Problem mit den ungenauen Klimamodellen doch bitte zu lösen -- man hätte gerne verlässlichere Aussagen über die Entwicklung des Weltklimas in den nächsten hundert Jahren. Herr Wolf ruft daraufhin beim Amt für Klimaforschung an, wo man ihm sinngemäß Folgendes mittelt: »Unsere Prognosen werden um so besser, je höher die räumliche Auflösung der Simulationen ist. Dazu brauchen wir mehr Rechenpower.« In der Informatikabteilung seines Amtes arbeiten drei Informatikerinnen -- eine ist zuständig für Technische Informatik, eine für Praktische Informatik und eine für Theoretische Informatik~--, die Herrn Wolf nun befragt, wie er für mehr Rechenpower sorgen könne. Dr.\ Naehle, die Referentin für Technische Informatik, antwortet: »Die Sache ist ganz klar: Wir benötigen jede Menge fpgas, die wir mit Klimasimulationsschaltungen flashen. Die schalten wir dann zusammen und bauen damit eine riesige dedizierte Klimasimulationsmaschine.« Dr.\ Fischerin, zuständig für die Praktische Informatiker, meint: »Das geht viel billiger und einfacher mit Standardkomponenten. Die könnten wir bei Ebay besorgen (wenn die Beschaffungsstelle mitspielt). Dann brauchen wir noch ein paar flotte Netzwerkswitches und mein geliebtes obscure-bsd auf allen Rechnern. Das ergibt dann einen schönen Supercomputer.« Dr.\ Meischnuck, die Theoretikerin, hat den zweifelsohne billigsten Vorschlag: »Wir schauen erstmal, was überhaupt das Problem ist. Bei diesen Klimasimulationen geht es doch meist um Differentialgleichungen. Die bestehenden Simulationsprogramm benutzen vielleicht noch stures Iterieren zur Lösung. Da greifen wir doch mal in die algorithmische Trickkiste, da findet sich sicherlich etwas Schnelleres.« Wenn Herr Wolf alle drei gewähren lassen würde, wer würde am Ende am schnellsten die gewünschten Simulationsergebnisse berechnen? In diesem Kapitel geht es um darum, Werkzeuge zu entwickeln, um solche Fragen sinnvoll zu beantworten. Es geht um eine »Theorie der Geschwindigkeit von Algorithmen«. Ziele dieser Theorie sind: \begin{enumerate} \item Sie soll möglichst unabhängig von konkreter Hardware sein. Dass ein Supercomputer Zahlen schneller sortiert als Ihr Telefon, ist wenig überraschend und sagt auch wenig darüber aus, wie schnell ein bestimmter Algorithmus ist. \item Sie soll ein allgemeines Bild von der Geschwindigkeit eines Algorithmus liefern. Dass ein Algorithmus auf bestimmten Spezialfällen sehr gut funktioniert, ist zwar schön für den Algorithmus und man kann ihn dafür auch belobigen, jedoch sagt dies wiederum wenig über sein allgemeines Verhalten aus. \end{enumerate} Es hat sich herausgestellt, dass die $O$-Notation besonders geeignet ist, die Geschwindigkeit von Algorithmus zu beschreiben. Grob gesprochen bedeutet »der Algorithmus hat Laufzeit $O(n^2)$«, dass er bei Eingaben der Größe $n$ grob $n^2$ Rechenschritte benötigt unabhängig von der Hardware. Welche der Informatikerinnen am Ende die besten Ergebnisse präsentieren kann, ist übrigens alles andere als klar: Wenn die Theoretikerin tatsächlich einen Algorithmus findet in einer kleineren $O$-Klasse, so wird sie damit die Superrechner der anderen beiden locker um Größenordnungen schlagen. Andererseits kann es aber bei Theoretikern leicht passieren, dass sie auch nach Jahren noch in ihrem Büro über einen neuen Algorithmus brüten, während die Computer der Techniker und Praktiker das Problem schon längst zur allgemeinen Zufriedenheit gelöst haben.
Beginnen wir die Komplexitätstheorie doch mit einer Fabel für kleine Informatiker: \includegraphicscopyrightborder[width=2.25cm]{\bodydir/media/tux-pd-larry_ewing-simon_budig-anja_gerwinski.jpg}{Authors Larry Ewing, Simon Budig, Anja Gerwinski, public domain}% \includegraphicscopyrightborder[width=2.25cm]{\bodydir/media/Puffy-cca-unknown.jpg}{Unknown author, Creative Commons Attribution Lizenz}% \includegraphicscopyrightborder[width=2.25cm]{\bodydir/media/Konqi-lgpl.jpg}{Unknown author, Lesser gnu Public Licence}% \includegraphicscopyrightborder[width=2.5cm]{\bodydir/media/beaver-pd-pearson-scott-foresman.jpg}{Pearson Foresman, public domain}% \begin{quotation} Eines Tages, als Tux der Pinguin gerade auf dem Weg von seiner Lieblingseisscholle nach Hause war mit einem leckeren Hering unter der Flosse, traf er unterwegs zwei seiner Freunde: Puffy den Kugelfisch und Konqi den Drachen. »Hallo,« sagte Tux, »wie geht es euren Usern?« »Gut,« antworteten die beiden, »aber unsere User haben uns jedem ein Problem gegeben, bei dem du uns bitte helfen musst.« Die beiden reichten Tux ein Blatt Papier, das ganz vollgeschrieben war mit Zahlen wie $19$, $0$, $50$, $42$, $123$, $78$ und noch viele mehr. Puffy sagte nun: »Tux, ich soll möglichst wenige dieser Zahlen auswählen, so dass die Summe mindestens $1000$ ergibt.« Tux dachte kurz nach und antwortete: »Na, das ist doch ganz einfach: Du sortierst die Zahlen und nimmst dann so lange die größten, bis du 1000 zusammenhast.« Nun zeigte Konqi sein Blatt und sagte: »Tux, ich soll auch möglichst wenige dieser Zahlen auswählen, aber die Summe soll genau $1000$ ergeben.« Tux rieb sich mit seiner Flosse den Kopf, dachte etwas nach und meinte dann: »Das scheint mir schwieriger zu sein. Da fragen wir doch mal den Biber.« So gingen sie zum Biber, der sich das Problem von Konqi anschaute und dann verkündete: »Das ist ein Problem nach meinem Geschmack! Mit etwas Fleiß kann man doch einfach alle Möglichkeiten durchprobieren, wie die Zahlen auszusuchen sind. Und mit Fleiß kenne ich mich ziemlich gut aus.« So machte sich der Biber daran, alle Möglichkeiten durchzuprobieren. Und wenn er nicht gestorben ist, so probiert er noch heute. \end{quotation} Die Moral hiervon ist (jede Fabel hat ja eine Moral): Niemand weiß, wie man Konqis Problem effizient löst. Die Probleme von Puffy und Konqi sehen fast identisch aus, haben aber -- nach allem was man weiß -- sehr unterschiedliche Komplexität. In diesem Kapitel wird damit begonnen, eine Theorie des »Messens von Komplexität« zu entwickeln. Das wesentliche Ziel dieser Theorie ist zu verstehen, was Puffys Problem einfach macht, Konqis hingegen schwierig.
Das Halteproblem ist eigentlich ganz einfach: Es ist die Frage, ob ein in Form seines Programmtextes gegebenes Computerprogramm bei einer bestimmten Eingabe anhält oder eben nicht. Diese Frage ist sicherlich vom praktischen Standpunkt aus nicht ganz unwichtig -- es wäre doch ausgesprochen schön, wenn man nur noch Programme ausliefern würde, die auf typischen Eingaben immer anhalten. In diesem Kapitel wird gezeigt werden, dass das Halteproblem nicht entscheidbar ist. Es kann also gar keinen Algorithmus geben, der beliebige andere Programme analysiert und uns dann immer korrekt sagt, ob diese anhalten oder nicht. Man beachte allerdings, dass man sehr wohl für einzelne konkrete Programm beweisen kann, dass sie bei bestimmten Eingaben anhalten. Man kann dies sogar in Teilen automatisieren -- nur eben nicht völlig allgemein. Die Unentscheidbarkeit des Halteproblems liegt nicht etwa daran, dass man in der Praxis nicht genügend Rechenzeit oder Speicherplatz hat, um das Problem zu lösen. Es geht prinzipiell nicht, egal wie viel Zeit Sie Ihrem Programm genehmigen. Der Beweis der Unentscheidbarkeit funktioniert in etwa wie folgt: Zunächst beweist man, dass eine bestimmte, reichlich künstlich wirkende Sprache unentscheidbar ist. Dann wird diese Sprache »umgeformt« zu immer neuen Sprachen, die dem Halteproblem immer »ähnlicher« werden. Dabei zeigen wir, dass über alle Umformungen hinweg die entstehenden Sprachen immer unentscheidbar bleiben. Bei diesem »Umformen von Problemen« bekommt man leicht einen Knoten im Gehirn. Die Argumente gehen nämlich so: Um zu zeigen, dass ein Problem unentscheidbar ist, nimmt man an, es gebe doch einen Entscheider für das Problem. Diesen benutzt man, um einen neuen Entscheider für ein anderes Problem zu bauen, von dem man schon weiß, dass es keinen Entscheider für das Problem gibt. Man baut also mit viel Liebe etwas, von dem man schon weiß, dass es dieses Ding gar nicht gibt. Bei diesen Argumenten kann es schnell passieren, dass man gar nicht mehr weiß, was man eigentlich weiß. In diesem Fall hilft es, sich mit Sokrates' Ausspruch »Ich weiß, dass ich nichts weiß« zu trösten.
Dieses Kapitel beinhaltet eine gute und eine schlechte Nachricht. Zunächst die schlechte Nachricht: Alle semantischen Eigenschaften von Programmen sind unentscheidbar. Jetzt die gute: Jedes Programm kann auf seinen eigenen Programmtext zugreifen. Im Laufe dieses Kapitels soll es hauptsächlich darum gehen, diese beiden Nachrichten zunächst überhaupt verständlich zu machen, sie dann mathematisch etwas exakter zu fassen, ihnen schöne Namen zu geben und sie schließlich auch zu beweisen.
In den letzten Kapiteln wurden eine ganze Reihe doch sehr heterogener Konzepte eingeführt: nichtdeterministische Turingmaschinen, Register-Maschinen, minimalistische imperative Programmiersprachen und funktionale Programmiersprachen. Ob Sie diese Konzepte spannend finden, müssen Sie selber wissen -- gähnend langweilig waren auf jeden Fall die Sätze, die wir über Mächtigkeit dieser Modelle bewiesen haben: Alle Kapitel endeten mit dem Satz »Mit dem neuen Modell können wir genau so viel berechnen wie mit deterministischen Turing-Maschinen.« (Natürlich schreibt diesen Satz mathematisch etwas hübscher und unverständlicher auf, die Aussage ist aber genau diese.) Liegt diese Ohnmacht der Modelle an der raffinierten, ja gar perfiden Auswahl der Maschinenmodell durch den Autor? Hat er vielleicht die logischen Programmiersprachen genau deshalb mit keiner Silbe erwähnt, weil bei diese viel mehr zu holen ist? Maschinenmodelle aus prähistorischer Zeit (nach Informatiker-Zeitrechnung, also älter als fünf Jahre) werden ausführlich behandelt, moderne Quanten-Computern hingegen wie zufällig unter den Teppich gekehrt -- was hat der Autor zu verschweigen? Wieso werden dna-Computer mit Nichtachtung gestraft? Wie bei den meisten Verschwörungstheorien ist die Wahrheit eher profan: Man könnte die Liste der Modelle und Programmiersprachen noch über viele, viele weitere Vorlesungen verlängern -- man würde kein neues Maschinen- oder Programmiermodell finden, das mächtiger ist als die Turing-Maschine. Glauben Sie mir, viele Leute haben das versucht. Erinnern wir uns daran, wie Turing seine Turing-Maschine eingeführt hat: Ausgehend von der Beobachtung, was ein Mensch prinzipiell überhaupt jemals berechnen könnte, hat er ein Modell definiert, das genau diese von Menschen durchführbaren Berechnungen nachvollzieht. Folglich müsste ein Maschinenmodell, das mehr kann als die Turing-Maschine, auch mehr können, als Turings idealisierter Mathematiker. Die Church-Turing-These besagt deshalb, dass einfach alles, was man »intuitiv« berechnen kann, auch von einer Turingmaschine berechnet werden kann. Wie beweist man die Church-Turing-These? Gar nicht. Die These ist kein mathematischer Satz, sondern eine philosophische Aussage. Das Problem ist, dass der Begriff »intuitiv berechenbar« mathematisch nicht genau definiert ist -- und definiert man diesen, so landet man eben genau bei einem Modell wie der Turing-Maschine. Dann würde man also nur beweisen, dass Turing-Maschinen genau das können, was Turing-Maschinen können. Das erinnert stark an den Slogan »Was Friseure können, können nur Friseure« der Friseurinnung und ist auch in etwa von der gleichen intellektuellen Nahrhaftigkeit. Stimmt die Church-Turing-These? Es scheint so. Andererseits könnte man sich durchaus vorstellen, dass neuartige Computer wirklich mehr können als Turing-Maschinen. Beispielsweise könnte man sich zunächst berechtigte Hoffnungen machen, dass Quanten-Computer Berechnungen anstellen können, an die Turing nicht gedacht hat. Diese Hoffnung zerschlägt sich jedoch schnell -- Quantenrechner können auch nicht mehr als der pc von der Stange; sie sind nur schneller. Trotzdem bleibt die Church-Turing-These nur eine These. So könnten in exotischen Umgebungen (wie in der Nähe eines Schwarzen Loches) durchaus Berechnungen möglich sein, an denen eine Turing-Maschine scheitert.
Vielleicht haben Sie sich schon gewundert, weshalb in einer Vorlesung zur \alert{Theoretischen} Informatik ausgerechnet den Hardware-Modellen in den letzten Kapiteln so ein breiter Raum gegeben wurde. (Andererseits -- vielleicht wundert Sie bei dieser Vorlesung gar nichts mehr.) Die \alert{Software}, die schließlich bei der täglichen Arbeit der meisten Informatikerinnen und Informatiker von zentraler Bedeutung ist, wurde bis jetzt weitgehend außen vor gelassen mit Ausnahme einiger etwas konstruiert wirkender Übungsaufgaben. \includegraphicscopyrightborderautowidth{\bodydir/media/space-shuttle-ccas-.jpg}{Unknown autor, Creative Commons Attribution ShareAlike Lizenz}% \includegraphicscopyrightborderautowidth{\bodydir/media/piston-pd-Pearson-Scott-Foresman.png}{Autor Pearson Scott Foresman, public domain}% Es ist also höchste Zeit, sich der Software aus theoretischer Sicht anzunehmen. Dazu werden wir in diesem und dem nächsten Kapitel verschiedene Programmiersprachen betrachten, einmal aus dem Bereich der strukturierten-imperativen Programmierung und einmal aus dem Bereich der funktionalen Programmierung. Die logische und die objektorientierte Programmierung (und Hunde) müssen leider draußen bleiben -- hier sei auf die speziellen Vorlesungen wie »Logikprogrammierung« verwiesen. Die im Folgenden untersuchten Programmiersprachen sind natürlich einfach gehalten. Will man erstmal prinzipiell verstehen, was verschiedene Programmiersprachen können oder nicht können, so sollte man nicht gleich mit Java in seiner ganzen api-Pracht beginnen. Eine Vorlesung über Maschinenbau beginnt ja auch nicht mit der Funktionsweise eines Space-Shuttles sonder lieber mit der eines Kolbens. In diesem Kapitel werden zwei Sprachen eingeführt: Die Sprache »Loop« und die Sprache »While«. Beide sind sehr einfach und es stellt sich heraus, dass Loop zu einfach ist -- es gibt einige Funktionen, die nicht »Loop-berechenbar« sind, obwohl man sie eigentlich recht einfach ausrechnen kann. Die Programmiersprache While ist fast identisch zu Loop, es gibt nur eine minimale Erweiterung: Die While-Schleife. Diese Erweiterung hat es aber in sich: Wir werden zeigen, dass While-Programme genauso mächtig sind wie ram-Programme, von denen wir wiederum schon wissen, dass sie alle Turing-Maschinen simulieren können.
16. Programme II: Rekursion

16. Programme II: Rekursion

2009-12-2501:30:00

Kaum etwas wird unter Informatik-Studenten und Informatik-Professoren mit mehr Elan diskutiert als die Frage, ob man im ersten Semester eines Informatik-Studiums lieber mit funktionaler} oder doch lieber mit imperativer Programmierung beginnen sollte. Sollten Sie sich an solcher einer Debatten beteiligen wollen, hier ein paar Argumente für beide Seiten: \begin{block{Warum man mit funktionaler Programmierung beginnen sollte} \begin{itemize} \item Funktionale Sprache sind eleganter, es gibt weniger Sonderregeln und Sonderfälle. \item Funktionale Sprachen zeigen Grundideen wie die Rekursion viel klarer auf, was für Einsteiger sehr nützlich ist. \item Alle Studierende fangen auf einem ähnlichen Wissensstand an. \item Es gibt keine Zeiger, wodurch eine große Verständnisschwierigkeit wegfällt. \item Funktionale Sprachen lassen sich prinzipiell viel besser optimieren. \end{itemize} \end{block} Für die imperativen Sprachen sprechen im Wesentlichen drei Gründe: \begin{block}{Warum man mit imperativer Programmierung beginnen sollte} \begin{itemize} \item Im Alltag der Software-Entwicklung werden imperative Sprachen eingesetzt. \item Im Alltag der Software-Entwicklung werden imperative Sprachen eingesetzt. \item Im Alltag der Software-Entwicklung werden imperative Sprachen eingesetzt. \end{itemize} \end{block} In diesem Kapitel werden nun gleich zwei funktionale Sprachen eingeführt mit den schönen Namen PrimitiveML und MüML (das »ML« steht für die Programmiersprache gleichen Namens, von der die Syntax dieser Sprachen geborgt wurde). Ähnlich wie im vorherigen Kapitel sind dies keine »ausgebauten« funktionalen Programmiersprachen; wieder geht es nur darum, einen möglichst minimalistischen Kern zu definieren, auf dem man dann jede Menge Syntactic-Sugar streuen kann, um daraus eine vernünftige Programmiersprache zu machen. Eine der wesentlichen Eigenschaften einer funktionalen Sprache ist, dass sie Rekursion unterstützt. Genaugenommen wird in der funktionalen Programmierung Rekursion für einfach alles genutzt, selbst da, wo es nun wirklich keinen Sinn ergibt. Dies mag einer der Hauptgründe sein, weshalb manche Leute allergisch auf solche Sprachen reagieren. Die beiden Sprachen PrimitiveML und MüML unterscheiden sich lediglich dadurch, welche Arten von Rekursion erlaubt sind. Zwei imperative Sprachen, zwei funktionale Sprachen, zwei Kapitel. In der Special Theory Edition} von \emph{Matrix Reloaded kommentiert Morpheus dies wie folgt: »I do not believe in chance when I see two chapters, two imperative language, two funtional languages. I do not see coincidence, I see providence, I see purpose. I believe it is our fate to be here. It is our destiny. I believe this chapter holds for each and every one of us the very meaning of our lives.« Wie wir sehen werden, hat er mit »I see providence« recht: Die imperative Sprache Loop ist genau gleichmächtig zur funktionalen Sprache PrimitiveML, die Sprache While hingegen zu MüML. Ob allerdings seinem letzten Satz zuzustimmen ist, bleibt abzuwarten.
Wie schon im Vorwort ausführlich dargelegt, versucht sich die Theoretischen Informatik sich mit den alltäglichen Problemen der Praktischen oder gar Technischen Informatiker möglichst nicht herumzuschlagen. Wir betrachten Modelle, bei denen es solche Absonderlichkeiten wie a20-Gates oder Non-Maskable-Interrupts einfach nicht gibt. Ihre Kulmination hat eine wahrhafte Abstraktionsorgie in der Turing-Maschine gefunden -- ausgehend von der Vorstellung eines realen Menschen (was ein, mit Verlaub, noch viel komplexeres Wesen ist als ein Computer) ist man bei einem Modell angekommen, das doch in seiner Abstraktheit und Einfachheit kaum noch zu unterbieten ist. (Tatsächlich geht es auch noch einfacher, aber dazu in einem späteren Kapitel.) Es ist nun an der Zeit, wieder etwas zurückzurudern. Treibt man es nämlich mit der Abstraktion zu weit, dann besteht die Gefahr, dass die bewiesenen Sätze einem in der Praxis nur bedingt weiterhelfen -- die realen Probleme sind wegabstrahiert worden. So werden in der Theoretischen Informatik beispielsweise mit viel Liebe Algorithmen sehr gründlich und detailliert untersucht, deren Laufzeit so katastrophal hoch ist, dass sie schon bei der Eingabelänge $1$ (in Worten: Eins) auf einem Quanten-Computer so groß wie das Universum so lange rechnen würden, wie das Universum alt ist. In diesem Kapitel geht es um ein Maschinen-Modell, das wesentlich näher am realen Computer ist als die Turing-Maschine. \alert{Register-Maschinen} verfügen über einen Speicher, der ähnlich einem realen Computerspeicher aus einzelnen Zellen aufgebaut ist, die Zahlen speichern. Weiterhin verfügen Register-Maschinen über einen Befehlssatz, der sehr dicht dran ist an dem typischer Prozessoren. Natürlich liegt auch Register-Maschinen noch ein gewisser Grad an Abstraktion zu Grunde. So kann man -- völlig unrealistischer Weise -- beliebig große Zahlen in jeder Speicherzelle speichern. Weiterhin ist der Befehlssatz noch etwas minimalistisch verglichen mit einem Pentium-Prozessor. Die entscheidende Frage ist nun, wie sich das etwas realistischere Modell von Computern zum Modell der Turing-Maschine verhält. Hier werden wir einen sehr befriedigenden Satz beweisen: Diese beiden Modelle sind genau gleich mächtig -- was Turing-Maschinen können, können auch Register-Maschinen; und umgekehrt. Die vom praktischen Standpunkt aus recht nützliche Konsequenz aus diesem Satz ist, dass wir \alert{es uns immer wieder neu aussuchen können,} ob wir lieber mit Turing-Maschinen oder mit Register-Maschinen arbeiten wollen. Will man theoretische Resultate beweisen, so sind Turing-Maschinen oft einfacher zu handhaben; will man praktische Algorithmen untersuchen, so sind Register-Maschinen besser. Sie haben die Wahl.
Wozu wurden in Kapitel~\ref{lecture-nfa} eigentlich nichtdeterministische endliche Automaten eingeführt? Wenn man sich das Kapitel nochmal durchliest, so wird dort wortreich erklärt, dass Nichtdeterminismus ganz toll und wichtig sei -- ein wirklich schlagendes Beispiel sucht man vergebens. Schlimmer noch: Es wird gleich gezeigt, dass man den Nichtdeterminismus gar nicht braucht, da er auch nicht mehr kann als die deterministischen Automaten! Er sieht verdächtig nach »Viel Lärm um nichts« aus; einem Thema, dem sich Shakespeare zwar ausführlich widmete, das deshalb aber für ein nichtgeisteswissenschaftliches Studium zunächst von geringer Relevanz erscheint. Man muss schon weiterlesen, um die Wichtigkeit von nichtdeterministischen endlichen Automaten zu entdecken: Erst im Beweis von Satz~\ref{thm-reg-char}, und dort auch nur im Kleingedruckten, werden diese Automaten gebraucht, um Ableitungen mittels regulärer Grammatiken durch endliche Automaten nachzuvollziehen. Mit anderen Worten: Das ganzes Konzept des nichtdeterministischen Automaten wurde eigentlich nur eingeführt, um ein Detailproblem in einem Beweis eines bestimmten Satzes zu lösen. Sollten Sie zu den Lesern gehören, denen Beweise sowieso unsympathisch sind, so werden Sie sicherlich spätestens jetzt dem Konzept des Nichtdeterminismus die Daseinsberechtigung absprechen. In diesem Kapitel geht es nun um nichtdeterministische Turing-Maschinen. Dabei liegt die Sachlage ähnlich wie bei endlichen Automaten: So richtig brauchen tut man diese Maschinen praktisch nicht. Wieder sind sie aber aus Sicht der Theorie sehr wichtig -- wodurch sie in einer Veranstaltung mit dem Titel »Theoretische Informatik« wohl doch eine Daseinsberechtigung haben.
Der britische Premierminister Gordon Brown verkündete im September 2009: \begin{quotation} So on behalf of the British government, and all those who live freely thanks to Alan's work I am very proud to say: we're sorry, you deserved so much better. \end{quotation} Den Grund für diese Entschuldigung ergibt sich aus der Biographie, von der die englische Wikipedia folgendes zu berichten weiß: \begin{quotation} \hfill Aus en.wikipedia.org/wiki/Alan\_Turing}, 2009 Alan Mathison Turing, [\dots] (\textsc{23 June 1912 – \textsc7 June 1954), was an English mathematician, logician, cryptanalyst, and computer scientist. He was influential in the development of computer science and providing a formalisation of the concept of the algorithm and computation with the Turing machine. [\dots] His Turing test was a significant and characteristically provocative contribution to the debate regarding artificial intelligence. During the Second World War, Turing worked for the Government Code and Cypher School at Bletchley Park, Britain's codebreaking centre. For a time he was head of Hut~8, the section responsible for German naval cryptanalysis. He devised a number of techniques for breaking German ciphers, including the method of the bombe, an electromechanical machine that could find settings for the Enigma machine. After the war he worked at the National Physical Laboratory, where he created one of the first designs for a stored-program computer, the ace. Towards the end of his life Turing became interested in chemistry. He wrote a paper on the chemical basis of morphogenesis, and he predicted oscillating chemical reactions such as the Belousov–Zhabotinsky reaction, which were first observed in the 1960s. Turing's homosexuality resulted in a criminal prosecution in 1952 -- homosexual acts were illegal in the United Kingdom at that time -- and he accepted treatment with female hormones, chemical castration, as an alternative to prison. He died in 1954, several weeks before his 42nd birthday, from an apparently self-administered cyanide poisoning, although his mother (and some others) considered his death to be accidental. \end{quotation}
Die Sprachen, die von Grammatiken, deren Regeln immer genau ein Nonterminal auf der Seite, die links von dem Pfeil, der zwischen den Worten, die die Regelseiten gemäß der Sprachregelung, die in dem Skript, das Sie gerade, wo auch immer der Ort, an den Sie diese Tätigkeit gerade ausüben, sein mag, lesen, genutzt wird, genannt werden, steht, haben, erzeugt werden, sind verschachtelt. \includegraphicscopyrightborderautowidth{\bodydir/media/Russian-Matroshka-ccas-Fanghong.jpg}{Autor Wikimedia-User Fanghong, Creative Commons Attribution Sharealike}% Menschen mögen vor Nebensatzkaskaden kapitulieren -- Computer hingegen halten sie für eine völlig normale Art der Kommunikation. Sätze, die jeder Lektor einem Schriftsteller gnadenlos »geradeziehen« würde, werden von Computern genüsslich nach ihrer Struktur durchforstet und dies ist bei den wichtigsten Texten, die Computer so lesen müssen, auch bitter nötig: Programmtexte} sind in ascii gegossene Nebensatzkathedralen. Damit Menschen überhaupt eine kleine Chance haben, der Struktur eines Programmtextes zu folgen, hat sich als sehr nützlich herausgestellt, mit Einrückungen zu arbeiten, um anzudeuten, was wozu gehört. Bekanntermaßen sind dem Compiler Ihre Einrückungen ziemlich schnurz, für ihn ist \begin{lstlisting[language=Java] \end{lstlisting} ein völlig klares Programm. Ihnen auch? Sprachen mit verschachtelten Worten begegnen einem in der Informatik auf Schritt und Tritt. Wie schon erwähnt sind Programmtexte hochgradig verschachtelt, jedoch auch html-Texte und auch allgemein xml-Texte, \LaTeX-Dokumente und viele mehr. In diesem und dem folgenden Kapitel soll es um Grammatiken gehen, mit denen wir diese Art von Sprachen gut beschreiben können: kontextfreie Grammatiken. Die erste wichtige Erkenntnis betreffend kontextfreie Grammatiken ist, dass sie tatsächlich mehr können als reguläre Grammatiken. Dies ist nach den vorherigen Kapiteln ja eine eher erfrischende Feststellung, konnte man doch vielleicht den Eindruck gewinnen, Hauptaufgabe der Theoretischen Informatik sei es, möglichst viele unterschiedliche Methoden zu ersinnen, reguläre Sprachen zu beschreiben. Ähnlich wie bei der Untersuchung von regulären Sprachen werden wir zunächst untersuchen, was kontextfreie Sprachen so alles können oder nicht können. Wie man die deskriptiven Fähigkeiten kontextfreier Grammatiken praktisch einsetzen soll sei hier erstmal zweitrangig. Im nächsten Kapitel geht es dann genau um die praktische Frage, wie man einen Parser für kontextfreie Sprachen bauen kann.
Nachdem im letzten Kapitel die prinzipielle Fähigkeiten, aber auch die prinzipiellen Grenzen von kontextfreien Sprachen ausgelotet wurden, soll es nun um die praktische Frage gehen, wie das Parsing für kontextfreie Sprache funktioniert. Die erste beruhigende Erkenntnis ist, dass es überhaupt prinzipiell möglich ist, jede beliebige kontextfreie Sprache zu parsen. Tatsächlich ist die Lage noch um einiges erfreulicher: Mit Hilfe des cyk-Algorithmus lässt sich sogar vergleichsweise effizient überprüfen, ob und wenn ja wie sich ein Wort aus einer gegebenen kontextfreien Grammatik erzeugen lässt. \includegraphicscopyrightborderautowidth{\bodydir/media/Grapevinesnail-ccasa-Juergen_Schoner.jpg}{Bild von Jürgen Schoner, Creative Commons Attribution Sharealike Lizenz}% Leider ist der cyk-Algorithmus nur »vergleichsweise« effizient. Es kommt darauf an, womit man ihn vergleicht. Im Vergleich zu Algorithmen für die Travelling Salesperson Problem oder für $\Lang{sat}$ ist der cyk-Algorithmus tatsächlich rasend schnell. Verglichen mit einem Such- oder Sortiertalgorithmus hingegen gleicht der cyk-Algorithmus eher ein Weinbergschnecke: Er hat eine Laufzeit von grob $O(n^3)$ bei Wörtern der Länge~$n$. Ist nun das »Wort«, das vom Parser verarbeitet werden soll, ein Programmtext, so kann $n$ schnell mal in der Größenordnung einer Million liegen. In solch einem Fall ist $O(n^3)$ leider keine akzeptable Laufzeit mehr; niemand möchte ca. 30 Jahre darauf warten, dass ein Compiler die Meldung »Semicolon expected in line 123456« produziert (eine, nebenbei bemerkt, besonders perfide Fehlermeldung, da der Compiler ja ganz genau weiß, was das Problem ist, und auch, wie man es lösen könnte -- er tut es nur einfach nicht). Eine zweite Idee ist, eine ähnliche Methodik wie bei regulären Grammatiken anzuwenden. Dort haben wir auch ausgehend von den Grammatiken ein Maschinenmodell entworfen (die endlichen Automaten), mit deren Hilfe wir dann die regulären Sprachen sehr effizient verarbeiten konnten. Zu den kontextfreien Grammatiken gibt es auch ein passendes Maschinenmodell: Kellerautomaten (englisch Push-Down-Automata, pdas). Genau wie normale Automaten verarbeiten auch pdas ihre Eingaben sehr effizient. Allerdings passt das Modell auch nicht perfekt: Nicht je kontextfreie Sprache lässt sich von einem deterministischen pda akzeptieren. Es ist deshalb in der Praxis recht wichtig, sich auf solche Grammatiken zu beschränken, wo dies dann doch möglich ist.
9. Nerode-Klassen

9. Nerode-Klassen

2009-11-2757:37

\includegraphicscopyrightborderautowidth{\bodydir/media/Muelltonnen_diverse-ccas-unknown.JPG}{Unbekannter Autor, Creative Commons Attribution Sharealike Lizenz} Wahrscheinlich tut man den Nerode-Klassen Unrecht, sie aus didaktischen Gründen im Rahmen der Mülltrennung einzuführen. Dabei ist es für die Lektüre vorteilhaft, aber auch nicht unbedingt nötig, das in Deutschland zur Obsession gesteigerte Verlangen zu verspüren, Müll nach allen nur denkbaren Kriterien zu trennen und zu sortieren. Es wird Ihnen in diesem Fall ganz natürlich vorkommen, auch Worte nach reichlich merkwürdigen Kriterien zu sortieren und in Worttonnen einzukippen. Der Begriff der »Worttonnen« hat sich in der Theorie allerdings (noch?) nicht wirklich durchgesetzt, hätte jedoch gegenüber der sperrigen, aber mathematisch korrekten Bezeichnung »maximale Rechtsäquivalenzklasse« durchaus Chancen. Tatsächlich nennt man Worttonnen vornehm »Nerode-Klassen«. Was bringt es nun, Worte in Klassen zu trennen -- egal ob sie nun Worttonnen oder Nerode-Klassen heißen? Eine ganze Menge, wie sich herausstellt: \begin{enumerate} \item Aus den Nerode-Klassen einer regulären Sprache lässt sich leicht ein endlicher Automat bauen. \item Dieser Automat ist auch gleich noch der kleinste überhaupt mögliche Automat für die Klasse. \item Ist die Anzahl der Nerode-Klassen einer Sprache unendlich, so kann die Sprache nicht regulär sein. \end{enumerate} Insbesondere der letzte Punkte dürfte alle Leser erfreuen, die mit dem Pumping-Lemma auf Kriegsfuß stehen, da es oft einfach ist zu zeigen, dass eine Sprache unendlich viele Nerode-Klassen hat.
8. Reguläre Ausdrücke

8. Reguläre Ausdrücke

2009-11-2701:46:36

Reguläre Grammatiken und endliche Automaten sind zwei Seiten derselben Münze: Grammatiken beschreiben Sprachen, Automaten akzeptieren sie. Heute soll der Münze noch eine dritte Seite hinzugefügt werden beziehungsweise, da dies offenbar bei Münzen zumindest im dreidimensionalen Raum nicht möglich ist, auf eine der Seiten noch ein weiteres Konzept zusätzlich eingraviert werden: reguläre Ausdrücke. Diese sind, genau wie reguläre Grammatiken, ein Beschreibungsformalismus für reguläre Sprachen. Reguläre Ausdrücke können nicht mehr und nicht weniger als reguläre Grammatiken, sie sind gleichmächtig. Jedoch sind reguläre Ausdrücke in aller Regel wesentlich kompakter als reguläre Grammatiken, gleichzeitig sind sie für Menschen wesentlich leichter zu erstellen -- wenn auch nicht unbedingt leichter zu lesen. Selbst ein Profi wird den regulären Ausdruck \verb="[.?!][]\"')]*\\($\\| $\\|\t\\| \\)[ \t\n]*"= nicht sofort verstehen. Dies ist laut Emacs-Handbuch »a simplified version of the regexp that Emacs uses, by default, to recognize the end of a sentence together with any whitespace that follows.« Auch wenn reguläre Ausdrücke fürchterlich komplex und unlesbar werden können, so ist die Grundidee doch recht einfach: Beginnend mit trivialen Sprachen (bestehend nur aus einem Wort) kann man durch bestimmte Operationen wie Vereinigung oder Verkettung neue Sprachen bauen. Ein regulärer Ausdruck gibt nun gerade an, wie und in welcher Reihenfolge die Operationen angewendet werden sollen. Die Hauptschwierigkeit bei regulären Ausdrücken ist auch nicht, dieses Konzept zu verstehen. Problematisch ist, dass sich keine einheitliche Syntax herausgebildet hat, wie man die Ausdrücke nun aufschreiben sollte. Hier kocht jeder sein eigenes Süppchen, welche wir nun alle auslöffeln müssen.
loading
Comments 
loading