Einführung in die Komplexitätstheorie Inhaltsverzeichnis 1. Einleitung 2 2. Berechnungsmodelle 3 3. Die Komplexitätsklassen P/NP 6 4. Entscheidungsprobleme, Optimierungsprobleme, Aufzählungsprobleme 10 5. NP-vollständige Probleme 11 6. Schlusswort 18 7. Quellenverzeichnis 19 Dieser Lehrtext befasst sich mit einem wichtigen Thema in der theoretischen Informatik, der Komplexitätstheorie. Der Text eignet sich als kurzer Einblick oder als Grundlage, wenn man sich damit tiefer beschäftigen will. Geschrieben wurde er als Ergänzung zur Unterrichtseinheit "Schwierige Probleme in der Informatik", die NP-Probleme durch Entdeckendes Lernen lehrt. Die Dokumente sind im Internet unter www.swisseduc.ch/informatik/ verfügbar.1. Einleitung Viele Probleme, denen man im Alltag begegnet, sind auch ein Thema der theoretischen Informatik. Ein Beispiel: Ein Sightseeing Bus möchte die Route einer Tour festlegen. Sie soll beim Bahnhof starten und enden und 10 Sehenswürdigkeiten einschliessen. Bei jeder Attraktion soll die Tour genau einmal vorbeikommen. Der Inhaber der Firma stellt sich folgende Fragen: Gibt es überhaupt solch eine Tour? Wieviele Touren mit diesen Eigenschaften existieren? Wie sehen die Routen aus? Dieses Problem ist in der theoretischen Informatik als Hamilton Cycle Problem bekannt. Meistens geht es aber in der theoretischen Informatik nicht darum, die Lösung eines bestimmten Problems zu berechnen. Sondern vielmehr analysiert man, unter welchen Bedingungen ein Problem überhaupt lösbar ist. Für lösbare Probleme entwickelt man Algorithmen und stellt sich die Frage nach der minimalen Anzahl Ausführungsschritten (Laufzeit). Wenn man die Laufzeiten verschiedener Probleme kennt, kann man diese in Gruppen einteilen. Solche Kategorien heissen in der theoretischen Informatik Komplexitätsklassen. Wir betrachten nachfolgend die zwei wichtigsten Klassen P und NP. Im abschliessenden Kapitel und als Krönung dieses Lehrtextes, werden einige Probleme beschrieben, die in der Klasse NP liegen. Wofür die Begriffe P und NP stehen und was sie bedeuten, wird später erklärt. Zuerst einmal lernen wir aber die Grundlagen der Komplexitätstheorie kennen. 2 2. Berechnungsmodelle Unter einem Berechnungsmodell kann man sich ein theoretisches Modell eines Computers vorstellen. Meistens geht man davon aus, dass es keine physikalischen Grenzen gibt. Das bedeutet unter anderem, dass unendlich viel Zeit und Speicherplatz vorhanden ist. Die theoretischen Informatiker haben viele verschiedene Modelle eingeführt. Ein einfaches Modell sind die Markov Algorithmen. Das berühmteste und wahrscheinlich auch bedeutendste ist die Turingmaschine. Beide Berechnungsmodelle sind universell. Das heisst sie sind in der Lage, alle lösbaren Probleme zu lösen. Mit einem universellen Berechnungsmodell kann man jedes andere universelle Berechnungsmodell simulieren. Dies ist ein berühmte These und wurde von Church und Turing eingeführt. Da die Turingmaschine benutzt wird, um die Klassenkomplexität bestimmen, werden wir uns auf dieses Modell konzentrieren. zu Turingmaschinen Der Mathematiker Alan Turing hat sich überlegt, wie ein Mensch vorgeht, wenn er eine mathematische Aufgabe löst. Normalerweise schreibt er dazu Symbole auf ein Blatt Papier, zum Beispiel ein Häuschenpapier. Mit der Hand bewegt er den Bleistift nach links und rechts. Das Gehirn steuert. Aus dieser Überlegung hat Turing die Turingmaschine entworfen. Eine Turingmaschine besteht aus drei Elementen. Zum einen aus einem Speicherband mit unendlich vielen Häuschen. In jedem dieser Häuschen kann ein Zeichen gespeichert werden. Dies entspricht dem Häuschenpapier. Auf dem Speicherband sitzt ein Schreib- und Lesekopf. Er kann sich schrittweise nach links oder rechts bewegen oder stehenbleiben. Im selben Schritt kann er ein Zeichen schreiben oder löschen. Dies entspricht dem Bleistift, den der Mensch mit der Hand führt. Als drittes Element enthält eine Turingmaschine ein Programm mit endlichen vielen Zuständen. Als Analogon beim Menschen taugt das Gehirn. Durch die verschiedenen Zustände im Programm wird das Verhalten der Turingmaschine gesteuert. 3 Nun kennen wir die Konfiguration einer solchen Maschine, aber wie funktioniert sie? Die Details sind hier nicht so wichtig, da wir die Turingmaschinen nur als Mittel zur Laufzeitanalyse kennenlernen. Aber hier trotzdem einen kleinen Einblick: Auf dem Band stehen zu Beginn gewisse Zeichen. Das Programm startet mit dem ersten Zustand. Der Zustand definiert, was der Schreib/-Lesekopf macht. Je nachdem, welches Zeichen der Lesekopf auf dem Band liest, springt man im Programm zu dem entsprechenden Zustand. Werden spezielle akzeptierende Zustände erreicht, stoppt die Turingmaschine. Dann enstprechen die Zeichen auf dem Band oder der erreichte Zustand der Ausgabe des Programms. Man unterscheidet zwischen deterministischen und nichtdeterministischen Turingmaschinen. Beide können dasselbe berechnen. Deterministische Turingmaschinen haben bei gleicher Eingabe immer nur eine Möglichkeit für einen Zustandsübergang im Programm. Das heisst, das Programm läuft bei gleicher Eingabe stets gleich ab. Nichtdeterministische Turingmaschinen haben mehrere Möglichkeiten für den Zustandsübergang. Die Maschine wählt einen der Übergänge. Man nimmt an, dass die Maschine gut raten kann und so meistens der richtige nächste Zustand erreicht wird. Dieses Konzept mag auf den ersten Blick nicht einleuchten. Wichtig ist nur, dass man im Kopf behält, dass es zwei Arten von Turingmaschinen gibt. Das faszinierende an einer Turingmaschine ist, dass sie mit den drei Operationen lesen, schreiben und Kopf bewegen jedes lösbare Problem lösen kann. Sämtliche mathematischen Grundoperationen wie Addition und Multiplikation lassen sich damit simulieren. Durch Kombination der Grundoperationen kann man alle mathematischen Funktionen erzeugen. Als Beispiel betrachten wir eine Turingmaschine, die für eine beliebige Zeichenkette feststellt, ob sie aus n Nullen gefolgt von n Einsen besteht. Die Zeichenkette auf dem Speicherband sei auf beiden Seiten durch ein Doppelkreuz begrenzt. Der Lesekopf steht zu Beginn auf dem Doppelkreuz links. Falls die Eingabe korrekt ist, soll die Turingmaschine mit einem leeren Speicherband terminieren, ansonsten soll sie mit einem nicht-leeren Band stoppen. Ein Beispiel einer (ungültigen) Zeichenkette: #000011101# Wie können wir dieses Problem nun mit einer Turingmaschine lösen? Da die Turingmaschine keinen internen Speicher hat, um die Zeichen zu zählen, ist es am Einfachsten, die Nullen und Einsen jeweils paarweise zu löschen. Dies erreichen wir mit vier Zuständen. 4 1. Gehe ganz nach links 2. Lese und lösche Zeichen (stoppe falls keine Null) 3. Gehe ganz nach rechts 4. Lese und lösche Zeichen (stoppe falls keine Eins) Diese vier Schritte werden so lange ausgeführt, bis ein Fehler gefunden wird oder bis alle Zeichen gelöscht worden sind. Wird im vierten Schritt keine Eins gelesen, schreibt die Maschine ein Doppelkreuz. Ansonsten würde sie bei Zeichenketten, die genau eine Null zuviel haben, auf dem leeren Band terminieren. Dieses Beispiel wurde der Software TuringKara entnommen. Das Programm bietet einen spielerischen Einstieg in Turingmaschinen. Die Software ist unter www.swisseduc.ch/informatik/karatojava/turingkara/ erhätlich. 5 3. Die Komplexitätsklassen P/NP Unter Komplexität versteht man in der Informatik die Anzahl Rechenschritte, die ein Algorithmus für ein Problem im ungünstigsten Fall benötigt, um es zu lösen. Oft spricht man auch von der worst-case Laufzeit. Interessant wäre doch, wenn man die Probleme nach Laufzeit ordnen kann. Dann sind Überlegungen möglich wie "wenn ich Problem A in kurzer Zeit lösen kann und Problem B in derselben Klasse ist, dann kann ich Problem B auch in kurzer Zeit lösen". Zu diesem Zweck haben die theoretischen Informatiker Komplexitätsklassen definiert. Eine Komplexitätsklasse ist eine untere Laufzeit-Schranke für jeden Algorithmus, der darin vorkommt. Um die Klassenkomplexität zu bestimmen, wird die Turingmaschine hinzugezogen. Man zählt dazu die Anzahl der durchlaufenen Zustandsübergänge im Programm, bis die Turingmaschine für das jeweilige Problem hält. Die Schwierigkeit besteht darin, die Probleme in die Klassen einzuteilen. Ist die Einteilung aber erst einmal vorgenommen, ist das extrem praktisch. Denn Probleme, die in derselben Klasse liegen, können mit einem optimalen Algorithmus mit ungefähr demselben Rechenaufwand gelöst werden. Die Einteilung erfolgt entweder nach Zeitaufwand oder Speicherverbrauch. Wir ignorieren im folgenden den Speicherverbrauch und konzentrieren uns auf die Zeitkomplexität. Es existieren viele verschiedene Zeitkomplexitätsklassen. Wir beschränken uns auf die zwei wichtigsten, die Klassen P und NP. Die Klasse P In der P-Klasse befinden sich sequentielle Algorithmen, deren Anzahl Berechnungsschritte durch ein Polynom beschränkt wird. Ein Berechnungsschritt ist ein Zustandsübergang der deterministischen Turingmaschine, die den Algorithmus ausführt. Wir betrachten als Beispiel den Suchalgorithmus SelectionSort. Als Input haben wir n Zahlen in unsortierter Reihenfolge. SelectionSort wählt im unsortierten Bereich das Minimum und vertauscht es mit dem ersten Element dieses Teilbereichs. Dann wird SelectionSort auf den neuen unsortierten Teilbereich angewendet. Wir analysieren die Komplexität: Um anfangs das kleinste Element zu finden, sind n-1 Vergleiche notwendig. Im zweiten Schritt benötigt der Algorithmus n-2 Vergleiche. Insgesamt macht der Algorithmus (n-1)+(n-2)+...+2+1 = n(n-1)/2 = n^2/2 – n/2 Vergleiche plus eine konstante Anzahl Vertauschungen. Die Anzahl Rechenschritte sind also durch ein quadratisches Polynom begrenzt. Daher liegt SelectionSort in der 6 Komplexitätsklasse P. Fast alle Algorithmen, die in Einführungsvorlesungen der Informatik kennengelernt werden, liegen in dieser Klasse. So zum Beispiel Such- und Sortieralgorithmen (zum Beispiel binäre Suche und Quicksort). Die meisten Probleme in dieser Klasse sind einfach zu lösen. Einfach bedeutet ohne grossen Rechenaufwand. Natürlich ist das in der Praxis nur bedingt anwendbar für Probleme, die durch Polynome hohen Grades begrenzt sind. Die Klasse NP NP steht für nichtdeterministisches Polynom. In der Komplexitätsklasse NP liegen Probleme, die von einer nichtdeterministischen Turingmaschine in polynomialer Zeit entschieden werden können. Es existiert eine zweite Definition: In NP liegen alle Probleme, deren Lösung von einer deterministischen Turingmaschine überprüft werden kann. Zum besseren Verständnis werde ich dies umformulieren. Probleme, deren Lösung von einer nichtdeterministischen Turingmaschine in polynomialer Zeit berechnet werden können, liegen in der Klasse NP. Die zweite Definition ist etwas einfacher zu verstehen und benötigt keine nichtdeterminischte Turingmaschine. Falls wir eine mögliche Lösung eines Problems bereits kennen und sich eine solche Lösung von einer deterministischen Turingmaschine überprüfen lässt, dann liegt dieses Problem in NP. Für die Praxis hat dies eine grosse Bedeutung. Alle Probleme in NP lassen sich (wahrscheinlich) nicht effizient lösen. Je nach Datenmenge ist die Rechenzeit so gross, dass sie selbst mit den modernsten Computern nicht lösbar sind. Ein klassisches Beispiel für NP-Probleme sind Probleme, bei denen alle Möglichkeiten durchprobiert werden müssen, um eine Lösung zu finden. Ein konkretes Beispiel: Gegeben eine Menge von Ganzzahlen. Entscheide, ob eine Teilmenge dieser Menge aufaddiert exakt Null ergibt. Dieses Problem lässt sich lösen, indem man alle Teilmengen durchprobiert, bis man eine Lösung findet. Im schlechtesten Fall müssen 2^n Teilmengen getestet werden. Die Lösung lässt sich von einer deterministischen Turingmaschine überprüfen. 7 Problemreduktionen Im 3-SAT Problem geht es um die Erfüllbarkeit logischer Formeln in konjunktiver Normalform mit höchstens 3 Literalen pro Klausel. Wir kennen verschiedene Algorithmen und deren Laufzeit für das Problem. Es wurde bewiesen, dass das 3-SAT Problem in NP liegt. Nun wäre es doch praktisch, wenn wir neue Probleme unbekannter Komplexität mit 3-SAT vergleichen und damit neue Erkenntnisse gewinnen könnten. Diese Technik wird Problemreduktion genannt. Mit Problemreduktionen kann man die Komplexität zweier Probleme miteinander vergleichen. Angenommen ein Problem A kann zu einem Problem B reduziert werden. Problem B liegt in NP. Die Reduktion gibt uns dann Informationen über die Komplexität des unbekannten Problems. Aber welche Informationen und was genau bedeutet “reduzieren”? Also: Gegeben ein Problem A unbekannter Komplexität und ein Problem mit Lösungsalgorithmus B bekannter Komplexität. Mit einer Codierungsfunktion überführen wir den Input des Problems A in den Input des Problems B. Wir wenden den Algorithmus B auf diesen Input an. Eine Decodierungsfunktion wandelt den erhaltenen Output wieder zurück. Wir haben damit Problem A auf Problem B reduziert. Ist dies durchführbar und sind die Codierungsfunktionen polynomial, dann haben wir Problem A zu Problem B reduziert. Wir schliessen daraus, dass Problem A höchstens so schwierig ist wie Problem B. Wir können auch zeigen, dass Problem A mindestens so schwierig ist wie Problem B, indem wir Problem B auf Problem A reduzieren. Oft reduziert man Probleme zu 3-SAT. Das Graphenfärbbarkeitsproblem lässt sich zum Beispiel auf 3-SAT reduzieren. Wir betrachten jedoch ein einfacheres Beispiel. Wir wissen, dass “Element Uniqueness” (gegeben n Elemente, entscheide ob alle Elemente verschieden sind) mindestens nlogn benötigt. Daraus beweisen wir mittels Reduktion eine untere Schranke für Sortieren: Die Codierungsfunktion c macht nichts beziehungsweise verändert den Input nicht. Die Decodierungsfunktion d geht die sortierte Liste durch. Falls zwei benachbarte Werte gleich sind, ist die Antwort nein, ansonsten ja. Dies kann in linearer Zeit berechnet werden. Somit haben wir Element Uniqueness auf Sortieren reduziert und bewiesen, dass Sortieren ebenfalls mindestens nlogn benötigt. 8 NP-Vollständigkeit Die NP-vollständigen Probleme sind eine Untermenge der Probleme in NP. Ein Problem ist genau dann NP-vollständig wenn es in NP liegt und sich jedes Problem in NP auf dieses Problem polynomial reduzieren lässt. Meistens zeigt man die NP-Vollständigkeit eines Problems A, indem man ein NPvollständiges Problem auf A polynomial reduziert. NP-vollständige Probleme sind besonders schwierig zu lösen. Man nimmt an, dass es keine effizienten Algorithmen für solche Probleme gibt. Momentan benötigen die besten bekannten Algorithmen exponentielle Zeit in der Problemgrösse. Daher sind optimale Algorithmen nur für kleine Probleminstanzen in vernünftiger Zeit durchführbar. Oft greift man daher auf Approximationen und Heuristiken zurück. Approximationen liefern nicht immer die optimale Lösung, jedoch eine Lösung, die oft nur wenig von der optimalen Lösung entfernt ist. Gute Heuristiken liefern oftmals eine gute oder sogar die korrekte Lösung, können aber auch in Zustände geraten, in denen sie nie terminieren. NP-vollständige Probleme und konkrete Heuristiken werden im letzten Kapitel besprochen. Falls für ein NP-vollständiges Problem ein effizienter Algorithmus gefunden würde, dann wäre jedes NP-vollständige Problem effizient lösbar. Man nimmt an, dass keine effizienten Algorithmen existieren, konnte das aber noch nie beweisen. Dies führt uns zur nächsten Frage. Ist P = NP? Diese Frage beschäftigt zur Zeit viele Informatiker und ist eine der grossen ungelösten Fragen in der Informatik. Preise in Millionenhöhe wurden für die Lösung ausgesetzt. Wieso ist aber diese Frage so wichtig? Würde bewiesen werden, dass P = NP, also die beiden Komplexitätsklassen äquivalent sind, dann würde dies bedeuten, dass auch Probleme in NP effizient gelöst werden könnten. Dies hätte nicht nur positive Auswirkungen. Die Kryptographie nützt die Schwierigkeit von NP-vollständigen Problemen aus, um sichere Verschlüsselungssysteme zu entwerfen. Wären nun NP-Probleme effizient lösbar, würden alle solchen Verschlüsselungssysteme keine Sicherheit mehr bieten. Man nimmt an, dass P ungleich NP ist. Solange dies nicht bewiesen wird, kann es aber doch möglich sein, dass die Komplexitätsklassen äquivalent sind. Deshalb wird in vielen Beweisen als Bemerkung “falls PNP” vermerkt. 9 4. Entscheidungsprobleme, Optimierungsprobleme, Aufzählungsprobleme Wir haben in der Einleitung bereits das Hamilton Cycle Problem kennengelernt. Eine formalere Definition könnte so lauten: ein Hamilton Kreis in einem Graphen G=(V,E) ist ein Zyklus (Kreis), der alle Knoten in V genau einmal besucht. V entspricht der Knotenmenge, mit E bezeichnet man die Menge aller Kanten. Die Definition für das Cliqueproblem: eine Clique in einem Graphen G=(V,E) ist eine Untermenge V', so dass für alle Knoten u,v in V' gilt: (u,v) ist eine Kante in E. Vereinfacht gesagt ist eine Clique eine Untermenge an Knoten, so dass alle Knoten dieser Untermenge benachbart sind. Benachbart bedeutet durch eine Kante verbunden. Alle diese Probleme lassen sich weiter unterteilen. Als Optimierungsproblem, Entscheidungsproblem oder als Aufzählungsproblem. Entscheidungsprobleme Hat der Graph G einen Hamilton Kreis? Hat der Graph G eine Clique mit 5 Knoten? Die Antwort von Entscheidungsproblemen zu finden ist meistens schwierig. Einfacher ist es, eine positive Antwort auf ihre Richtigkeit zu prüfen. Optimierungsprobleme Finde die grösste Clique in einem Graphen. Die Antwort zu finden und sie zu überprüfen ist normalerweise schwierig. Wie finde ich heraus, dass eine Clique tatsächlich die Grösste ist? Trotzdem ist das überprüfen meistens einfacher als die Antwort zu finden. Aufzählungsprobleme Wieviele Cliquen oder Hamilton Kreise existieren in einem Graphen? Aufzählungsprobleme benötigen viel Zeit, sind aber meist einfach zu lösen. Man überprüft durch Backtracking alle möglichen Fälle und findet so die Lösung. Die Komplexitätstheorie befasst sich häufig mit Entscheidungsproblemen. In der Praxis sind oft Optimierungsprobleme von Interesse. Führe dir nochmals das Beispiel aus der Einleitung vor Augen. Der Reiseveranstalter sucht Routen für die Sightseeing-Tour. Dies entspricht einem Entscheidungsproblem. Falls er in einem zweiten Schritt die kürzeste Tour sucht, muss er das Optimierungsproblem lösen. 10 5. NP-vollständige Probleme Hamilton Kreis Ein Hamilton Kreis ist ein Pfad, der jeden Knoten genau einmal besucht. Existiert in einem Graphen ein Hamilton Kreis? Das Problem rund um diese Frage ist ein schwieriges Problem und NP-vollständig. Eine Stadt kann einfach als Graph betrachtet werden, indem man sich die Kreuzungen als Knoten und die Strassenabschnitte als Kanten vorstellt. Als Beispiel für das Hamilton Kreis Problem in solch einem Graphen haben wir bereits in der Einführung die Suche nach einer Tour des Reisebusses kennengelernt. Ein Hamilton Kreis lässt sich unter anderem mit Backtracking finden. Man beginnt bei einem Knoten und speichert die Pfade ausgehend von diesem Knoten zu unbesuchten Knoten. Für jeden Pfad speichert man neue Pfade ausgehend vom letzten Knoten zu unbesuchten Nachbarn. Diese Schritte führt man durch, bis ein Hamilton Kreis gefunden wird oder bis alle Pfade besucht wurden. Wir können das Problem sowohl für ungerichtete als auch für gerichtete Graphen lösen, d.h. wenn die Kanten eine Richtung haben. Eine gerichtete Kante kann man mit einer Einbahnstrasse vergleichen. Ungerichtete Graphen lassen sich auf gerichtete reduzieren. Eine wichtige Anwendung von Hamilton Kreisen treffen wir im nächsten Problem an. Travelling Salesman (Problem des Handelsreisenden) Das Travelling Salesman Problem ist verwandt mit dem Hamilton Kreis Problem. Den Kanten sind nun aber Gewichte zugewiesen. In der Entscheidungsvariante beantwortet man die Frage, ob in einem Graphen eine Tour existiert, die kürzer als k ist. Im Optimierungsproblem wird die kürzeste Travelling Salesman Tour gesucht. Eine Tour besucht jede Stadt genau einmal. Wahrscheinlich kennen viele Leute dieses Problem aus dem Alltag. Es modelliert das Problem, mehrere Orte nacheinander auf dem kürzesten Weg zu besuchen und zum Anfangsort zurückzukehren. Handelsreisende, die 11 verschiedene Orte besuchen und den kürzesten Weg gehen möchten, müssen dieses Problem lösen können, daher der Name. Auch beim Postbeamten, der die Zeitungen austrägt, tritt dieses Problem auf. Jedoch ist dies nicht das beste Beispiel. Wieso sollten Handelsreisende eine Stadt oder Postbeamte ein Haus nicht mehrmals besuchen, wenn sie dadurch Zeit sparen? Wir betrachten nun Algorithmen für das euklidische Travelling Salesman Problem. Im euklidischen TSP Problem entspricht das Gewicht einer Kante seiner Länge. Mit Backtracking lässt sich dieses Problem natürlich lösen, jedoch sehr ineffzient. Der Algorithmus wählt die erste Kante und kreiert neue Pfade, indem er Kanten zu den unbesuchten Kanten hinzufügt. Dies macht er solange, bis entweder alle Pfade eine Tour sind oder bis alle Pfade länger als die bereits gefundene kürzeste Tour ist. Dieses Problem fehlt in keinem Informatikunterricht und ist gut untersucht. Es existieren auch zahlreiche Heuristiken. Die Nearest Neighbour Heuristik beginnt beim ersten Knoten und wählt immer den nächsten (dessen Verbindungskante das kleinste Gewicht hat) unbesuchten Knoten bis kein Knoten mehr übrig bleibt. Das geht natürlich sehr schnell, ist aber dementsprechenend ungenau. Etwas besser schneidet die konvexe Hülle Heuristik ab. Sie erzeugt zuerst die konvexe Hülle aller Knoten. Dann wird der Knoten ausgewählt, welcher der Tour am nächsten liegt und hinzugefügt. Der Algorithmus terminiert, wenn alle Knoten Teil der Tour sind. Die Greedy Heuristik wählt die Kante mit dem kleinsten Gewicht und fügt sie zur Tour hinzu, sofern die Kante keinen Zyklus erzeugt, bis kein Knoten mehr übrig bleibt. Two-Optimization generiert zuerst eine zufällige Tour. Danach sucht sie zwei Kanten und tauscht deren Endknoten, falls dies die Tour verkleinert. Dies macht der Algorithmus, bis keine zwei solchen Kanten mehr gefunden werden. Nochmals zur Erinnerung: das Problem des Handlungsreisenden ist NPvollständig. Das bedeutet, es ist zwar lösbar, jedoch mit erheblichem Aufwand. Alle bekannten korrekten Algorithmen sind so langsam, dass sie schon bei Graphen mit 300 Knoten nicht mehr in nützlicher Zeit ein Resultat generieren. Ein Algorithmus, der alle Pfade durchsucht, müsste in einem 12 Graphen mit 300 Knoten ungefähr 300! (mein Taschenrechner kann nicht einmal mehr 300! berechnen) Touren durchlaufen. Deshalb braucht man die Heuristiken, die als Abschätzung oftmals genügen. Das Sightseeing Bus Unternehmen könnte damit die kürzeste Tour in der Stadt berechnen, um Benzin und Zeit zu sparen. Färbung von Graphen (Colorability) Eine beliebte Aufgabe im Geographieunterricht ist es, eine Landkarte mit verschiedenen Farbstiften so einzufärben, dass keine benachbarten Länder dieselbe Farbe aufweisen. Du fragst dich eventuell, wieviele verschiedene Buntstifte du dazu benötigst. Reichen 4 Farbstifte aus? Doch so einfach ist dieses Problem nicht zu lösen. Es handelt sich hierbei um einen Spezialfall des Graphfärbungsproblem. Jedem Knoten wird eine Farbe zugewiesen. Eine Färbung ist gültig, wenn keine benachbarten Knoten mit derselben Farbe eingefärbt wurden. Knoten werden benachbart genannt, wenn sie durch eine Kante direkt verbunden sind. gültige Färbung ungültige Färbung Ein Graph heisst k-färbbar, wenn er mit k Farben eingefärbt werden kann. Das kleinstmögliche k zu finden, ist ein NP-vollständiges Problem und daher aufwändig zu lösen. Auch dieses Problem lässt sich mit Backtracking lösen. Backtracking testet für ein gegebenes k alle möglichen Färbungen bis entweder eine Lösung gefunden wird oder alle Möglichkeiten erschöpft sind. Das minimale k lässt sich so finden: Man lässt den Algorithmus für k=1,2,... laufen, bis eine Färbung gefunden wird. Die Anzahl Ausführungsschritte ist bei einer Heuristik viel geringer. Jedoch kann nicht garantiert werden, dass das Resultat korrekt ist. Die Greedy Heuristik sucht den Knoten mit den meisten nicht zugewiesenen Kanten. Eine Kante ist nicht zugewiesen, falls einer seiner Knoten nicht eingefärbt ist. Nach dem Färben dieses Knotens fährt er bei seinen Nachbarn gleichermassen weiter. Der Algorithmus terminiert, wenn er eine gültige Färbung gefunden hat oder falls ein Knoten nicht eingefärbt werden kann. 13 Um auf das Landkartenbeispiel zurückzukommen: Du kannst Dir die Landkarte als Graphen vorstellen. Länder werden zu Knoten. Die Knoten benachbarter Länder werden durch eine Kante verbunden. Die gute Nachricht: Eine Landkarte ergibt einen planaren Graphen, das heisst die Kanten schneiden sich nur in den Knoten. Falls jedes Land aus nur einer zusammenhängenden Fläche besteht, benötigt die Färbung höchstens aus 4 Farben. Die schlechte Nachricht: Trotz dieser Einschränkung ist das Problem immer noch NP-vollständig . Im Alltag existieren weitere prominente Beispiele der Graphenfärbbarkeit, denen wir täglich begegnen: Bei einer Ampelschaltung dürfen gewisse Ampeln nicht gleichzeitig auf grün geschaltet werden. Bei der Stundenplanerstellung sind die Stunden, die nicht gleichzeitig stattfinden dürfen (gleiche Lehrer, Klasse, Zimmer) miteinander in Konflikt. Beide Probleme lassen sich auf das Graphenfärbbarkeitsproblem abbilden. Vertex Cover (Knotenüberdeckung) Ein Vertex Cover ist eine Menge an Knoten, die alle Kanten abdecken. Eine Kante ist abgedeckt, wenn mindestens einer seiner Knoten im Vertex Cover liegt. Die Grösse eines Vertex Cover ist die Anzahl seiner Knoten. Vertex Cover der Grösse 4 Auch dieses Graphenproblem lässt sich als ein Entscheidungs- und Optimierungsproblem formulieren. In der ersten Variante fragt man sich, ob ein gegebener Graph ein Vertex Cover der Grösse k besitzt. Im Optimierungsfall sucht man das minimale Vertex Cover, also die minimale Anzahl Knoten, so dass alle Kanten abgedeckt sind. Wie alle Probleme in diesem Kapitel ist auch dieses NP-vollständig. Wir betrachten einen Algorithmus für das Entscheidungsproblem. Gegeben ist ein Graph und man soll prüfen, ob alle Kanten mit 5 Knoten abgedeckt werden können. Backtracking findet in jedem Fall die richtige Lösung. Der Algorithmus erstellt eine Liste mit allen Knotenmengen der Grösse 5. Dann prüft er diese Knotenmengen darauf, ob sie ein gültiges Vertex Cover sind. Er terminiert, falls er eine Lösung gefunden hat oder alle Möglichkeiten getestet hat. 14 Für die Optimierungsvariante existiert eine Approximation, die ein Vertex Cover findet, das höchstens doppelt so gross wie das minimale ist. Der Algorithmus wählt zufällig eine Kante und fügt deren Knoten zum Vertex Cover hinzu. Dann werden die beiden Knoten und die angrenzenden Kanten gelöscht. Der Algorithmus beginnt von vorne bis die Knotenmenge ein gültiges Vertex Cover darstellt. Ein vielleicht etwas gesuchtes Anwendungsbeispiel von Vertex Cover ist die Positionierung von Überwachungskameras in einem Gebäude. Man will überall dort Kameras installieren, wo mehrere Gänge aufeinandertreffen. Aus Kostengründen ist man natürlich daran interessiert, mit möglichst wenigen Kameras auszukommen. Clique Unter einer Clique versteht ein Nichtinformatiker eine Gruppe Menschen, die miteinander befreundet sind. Also kennt in einer Clique jeder jeden. Diese Erkenntnis projizieren wir nun auf einen Graphen. Die Knoten entsprechen den Menschen. Eine Kante zwischen zwei Menschen bedeutet, dass sie sich kennen. Eine Clique in einem Graphen ist eine Knotenmenge, in der jeder Knoten zu jedem anderen Knoten benachbart ist. Es ist also ein vollständiger Subgraph des ganzen Graphen. Wir können auch sagen: in einer Clique kennt jeder Knoten jeden. Das Problem, die maximale Clique in einem Graphen zu finden, ist NP vollständig. Es existiert also wahrscheinlich kein effizienter Algorithmus, der die maximale Clique berechnet. Der Backtracking Algorithmus arbeitet ähnlich wie beim Vertex Cover Problem. Soll eine Clique der Grösse 3 gefunden werden, prüft er alle Knotenmengen der Grösse 3. Dies solange bis er eine Lösung findet oder alle Möglichkeiten erschöpft sind. Man könnte den Algorithmus folgendermassen verbessern: sucht man nach einer Clique der Grösse k, können alle Knoten gelöscht werden, die weniger als k-1 Nachbarn besitzen. Denn solche Knoten können niemals zu einer Clique der Grösse k gehören. Ebenfalls können dann deren Kanten gelöscht werden und eventuell weitere Knoten ignoriert werden. 15 Independent Set (Unabhängige Menge) In einer unabhängigen Menge sind keine zwei Knoten benachbart. In solch einer Menge gilt also für je zwei Knoten, dass sie nicht durch eine Kante verbunden sind. Dieses Problem ähnelt dem vorangehenden Cliquenproblem. Eine Clique in einem Graphen entspricht einer unabhängigen Menge in dem Komplementgraphen. Den Komplementgraphen erhält man, wenn man alle Kanten des ursprünglichen Graphen löscht und die zuvor unverbundenen Knoten miteinander verbindet. Dies führt uns gleich zu einer Lösung. Wir können ein maximales Independent Set finden, indem wir die Clique im Komplementgraphen berechnen. Daraus lässt sich auch schliessen, dass das Independent Set ebenfalls NP vollständig sein muss. Natürlich können wir die Lösung auch direkt finden. Soll eine unabhängige Menge der Grösse k berechnet werden, sucht Backtracking alle Untermengen der Grösse k nach einer unabhängigen Menge ab. Es gibt Prominente, die mögen sich nicht besonders. Dies muss zum Beispiel bei den Oscarverleihungen beachtet werden. Wie platziere ich die Schauspieler, Regisseure und Produzenten im Saal so, dass keine zwei zerstrittene Promis neben- oder hintereinander sitzen müssen? Der Organisator kann die Stühle als Knoten darstellen und jene Stühle durch eine Kante verbinden, die neben- oder hintereinander liegen. Mit einer unabhängigen Menge findet er Stühle, die zu keinem anderen Stuhl aus der unabhängigen Menge benachbart sind. Satisfiability (Erfüllbarkeit logischer Formeln) Gegeben ist eine logische Formel und gesucht ist eine Belegung der Variablen, so dass die Formel erfüllt ist. Häufig wird 3-SAT, eine Vereinfachung dieses Problems betrachtet. In 3-SAT sind die Formeln in konjunktiver Normalform mit höchstens 3 Literalen pro Klausel gegeben. Ein Beispiel einer solchen Formel ist (x1 x2 x3) (x1 x2 x3) (x2 x3). Eine Belegung, die diese Formel erfüllt ist x1=0, x2=1, x3=1. Cook hat bewiesen, dass das Erfüllbarkeitsproblem NP-vollständig ist. Diese Eigenschaft erleichtert die NP-Vollständigkeitsbeweise anderer Probleme. Durch die Reduktion auf Satisfiabilty in polynomialer Zeit kann man die NP16 Vollständigkeit eines Problems zeigen. Satisfiability ist also sozusagen die Mutter aller NP-vollständigen Probleme. Falls für Satisfiability ein effizienter Algorithmus gefunden würde, dann würden für alle NP-vollständigen Probleme effiziente Algorithmen existieren. Eine Belegung, welche die Formel erfüllt, kann man mittels Backtracking finden. Man setzt alle Variablen auf false und probiert alle Möglichkeiten durch, bis eine Belegung gefunden wird, welche die Formel erfüllt; vorausgesetzt eine solche Belegung existiert. Ein physikalisches Modell versucht die Formel mit physikalischen Kräften zu lösen. Dabei versucht jede Klausel ihre Literale in die Richtung zu ziehen, in der die Klausel erfüllt ist. Je näher die Literale am günstigen Wert liegen, desto stärker ist die ziehende Kraft. Beim 3-SAT Beispiel von oben versucht die zweite Klausel x2 nach 1 zu ziehen. X1 und x3 zieht er in Richtung 0. 17 6. Schlusswort Wie man sieht, ist die Komplexitätstheorie ein faszinierendes Gebiet. Auf den ersten Blick ist sie zwar sehr theoretisch, was abschrecken kann. Wenn man sich aber näher damit befasst, sieht man, dass viele Probleme alles andere als realitätsfremd sind. Wenn man sich näher damit befassen möchte, eignet sich folgendes: Als erstes die Software GraphBench, die sich mit NP-vollständigen Problemen befasst. Das Programm führt spielerisch in ausgewählte Probleme ein. Man kann damit alle in diesem Text erwähnten Probleme betrachten und deren Algorithmen visualisieren. Von derselben Gruppe sind auch Kara (Turingmaschinen) und Exorciser (endliche Automaten) entwickelt worden, die ich auch empfehlen kann. Das Buch “Das Affenpuzzle und weitere bad news aus der Computerwelt” von David Harel behandelt ähnliche Themen wie dieser Lehrtext, jedoch ausführlicher. Der Autor verzichtet auf unnötigen Formalismus, dadurch liest sich das Buch sehr flüssig. Weitere Informationen kann man den Quellenangaben entnehmen.7. Quellenverzeichnis Wikipedia (verschiedene Autoren): 2002-2004, http://de.wikipedia.org/wiki/Kategorie:Theoretische_Informatik Harel David:Das Affenpuzzle und weitere bad news aus der Computerwelt, 2002 (Springer). Nievergelt J.: Lecture Notes “Theory of computation”, 2002. GraphBench http://www.swisseduc.ch/informatik/graphbench/ TuringKara http:// www.swisseduc.ch/informatik/karatojava/turingkara/ 18