Wasser für die Wüste Praktischer Umgang mit Steiner Bäumen René Trumpp Matrikel Nr. 021980 Betreuer: Prof. Dr. habil. Thomas Thierauf Hochschule Aalen Studiengang Informatik Datum: 15. März 2006 Inhaltsverzeichnis 1 Einleitung 1.1 Wasser für die Wüste . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Grundbegriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Ziel und Abgrenzung . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 5 6 2 Minimal aufspannende Bäume 2.1 Problembeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Ein erster Lösungsansatz . . . . . . . . . . . . . . . . . . . . . . . 2.3 Ein “gieriger“ Lösungsansatz . . . . . . . . . . . . . . . . . . . . . 7 7 8 9 3 Steiner Problem in Graphen 3.1 Problembeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Lösungsansatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 12 4 Rechtwinkliges Steiner Problem 4.1 Problembeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Lösungsansatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 14 15 5 Geographisches Steiner Problem 5.1 Problembeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Lösungsansatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 16 16 6 NP-Vollständige Probleme 6.1 Deterministisch oder nicht ? . . . . . . . . . . . . 6.2 Was ist NP ? . . . . . . . . . . . . . . . . . . . . 6.3 Steiner Problem ist ein NP-Vollständiges Problem 6.4 Andere NP-Vollständige Probleme . . . . . . . . . 6.4.1 Problem des Handelsreisenden . . . . . . . 6.4.2 Rucksackproblem . . . . . . . . . . . . . . . . . . . . 17 17 17 18 20 20 20 7 Näherungsfunktionen 7.1 Sonderfälle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1 Keine Steinerknoten . . . . . . . . . . . . . . . . . . . . . 7.1.2 Nur zwei Terminale . . . . . . . . . . . . . . . . . . . . . . 22 22 22 22 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 René Trumpp 7.2 7.3 . . . . . . . . . 23 23 24 25 26 27 28 28 30 8 Zusammenfassung 8.1 Rückblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 31 9 Anhang 9.1 Implementierung . . . . . . . . . . . . . . . 9.1.1 Klassendiagramm . . . . . . . . . . . 9.1.2 Quelltexte . . . . . . . . . . . . . . . Graph Klasse . . . . . . . . . . . . . Node Klasse . . . . . . . . . . . . . . Minimal Spanning Tree Klasse . . . . Steiner Baum Trivial Klasse . . . . . Rechtwinkliger Steiner Graph Klasse Naeherung Ratio 2 Klasse . . . . . . Distance Graph Klasse . . . . . . . . 32 32 32 33 33 37 38 39 40 42 44 7.4 7.5 7.6 Qualitätskriterien für Näherungsfunktionen Näherungsfunktion mit Ratio 2 . . . . . . 7.3.1 Satz . . . . . . . . . . . . . . . . . 7.3.2 Beweis . . . . . . . . . . . . . . . . 7.3.3 Algorithmus . . . . . . . . . . . . . 7.3.4 Beispiel . . . . . . . . . . . . . . . Näherungsfunktion mit Ratio 1,217 . . . . Andere Näherungsfunktionen . . . . . . . Schranke . . . . . . . . . . . . . . . . . . . Wasser für die Wüste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Literaturverzeichnis 46 Abbildungsverzeichnis 47 2 1 Einleitung 1.1 Wasser für die Wüste In einem kleinen Land weit hinter dem Horizont lebte einst ein gütiger und sehr gerechter König. Sein Name war König Algorimas (von seinen Freunden wurde er Algo genannt). Algorimas war bei seinem Volk sehr beliebt, denn er war stets bemüht sein Volk an seinem Reichtum teilhaben zu lassen und so lebten alle in seinem Land glücklich und zufrieden. Doch eines Tages brach eine große Dürre über das Land herein. So verwüstete das Land mehr und mehr... Abbildung 1.1: Das Königreich von König Algorimas Trotz seines Reichtums konnte der gute König Algorimas nichts gegen das große Leid in seinem Land tun. In einigen Städten gab es zwar noch Wasserquellen, aber leider nicht in allen. Aus diesem Grund überlegte sich der König, dass er ein Leitungssystem bauen lassen musste, welches alle Städte miteinander verbindet und so Wasser für die Wüste liefert. Da er aber nicht dumm war, wollte er so wenig Leitungen wie möglich verlegen lassen. Denn Leitungen waren teuer und es war harte und schwere Arbeit sie herzustellen und zu verlegen. Deshalb lies er verlauten, dass derjenige, der ihm 3 René Trumpp Wasser für die Wüste das kürzeste Leitungssystem plant, seine wunderschöne Tochter heiraten darf und sein Nachfolger werden würde... Das könnte der Beginn eines schönen Märchens sein, ist es aber leider nicht. Mit dieser kleinen Geschichte möchte ich sie zu einem ersten Nachdenken über folgende Problematik bringen: In einer Ebene hat man verschiedene Punkte und möchte diese miteinander verbinden. Es soll der kürzeste Weg gefunden werden. So einfach sich diese Problemstellung auch anhört, ich werde ihnen in den folgenden Kapiteln zeigen, dass dieses Problem (und einige Abwandlungen) nicht effizient lösbar ist. Ich werde mich dabei immer wieder auf unseren König Algorimas beziehen, da sich an diesem Beispiel vieles leichter erklären lässt. Sollten sie jetzt denken, dass das Beispiel mit dem König völlig überholt ist und es heute keine praktische Relevanz mehr hat, muss ich ihnen leider widersprechen. Natürlich werden wir heute keinen König mehr finden, der solche Probleme hat (oder eine schöne Tochter deshalb verheiraten will), jedoch lassen sich folgende Szenarien auch auf das gleiche Problem zurückführen: • Wasserversorgung in Städten • Stromversorgung • Netzwerke (z.B. Telefon, LAN) • Leiterbahnen auf Platinen • ... Um leichter mit den Problemen arbeiten zu können, müssen wir die reale Welt in unsere theoretische Welt überführen. Wir müssen das Problem formal beschreiben. In unserem Beispiel ist das aber ganz einfach. Die Landkarten (siehe Abbildung 1.1) lässt sich in folgenden Graphen umwandeln: a g E f ? ?? ?? ?? ? e ?b ?? ?? ?? ? c / // // // // // // d Abbildung 1.2: Landkarte als Graph 4 René Trumpp Wasser für die Wüste Hierbei stellen die Knoten V die Städte dar und das Gewicht der Kanten E den Abstand der Städte voneinander. So stellt der Knoten a die Stadt Altonia und der Knoten b die Stadt Brulania dar. Damit beschreibt das Gewicht der Kante Eab , die Entfernung von Altonia zu Brulania. 1.2 Grundbegriffe Um richtig miteinander “reden“ zu können, müssen wir uns vergewissern, dass wir mit denselben Begriffen auch dasselbe meinen. Denn ein Baum muss nicht immer mit den Wurzeln im Boden stecken, aber eins nach dem anderen: Graph Unter einem Graph G versteht man ein Paar (V ,E), wobei V eine endliche Knotenmenge (vertex) und E eine endliche Menge von Kanten (edge) bezeichnen. Es ist nicht verlangt, dass alle Knotenpaare durch Kanten miteinander verbunden sind. Kante Eine Kante verbindet genau zwei Knoten. Kanten können gerichtet oder ungerichtet sein. Man kann das sehr gut mit Einbahnstraßen und normalen Straßen vergleichen. Bei ungerichteten Kanten gilt: Eab = Eba a / b c d Abbildung 1.3: Gerichtete und ungerichtete Kante Vollständigkeit In einem vollständigem Graphen G ist jeder Knoten mit jedem anderen Knoten verbunden. Gewicht Das Gewicht einer Kante ist eine Markierung der Kante, welche z.B. ihre Länge, eine Masse oder einen Euro-Betrag repräsentieren kann. Abkürzend schreibt man statt Gewicht der Kante e einfach λ(e). λ wird dann als Gewichtsfunktion bezeichnet. Aus gewichteten Kanten bestehende Graphen nennt man gewichtete Graphen. Das Gewicht eines Weges zwischen zwei a 5 b Abbildung 1.4: Gewichtete Kante bzw. gewichteter Graph Knoten (u, v) ist die Summe der Gewichte aller Kanten auf dem Weg von u nach v. Das Gewicht eines Graphen, ist die Summe der Gewichte aller Kanten im Graph. 5 René Trumpp Wasser für die Wüste Baum Unter einem Baum B versteht man einen ungerichteten Graphen, der alle Knoten V mit (|V | − 1) Kanten miteinander verbindet. Ist der Baum ein Teilgraph eines Graphen G mit |VB | = |VG |, so heißt dieser aufspannender Baum von G. Blatt Als Blätter werden Knoten in Bäumen bezeichnet, die nur mit einem einzigen weiteren Knoten über eine Kante verbunden sind. Effizient Ein Algorithmus heißt effizient, wenn er ein vorgegebenes Problem mit möglichst geringem Ressourcenaufwand (Zeit, Speicher) löst. Wir werden nur die Laufzeit, also die Zeiteffizienz, der Algorithmen betrachten. Dazu verwenden wir die Groß-O O Notation (siehe z.B. [Wag03]). Anzahl Zahl der Elemente in einer Menge (Abkürzung: ] ) 1.3 Ziel und Abgrenzung Es gibt viele Probleme, von denen glaubt man im ersten Moment, dass sie schon irgendwie (effektiv) lösbar sind. Ich möchte Ihnen in den nächsten Kapiteln einige Probleme ähnlich dem Einführungsbeispiel vorstellen. Es soll dabei gezeigt werden, wie sich kleine Unterschiede in der Problemstellung auf die Möglichkeit zur Lösung des Problems auswirken können. Sie werden sehen, dass manche Probleme schnell und einfach, andere wiederum nicht effizient lösbar sind. Insbesondere werde ich ihnen das s.g. Steiner Baum Problem vorstellen. Es gehört zu den NP-Vollständigen Problemen, weshalb auch kein effizienter Algorithmus zur Lösung dieses Problems existiert. Deshalb werde ich ihnen am Ende noch Algorithmen zur Näherung der Lösung vorstellen. Sie werden sehen, dass diese in akzeptabler Zeit vielleicht nicht die perfekte Lösung liefern (aber fast). 6 2 Minimal aufspannende Bäume 2.1 Problembeschreibung Schon Pythagoras hat 600 v. Chr. herausgefunden, dass der kürzeste Weg zwischen zwei Punkten die Gerade ist. Deshalb dachte König Algorimas (sehr blauäugig wie wir später sehen werden), dass das auch für mehrere Punkte gelten muss. Er stellte also die Aufgabe wie folgt: Das Leitungssystem soll alle Städte verbinden. Außerhalb der Städte ist es nicht möglich die Leitungen zu verzweigen. D.h. jede beliebige Stadt kann auf direktem Weg mit jeder anderen Stadt verbunden werden. Es sollen so wenig Leitungen wie möglich verwendet werden. a g // // // // c // jjjj /// j j j // // jjj / jjjjjjj // ?j // ? ?? f // ?? // ?? b e d Abbildung 2.1: Beispiel für einen aufspannenden Baum Übersetzen wir zuerst die Aufgabe von König Algorimas wieder in unsere theoretische Betrachtungsweise. Es ist ein Graph G mit V Knoten gegeben. Die Knoten sind mit gewichteten Kanten E verbunden. Bei König Algorimas würde immer ein vollständiger ungerichteter Graph entstehen (man kann ja immer den Abstand zweier Städte zueinander messen), wir wollen sein Problem aber gleich etwas allgemeiner lösen und setzen keinen vollständigen Graphen voraus (es muss also nicht jeder Knoten mit jedem anderen verbunden sein). Gesucht ist der Teilgraph G∗ , der alle Knoten V miteinander verbindet und dessen Summe der Kantengewichte minimal ist. Diese Problemstellung ist in der Literatur als “Minimal aufspannender Baum“ bekannt. 7 René Trumpp Wasser für die Wüste a ?b / ??? /// ?? // ?? // / c // // // // / // ? // g f ??? // ?? // ?? e d Abbildung 2.2: Weiteres Beispiel für einen aufspannenden Baum 2.2 Ein erster Lösungsansatz Ein Lösungsansatz, der wahrscheinlich jedem zuerst in den Sinn kommt ist der einfachste: Wir berechnen jeden möglichen aufspannenden Baum G∗ und suchen den mit dem kleinsten Gewicht heraus. Dass dieser verbal beschriebene Algorithmus sicherlich richtig ist und auch immer funktioniert ist klar, jedoch müssen wir seine Laufzeit betrachten. Dazu sollten wir zuerst einige Überlegungen anstellen. Die Anzahl der Kanten hängt in einem vollständigen Graphen von der Anzahl n der Knoten ab: ]E = n X (i − 1) = 0 + 1 + 2 + · · · + (n − 1) = i=1 n−1 X (i) = i=1 n ∗ (n − 1) 2 Diese Formel kann man sehr schnell herleiten, wenn man sich überlegt, dass bei jedem neuen Knoten eine neue Kante zu jedem bereits vorhandenem Knoten hinzukommt. Ein aufspannender Baum hat immer n − 1 Kanten. Will man nun aber die Anzahl der Bäume in einem Graphen mit n Knoten herausfinden, wird es schwieriger. In [CoLeRiSt01] wird bewiesen, dass sich die Anzahl mit Hilfe der s.g. Catalan-Zahlenfolge (1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, ...) berechnen lässt. So ergibt sich 2n (2n)! 1 ∗ = ]ST = (n + 1) n (n + 1)!n! Mit diesem Wissen ist unser doch so netter Algorithmus fast wertlos, denn so viele Bäume möchte man doch nicht vergleichen. 8 René Trumpp Wasser für die Wüste 2.3 Ein “gieriger“ Lösungsansatz Doch damit geben wir uns natürlich nicht zufrieden. Es geht nämlich besser. Dazu müssen wir einfach “gierig“ sein. Schauen sie sich hierzu folgenden Algorithmus an: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Wähle beliebigen Knoten s ; S ={ s }; for each v ∈ V \{ s } do { Distanz ( v )= Kante (s , v ); Vorgänger ( v )= s ; } Distanz ( s )=0; while S6= V do { Finde u∈V \ S mit Distanz ( u )= min { Distanz ( v )| v∈V \ S }; S = S∪{ u }; for each v∈V \ S do { if Kante (u , v ) < Distanz ( v ) then { Distanz ( v )= Kante (u , v ); Vorgänger ( v )= u ; } } } Dieser Algorithmus ist insofern “gierig“, als dass er immer vom aktuellen Teilbaum aus die kleinste angrenzende Kante auswählt und hinzufügt. Er wählt so nie eine falsche Kante aus. Diese Art Algorithmen haben in der Literatur die Bezeichnung Greedy-Algorithmen und können auf viele Problemstellungen angewendet werden (siehe z.B. [Wag03]). Doch nun wollen wir die Abschätzung der Laufzeit des Algorithmus betrachten: Die For-Schleife aus Zeile 3 läuft n − 1 mal durch. Die While-Schleife aus Zeile 8 läuft auch n − 1 mal durch, denn bei jedem Durchlauf wird ein Knoten zu S hinzugefügt. Die Laufzeit der Suche in Zeile 9 hängt stark von der verwendeten Datenstruktur ab, in welcher wir die Knoten und Kanten speichern. Wir wollen sie jetzt einfach mit log n abschätzen. Die For-Schleife aus Zeile 11 wird bei jedem Durchlauf weniger oft durchlaufen, jedoch schätzen wir sie mit n ab. Daraus ergibt sich: O(n − 1) + O(n ∗ log n) + O(n ∗ n) = O(n2 ) Trotz dieser sehr groben Abschätzung und des noch verbesserungsfähigen Algorithmus haben wir so eine quadratische Laufzeit erreicht. Eine merkliche Verbesserung zu unserem ersten Ansatz. In der Literatur (siehe [ProSte02]) ist er unter 9 René Trumpp Wasser für die Wüste “Prim Algorithmus für aufspannende Bäume“ zu finden (nach seinem Erfinder Robert C. Prim) und lässt sich bis zu einer Laufzeit von O(e + n ∗ log n) (mit e = Anzahl der Kanten) verbessern. Wir werden aber nun sehen, dass so eine Verbesserung leider nicht immer möglich ist. 10 3 Das Steiner Problem in Graphen 3.1 Problembeschreibung Als König Algorimas nochmals über seine gestellte Aufgabe nachdachte, fiel ihm auf, dass er wohl einen Fehler gemacht haben musste. Er überlegte sich an 4 Städten die ganze Situation nochmals durch. a b 2 c 2 2 d Abbildung 3.1: Lösung für König Algorimas erste Aufgabe bei 4 Punkten Wenn die 4 Städte in einem Quadrat angeordnet sind, und jede Stadt zu seiner nächsten Nachbarstadt den Abstand 2 hat, so wäre die kürzeste Lösung ein Leitungssystem der Länge 2 + 2 + 2 = 6. a ?? ?? ?? ?? v c b ?? ?? ?? ?? d Abbildung 3.2: Lösung für 4 Punkte mit zusätzlichem Punkt in der Mitte König Algorimas überlegte nun weiter und erkannte, dass wenn man in der Mitte des Quadrats eine zusätzliche Verteilerstation bauen würde, dass die Länge des benötigten Leitungssystems kleiner werden würde. Die Länge √ des Leitungssystems √ wäre dann die doppelte Länge einer Diagonalen, also 2∗( 2∗2) = 4∗ 2 ≈ 5, 66. Er erkannte, dass durch zusätzliche Verteilerstationen die Länge des Leitungssystems verkürzt werden kann. Er schaute sich die Landkarte seines Reichs an und zeichnete einige neue Punkte ein, an denen man Verteilerstationen bauen könnte. Er änderte seine Aufgabenstellung wie folgt: 11 René Trumpp Wasser für die Wüste Das Leitungssystem soll alle Städte verbinden. Außerhalb der Städte ist es nur an den Verteilerstationen möglich die Leitungen zu verzweigen. Es müssen nicht alle Verteilerstationen genutzt werden. a g b // // // s t // c // // / u ? OOO OOO f ??? OOO ?? OOO ?? O e d Abbildung 3.3: Landkarte mit zusätzlichen Verteilerstationen Bevor wir uns um die genaue Aufgabenstellung widmen, müssen wir zunächst die neuen Gegebenheiten betrachten. Es ist ein vollständiger Graph G mit V Knoten gegeben. Die Knoten V sind aber in zwei disjunkte Mengen unterteilt, in die Terminale () und die s.g. Steinerknoten ( ). Ein Baum B, der alle Terminale und beliebig viele Steinerknoten miteinander verbindet, heißt Steiner Baum, falls kein Steinerknoten ein Blatt ist. Die neue Aufgabenstellung heißt also ganz einfach, finde für einen gegebenen Graphen G mit V Knoten (Terminale und Steinerknoten) den minimalen Steinerbaum. Das Problem ist NP-Vollständig (siehe Kapitel 6). 3.2 Lösungsansatz Ohne Steinerknoten wäre das Problem einfach mit dem Algorithmus für aufspannende Bäume zu lösen. Man würde den aufspannenden Baum des Graphen berechnen und hätte die Lösung. Vielleicht könnte man an diesem Ansatz ja weitermachen. Nehmen wir an, wir hätten einen Graphen G, der nur aus Terminalen besteht. Fügt man in G nun einen Steinerknoten s1 ein, muss man den minimal aufspannenden Baum von G und von G ∪ {s1 } berechnen und danach den kleineren auswählen. Soweit funktioniert das ganz gut, aber jetzt kommt das Problem. Fügen wir jetzt noch einen Steinerknoten s2 hinzu, müssen wir den minimal aufspannenden Baum von G,G ∪ {s1 }, G ∪ {s2 } und von G ∪ {s1 , s2 } berechnen und danach den kleinsten auswählen. Fügen wir nun einen weiteren Steinerknoten hinzu, müssen wir noch mehr Kombinationen durchprobieren. Außerdem können wir das Wissen aus vorherigen Untersuchungen nicht verwenden, denn auch wenn ein Steinerknoten si in einem Schritt nicht im minimalen Steinerbaum enthalten ist, muss das nicht bedeuten, dass er in allen folgenden Schritte auch nicht enthalten ist. 12 René Trumpp Wasser für die Wüste Mit jedem neuen Steinerknoten verdoppelt sich die Anzahl der zu untersuchenden Teilgraphen. D.h. bei n Steinerknoten müssen 2n minimal aufspannende Bäume berechnet und verglichen werden. 13 4 Das rechtwinklige Steiner Problem 4.1 Problembeschreibung König Algorimas hatte eingesehen, dass zusätzliche Verteilerstationen die Leitungslänge verkürzen können. Er schaute sich also nochmals seine Landkarte an und legte ein Gitternetz darüber. Er stellte seine Aufgabe erneut: Das Leitungssystem soll alle Städte verbinden. Außerhalb der Städte sind an allen Gitternetzkreuzungen neue Verteilerstationen möglich. Die Leitungen dürfen nur entlang der Gitternetzlinien verlaufen. _ _ _ _ _ _ _ _ _ _ _ _ g _ _ _ _ _ a__ _ __ _ __ _ __ _ _ _ _ _ _ f _ __ _ __ _ __ _ __ _ __ _ _ _ e _ _ _ _ _ _b __ __ __ _ __ _ _ _c _ __ _ __ _ _ _ _ _ _ _ _ _ _ __ d _ _ _ _ _ _ _ _ Abbildung 4.1: Eine mögliche Lösung des rechtwinkligen Steinerproblems Wir wollen nun diese Aufgabenstellung in die Graphentheorie übersetzen. Gegeben ist ein Graph G mit V Knoten. Die Koordinaten von V entsprechen (Vx , Vy ). Es dürfen beliebig viele Steinerknoten S mit den Koordinaten (Sx ,Sy ) hinzugefügt werden, wobei gelten muss, ∃v ∈ V , so dass gilt vx = sx oder vy = sy . Gesucht ist wieder ein minimal aufspannender Baum. 14 René Trumpp Wasser für die Wüste 4.2 Lösungsansatz Zuerst muss man sich überlegen, wie viele Steinerknoten in dem Gitternetz möglich sind. Dazu betrachtet man die Höhe und Breite des Netzes. _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ __ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Abbildung 4.2: Gitternetz mit Höhe und Breite 3 In einem Netz mit der Höhe h und Breite b gibt es insgesamt h ∗ b Gitternetzkreuzungen. D.h. die maximale Anzahl der Steinerknoten beträgt (h ∗ b) − ]V Mit diesem Hintergrundwissen können wir nun die Formel aus Kapitel 3.2 abwandeln. Da die Anzahl der Steinerknoten nun nicht mehr vorgegeben ist, müssen wir vom Maximum ausgehen. ]ST = 2]S = 2(h∗b)−]V Die Anzahl der zu untersuchenden minimal aufspannenden Bäume hängt also nun von der Höhe und Breite des Graphen, sowie der Anzahl der Terminale ab. 15 5 Das geographische Steiner Problem 5.1 Problembeschreibung König Algorimas wollte nun noch den letzten Schritt machen und die Aufgabenstellung nochmals erweitern. Er lies alle vorherigen Einschränkungen weg und stellte seine Aufgabe neu: Das Leitungssystem soll alle Städte verbinden. Außerhalb der Städte sind an allen Punkten neue Verteilerstationen möglich. a b s c ??? ?? ?? ? t ? OOO g OOO f ??? OOO ?? OOO ?? O e d Abbildung 5.1: Landkarte mit beliebigen zusätzlichen Verteilerstationen 5.2 Lösungsansatz Die exakte Lösung dieses Problems ist nahezu unmöglich. Wir haben ein paar Punkte in einer Ebene und dürfen jeden beliebigen Punkt einfügen. Wenn wir wieder an unsere Formel aus Kapitel 3.2 denken, müsste im Exponenten ja “Unendlich“ stehen. Aus diesem Grund stellt sich uns jetzt die Frage, muss es denn immer die exakte Lösung sein? Warum sind wir denn nicht mit einer fast genauso guten Lösung zufrieden, wenn es uns viel leichter fallen würde, diese zu finden? Deshalb wollen wir uns nun mit der Näherungen des Steiner Problems befassen. 16 6 NP-Vollständige Probleme 6.1 Deterministisch oder nicht ? In der theoretischen Informatik haben wir oft mit den verschiedensten Problemstellungen zu tun. Wir gehen strukturiert an das Problem heran und suchen nach einer Lösung. Dabei haben wir immer eine klare Richtung, verlassen uns nie auf den Zufall oder auf göttliche Eingebung, denn wie sollte man auch damit rechnen können, der Computer kann es ja auch nicht. Diese Anschauung bezeichnet man als deterministisch. Bei dem beliebten Minimum- oder Maximum-Suchproblem haben wir ein sehr gutes Beispiel: Wir sagen dem Computer, er solle doch alle möglichen Lösungen anschauen und uns nach dem Vergleich aller, das kleinste bzw. größte ausgeben. Es gibt jedoch auch einen anderen Ansatz, warum kann man nicht einfach zum Computer sagen, wähle das kleinste bzw. größte aus und gib es mir dann auf dem Bildschirm aus. Wir gehen also davon aus, der Computer wisse bereits vorher die Lösung und wir müssen ihn nur noch darum bitten, sie uns auszugeben. Sie werden jetzt verständlicherweise den Kopf schütteln und sich fragen, warum wir das hier betrachten, denn so etwas ist ja nicht möglich. Es gibt kein Orakel von Delphi, welches der Computer fragen könnte. Das stimmt. Jedoch ist diese nichtdeterministische Art der Fragestellung für uns zu einem späteren Zeitpunkt noch extrem wichtig. 6.2 Was ist NP ? Wir haben bereits das “Minimal aufspannende Baum“ Problem kennen gelernt. Im Nachhinein kann man sagen, dass es doch sehr einfach zu lösen war. Doch wenn wir jetzt wieder auf ein Problem stoßen brauchen wir eine Metrik, ein Lineal, mit dem wir das neue Problem messen können. Erst dann können wir eine Aussage darüber treffen, welches einfacher oder schwerer ist. Diese Messung geschieht in der theoretischen Informatik durch die Einteilung in die s.g. Komplexitätsklassen. Es gibt davon verschiedene, wobei gleich schwere Probleme in der gleichen Klasse liegen. 17 René Trumpp Wasser für die Wüste Nun stellt sich die Frage, wie wir die Einteilung eines Problems in eine solche Klasse vornehmen. Man hat sich dazu entschlossen, die Probleme nach ihrer Laufzeitkomplexität einzuordnen. In der Klasse P liegen also alle Probleme, die deterministisch in polynomialer Zeit für Eingaben der Länge n eine Lösung finden. Analog dazu liegen in N P alle Probleme, die nicht-deterministisch in polynomialer Zeit für Eingaben der Länge n eine Lösung finden. Eine formale Definition von NP lautet: Ein Problem L ⊆ Σ∗ ist genau dann in NP, wenn es ein Polynom p(n) und ein Problem L0 ∈ P gibt, so dass gilt: L = {x ∈ Σ∗ |∃y, |y| ≤ p(|x|) ∧ (x, y) ∈ L0 } Es gibt noch eine zweite (vielleicht einfachere) Möglichkeit, die Klasse N P zu beschreiben: ein Algorithmus rät (nicht-deterministisch) eine Lösung für das Problem und prüft danach in polynomialer Zeit nach, ob diese stimmt. Aus den obigen Definitionen wird klar, dass P ⊆ N P gilt. Ob aber P = N P gilt, ist noch ungeklärt, wobei im Moment P 6= N P vermutet wird. Ein Problem ist NP-Vollständig, wenn es in der Komplexitätsklasse NP liegt und wenn sich jedes andere Problem in NP auf dieses reduzieren lässt. 6.3 Steiner Problem ist ein NP-Vollständiges Problem Der Beweis, dass das Steiner Problem ein NP-Vollständiges Problem ist, wird durch die Reduktion des 3SAT - Problems auf das Steiner Problem geführt. Das NP-Vollständige 3SAT - Problem stellt sich wie folgt: Gegeben ist eine boolsche Formel F in konjunktiver Normalform bei der jede Klausel maximal 3 Literale enthält. Gibt es eine Variablenbelegung, die F erfüllt ? Beispiel: F = (x1 ∨ x2 ∨ x3 ) ∧ (x1 ∨ x3 ) ∧ (x2 ∨ x3 ∨ x4 ) Zuerst müssen wir das 3SAT - Problem in ein Steiner Baum Problem transformieren. Dazu konstruieren wir einen Graphen G wie folgt: Wir verbinden zwei Variablen u, v über einen “Literal-Pfad“ (siehe Abbildung 6.1), dazu stellen wir allen n Literalen x1 , . . . , xn den negativierten Literal x1 , . . . , xi gegenüber. Für jede der m Klauseln fügen wir einen neuen Knoten Ci zu G hinzu, wobei jedes Ci über eine Kante der Länge t = 2n + 1 mit den in Ci vorkommenden Literalen verbunden ist. Als Terminale wählen wir K = {u, v} ∪ {C1 , . . . , Cm }. Den Grenzwert B legen wir auf B = 2n + t ∗ m fest. 18 René Trumpp Wasser für die Wüste u CV1, l h R M q ) G w ? % 7 1 " , x1 x2 x3 x4 ?? ?? ?? ?? ??? ??? ??? ??? ?? ?? ?? ?? ?v ? ? ? ?? ?? ?? ?? ?? ?? ?? ??? ?? ?? ?? ?? ?? ? ?? ? ? x1# x2 8 x3# x 4 > D / / ~ J | N @ S @V H H xz Nv ZN# C2 C3 Abbildung 6.1: Transformiertes 3SAT-Problem Das Steiner Baum Problem lautet nun: Ist die Länge des Steiner Baums in G mit den Terminalen K kleiner als B. Mit Hilfe einer Reduktion zeigen wir nun, dass gilt • 3SAT ist erfüllbar ⇒ Steiner Baum hat Länge ≤ B • Steiner Baum hat Länge ≤ B ⇒ 3SAT ist erfüllbar und somit, dass das Steiner Baum Problem NP-Vollständig ist. Zuerst nehmen wir an, dass das 3SAT - Problem erfüllbar sei. Um einen Steiner Baum für K zu erzeugen, starten wir mit einem u − v Pfad P , der eine akzeptierende Belegung darstellt. Dabei bedeutet xi ∈ P , dass xi in der Belegung als “wahr“ verwendet wird. Analog bedeutet xi ∈ P , dass xi in der Belegung als “falsch“ verwendet wird. Betrachten wir nun für jede Klausel den Knoten Ci , so sehen wir, dass wir Ci mit einer Kante der Länge t zu P hinzufügen können. Dadurch erhalten wir einen Steiner Baum von K mit der Länge 2n + t ∗ m = B. Um die andere Folgerungsrichtung zu zeigen, nehmen wir zuerst an, T sei ein Steiner Baum für K mit höchstens der Länge B. Man sieht sehr schnell, dass für jede Klausel der Knoten Ci mit dem Pfad verbunden sein muss. Nehmen wir einmal an, es gäbe eine Klausel Ci0 , die mit mehr als einer Kante mit dem Pfad verbunden wäre. Dann wäre die Länge von T ≥ (m + 1) ∗ t ≥ B, es ist also nicht 19 René Trumpp Wasser für die Wüste möglich. Das zeigt, dass u und v nur entlang des “Literal-Pfads“ miteinander verbunden sein können, also mit mindestens 2n Kanten. Da jede Klausel mindestens eine Kante/einen Kantenzug der Länge t braucht, um Ci mit dem Pfad zu verbinden, folgern wir, dass der u−v Pfad genau 2n Kanten enthält und dass jede Klausel exakt eine Kante der Länge t braucht um auch mit dem Pfad verbunden werden zu können. Also bildet der u − v Pfad eine erfüllbare Belegung. 6.4 Andere NP-Vollständige Probleme Das Problem der minimalen Steiner Bäume ist ein solches NP-Vollständiges Problem. Die nachfolgend aufgeführten Probleme stellen einen kleinen Auszug vieler weiterer NP-Vollständiger Probleme dar, mehr Informationen finden sie unter [AstBai02]. 6.4.1 Problem des Handelsreisenden Das Handelsreisende Problem ist wohl das bekannteste NP-Vollständige Problem. Es tritt in so gut wie jeder Routenplanungssoftware oder Logistiksoftware auf und lässt sich sehr einfach beschreiben: Ein Handelsreisender muss an einem Tag n Kunden in verschiedenen Städten besuchen. Gesucht ist die kürzeste Rundreise, bei der er bei jedem Kunden genau einmal vorbeikommt und am Ende seiner Reise wieder zu Hause ist. 6.4.2 Rucksackproblem Das Rucksackproblem findet auch in der Logistik seine Anwendung, denn es ist egal, ob ein Rucksack oder ein LKW vollgeladen wird. Die Aufgabenstellung ist wie folgt: Gegeben sind n Gegenstände mit den Gewichten (Massen) g1 , g2 , . . . , gn und den zugehörigen Werten w1 , w2 , . . . , wn . Das Gewicht des i-ten Gegenstands beträgt gi , sein Wert wi . Die Interpretation des Wertes hängt vom Kontext ab, so wäre es bei Schmuck der Geldwert oder bei der Ausrüstung eines Bergsteigers der Beitrag, den der Gegenstand zur erfolgreichen Bewältigung seines Aufstiegs beträgt. Der Rucksack ist beschränkt belastbar, sein Maximalgewicht sei K. D.h. das Gesamtgewicht aller eingepackten Gegenstände darf K nicht überschreiten. Gesucht ist nun die Teilmenge der Gegenstände, die maximalen Wert hat, aber K nicht überschreitet. 20 René Trumpp Wasser für die Wüste Anders formuliert: Es sind die natürliche Zahlen gP 1 , g2 , . . . , gn , w1 , wP 2 , . . . , wn und K gegeben, gesucht ist eine Teilmenge I, so dass i∈I gi ≤ K und i∈I wi = M AX. 21 7 Näherungsfunktionen 7.1 Sonderfälle Wir haben in den vorangegangenen Kapiteln einige sehr schwere Probleme kennen gelernt, die nicht “effizient“ lösbar waren. Nun drängt sich die Frage auf, warum wir nicht mit einer fast perfekten Lösung leben sollten, wenn es um einiges einfacher wäre, diese zu berechnen. Doch zuerst wollen wir zwei Sonderfälle betrachten, bei denen wir auch ohne Näherung zu einer exakten Lösung kommen. 7.1.1 Keine Steinerknoten Gegeben ist ein Graph G, der nur aus Terminalen besteht (also keine Steinerknoten besitzt). Anschaulich ist dann der minimale Steinerbaum identisch mit dem minimal aufspannenden Baum (siehe Kapitel 2). Die Laufzeit für diesen Sonderfall ist also O(n2 ). 7.1.2 Nur zwei Terminale Gegeben ist ein Graph G, der nur aus zwei Terminalen (T1 , T2 ) und n Steinerknoten besteht. Ein minimaler Steiner Baum entspricht dem kürzesten Weg zwischen T1 und T2 . Die Problemstellung eines kürzesten Weges zwischen zwei Knoten in einem Graphen ist jedoch leicht lösbar: 1 2 3 4 5 6 7 8 9 for each v ∈ V do { Distanz ( v )=∞; Vorgänger ( v )= NULL ; } Distanz (T1 )=0; Vorgänger (T1 )=T1 ; U=V; while U6= ∅ do { 22 René Trumpp 10 11 12 13 14 15 16 17 18 Wasser für die Wüste Finde u∈U mit Distanz ( u )= min { Distanz ( v )| v∈U }; U =U -{ u }; for each (u , v )∈E , v∈U do { if ( Distanz ( u ) + Gewicht (u , v )) < Distanz ( v ) then { Distanz ( v )= Distanz ( u ) + Gewicht (u , v ); Vorgänger ( v )= u ; } } } Der gezeigte Algorithmus stammt von Edsger Wybe Dijkstra. Er berechnet den kürzesten Weg zwischen 2 Knoten, indem er zuerst den Abstand zwischen T1 und allen anderen Knoten auf ∞ setzt. Sobald ein kürzerer Abstand als der bereits bekannte zu einem Knoten gefunden wird, wird der neu gefundene Weg als kürzester markiert. Um nun den kürzesten Weg zwischen T1 und T2 auszulesen, geht man von T2 immer zum Vorgänger bis T1 erreicht ist. Die Laufzeit des Algorithmus ist O(n2 ). 7.2 Qualitätskriterien für Näherungsfunktionen Nachdem wir die Sonderfälle betrachtet haben, schauen wir uns nun die Näherungsfunktionen an. Es stellt sich natürlich die Frage, wie wir unsere Näherungen miteinander vergleichen. Wie können wir sagen, die eine Näherung ist besser oder schlechter als die andere? Benutzt man für verschiedene Graphen zwei verschiedene Näherungsfunktionen, wird wahrscheinlich einmal der eine und einmal der andere besser sein. Also können wir es schlecht an einem Beispiel festmachen. Man hat sich also für eine andere Vergleichsmethode entschieden, man vergleicht immer den “Worst-Case“, d.h. wie schlecht ist die Näherung im schlechtesten Fall. Hierbei gibt man das Verhältnis (Ratio) zur optimalen Lösung an. Wir werden im nächsten Schritt eine Näherungsfunktion mit Ratio 2 betrachten, d.h. eine Näherungsfunktion, die im schlechtesten Fall das doppelte Gewicht der optimalen Lösung hat. 7.3 Näherungsfunktion mit Ratio 2 Bevor wir uns um die eigentliche Näherungsfunktion kümmern können, brauchen wir noch den s.g. Distanzgraphen. Bei einem gewichteten Graphen G mit den Knoten V und den Kanten E ist der Distanzgraph D wie folgt definiert: 23 René Trumpp Wasser für die Wüste • D ist ungerichtet und vollständig • Das Gewicht der Kante (u,v) in D entspricht dem Gewicht des kürzesten Weges in G von u nach v. Um den Distanzgraphen zu berechnen benutzen wir den Dijkstra Algorithmus für jeden Knoten einzeln. Es reicht den Algorithmus für jeden Knoten einmal aufzurufen und nicht für jedes Knotenpaar, denn er berechnet den kürzesten Abstand vom Ausgangsknoten zu allen anderen Knoten. Seine Laufzeit ist n ∗ O(n2 ) = O(n3 ), wir werden sehen, dass das auch der nach unten beschränkende Faktor in unserer Näherungsfunktion sein wird. 7.3.1 Satz Es sei • G ein ungerichteter gewichteter Graph mit V Knoten • T eine Teilmenge von V • D sei der Distanzgraph von T • B der minimale Steinerbaum von G mit T als Terminale • msp(D) der minimal aufspannenden Baum von D dann gilt Gewicht von msp(D) ≤ 2∗ Gewicht von B 24 René Trumpp Wasser für die Wüste 7.3.2 Beweis Abbildung 7.1: Zyklische Durchmusterung Wir betrachten zuerst eine zyklische Durchmusterung zu B beginnend bei einem Blatt u0 (d.h. u0 ist ein Terminal). Eine zyklische Durchmusterung stellt man sich am einfachsten so vor, dass man um den Graphen B einmal außen herumfährt. Es gibt also eine Folge von Knoten u0 , u1 , . . . , u2m , wobei u0 = u2m , mit : 1. Alle Kanten der Folge existieren auch in B : ∃ei ∈ EB |ei = (ui , ui+1 ), ∀0 ≤ i < 2m 2. Jede Kante ei ∈ EB kommt in der Folge genau zwei mal vor: e = (ui , uj ) und e = (uj , ui ) für i 6= j 3. Bis auf den Knoten u0 kommt jedes Blatt in der Folge nur einmal vor Aus der zweiten Eigenschaft folgt (mit λ als Gewichtsfunktion): 2m X i=0 λ(ei ) = X 2 ∗ λ(e) = 2 ∗ λ(B) e∈EB Wir betrachten nun eine neue Teilfolge, die aus k Terminalen besteht: u0 = ui1 , . . . , uik Wegen der ersten Eigenschaft bildet für alle j ∈ {1, . . . , k − 1} die Teilfolge uij , uij +1 , . . . , uij+1 25 René Trumpp Wasser für die Wüste einen Weg in G, so dass ij+1 −1 X λ(ei ) ≥ Abstand(uij , uij+1 )inD i=ij gilt, da wir im Distanzgraphen immer die kürzesten Wege betrachtet haben. Da mit der ersten Eigenschaft auch uik , uik +1 , . . . , u2m einen Weg in G bilden, gilt auch 2m−1 X λ(ei ) ≥ Abstand(uik , u0 )inD i=ik Damit folgt: 2 ∗ λ(B) = 2m X i=0 ≥ k−1 X λ(ei ) = −1 k−1 ij+1 X X j=1 i=ij λ(ei ) + 2m−1 X λ(ei ) i=ik [Abstand(uij , uij+1 )inD] + Abstand(uik , ui1 )inD j=1 Dies entspricht der Länge des Kreises ui1 , ui2 , . . . , uik , ui1 in D. Dieses Gewicht ist aber sicher höher als das Gewicht eines minimal aufspannenden Baumes in D. 7.3.3 Algorithmus Kou, Markowsky und Berman nutzen diese Eigenschaft für ihren Algorithmus: 1 2 3 4 5 6 7 8 9 10 Berechne den Distanzgraphen D von G Berechne den minimal aufspannenden Baum Berechne zu M 0 den Teilgraphen G0 von G Bestimme zu jeder Kante (u,v) aus M 0 Weg p (u,v) in G, der ihre Länge hat , S G0 = p (u,v) M 0 von D wie folgt einen kürzesten setze dann Berechne den minimal aufspannenden Baum M von G0 Lösche alle Blätter aus M , die keine Terminale sind , solange bis nur noch Terminale Blätter sind Die letzten beiden Schritte sind nötig, da die kürzesten Wege zu den Spannbaumkanten von D nicht eindeutig bestimmt sind und so sehr ungünstig gewählt werden können (wie wir im folgenden Beispiel sehen werden). Vergleicht man die einzelnen Schritte, so ist die Bestimmung der kürzesten Wege am schwierigsten. Also wird von ihr die Laufzeit abhängig und somit zu O(n3 ). 26 René Trumpp Wasser für die Wüste 7.3.4 Beispiel Wenden wir nun den Algorithmus auf ein Rechwinkliges Steinerproblem an (Gitterabstand = 1): _ _ __ _ _ __ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ __ _ __ _ __ _ __ _ __ _ __ _ __ _ __ _ _ _ 4 4 4 _ _ _ _ _ _ _ _ _ _ Abbildung 7.2: Beispielgraph G _ _ _ _ __ _ __ _ __ _ __ Abbildung 7.4: M’ von D (Schritt 2) _ _ __ _ _ __ _ _ _ _ _ _ __ _ _ _ _ _ __ _ __ _ __ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ Abbildung 7.6: M von G’ (Schritt 4) ??? ?? ?? ??4 ?? 4 ?? ?? ? 4 ? 4 ?? ?? ?? ?? 4 ? 4 ?? ?? ?? Abbildung 7.3: Distanzgraph D zu G (Schritt 1) _ _ __ _ _ __ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ __ _ __ _ __ _ __ _ __ _ __ _ __ _ __ _ _ _ _ _ _ _ __ _ __ _ __ _ __ _ __ _ __ _ __ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ Abbildung 7.5: Teilgraph G’ (Schritt 3) _ _ __ _ _ __ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Abbildung 7.7: Fertiger Steiner Baum B (Schritt 5) 27 René Trumpp Wasser für die Wüste 7.4 Näherungsfunktion mit Ratio 1,217 In [GrHoNiPr02] wird eine Näherungsfunktion beschrieben, welcher eine Näherung mit Ratio 73/60 < 1, 217 erreicht. Gegeben ist ein quasi-bipartiter Graph G = (V, E), mit einer Teilmenge R ⊆ V , den Terminalen. Jeder Steiner Baum kann in s.g. volle Komponenten aufgeteilt werden. Eine volle Komponente ist ein Steiner Baum einer Teilmenge von R, in der jedes Terminal ein Blatt ist. Die Länge einer vollen Komponente ist die Summe der Längen seiner Kanten. Ein Steiner Baum ist eine Ansammlung von vollen Komponenten, die verbunden ist und gleichzeitig alle R enthält. Das Steiner Baum Problem kann also wie folgt als Hypergraphen-Problem formuliert werden: Jede volle Komponente repräsentiert eine Hyperkante, bestehend aus seinen Terminalen. Es sei F C die Menge dieser Hyperkanten und | t | die Länge der vollen Komponente. Dann ist das Steiner Baum Problem, der minimal aufspannende Sub-Hypergraph in dem gewichteten Hypergraphen (R, F C, | ◦ |). Im folgenden wird diese Formulierung für das Steiner Baum Problem verwendet. Nachstehender Algorithmus wird als Greedy-MSS bezeichnet: 1 2 3 4 5 6 Eingabe : Ein gewichteter Hypergraph (R, F C, | ◦ |) i =0; WHILE (R, {t1 , . . . , ti }) ist nicht verbunden Suche ti+1 ∈ F C , so dass fi minimiert wird ; i = i +1; RETURN t1 , . . . , ti Bei jedem Durchlauf der While-Schleife wird eine Hyperkante ausgewählt, die die Länge auf |t| fi (t) = ci (t) minimiert. Wobei ci (t) als die Differenz zwischen der Anzahl der verbunden Komponenten von (R, t1 , . . . , ti ) und von (R, t, t1 , . . . , ti ) definiert ist. Theorem: Greedy-MSS berechnet eine Näherung mit Ratio 73/60 < 1, 217 für das Steiner Baum Problem in quasi-bipartiten Graphen. Den ausführlichen Beweis für das Theorem finden Sie in [GrHoNiPr02]. 7.5 Andere Näherungsfunktionen Natürlich gibt es noch andere Näherungsfunktionen mit teilweise sogar noch besseren Ratios, alle hier aufzuführen würde aber zu weit gehen. Sollten Sie Interesse 28 René Trumpp Wasser für die Wüste haben, finden sich im Internet einige interessante Veröffentlichungen zu diesem Thema. Vielleicht sollten wir uns einfach einen eigene Näherungsfunktion überlegen. Für ein Dreieck (mit keinem Winkel größer als 120 Grad) aus drei Punkten (a, b, c) ist uns eine Berechnungsvorschrift für den Steinerpunkt bekannt (aus [ProSte02]): 1 2 3 4 5 6 Konstruiere das gleichseitige Dreieck 4abd, so dass d auf der einen Seite der Linie ab liegt und c auf der anderen . Konstruiere den Umkreis U von 4abd. Der Schnittpunkt der Linie cd mit dem Kreis U ist der gesuchte Steinerpunkt S . Als Näherung für das Steiner Baum Problem wäre es nun denkbar, die exakte Lösung für drei Punkte immer wieder zu verwenden. Die Auswahl, wie man den Graphen zerlegt ist dabei das schwierigste Problem. Es sind verschiedene Zerlegungen möglich, hier ein Beispiel: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Wähle beliebiges u ∈ V ; G = {u}; V = V − {u}; Finde v1 ∈ V mit Distanz (v1 , u)= max { Distanz (v, u)|v ∈ V }; V = V − {v1 }; G = G + {v1 }; Finde v2 ∈ V mit Distanz (v2 , u)= max { Distanz (v, u)|v ∈ V }; V = V − {v2 }; G = G + {v1 }; Konstruiere Steinerpunkt s in (u, v1 , v2 ); G = G + {s}; while V 6= ∅ { Finde u ∈ V mit Distanz (u, G)= max { Distanz (u, G)}; V = V − {u}; Finde v1 ∈ G mit Distanz (v1 , u)= min { Distanz (v, u)|v ∈ G}; Finde v2 ∈ G mit Distanz (v2 , u)= min { Distanz (v, u)|v ∈ G}; Konstruiere Steinerpunkt s in (u, v1 , v2 ); G = G + {s}; } Berechne den minimal aufspannenden Baum T von G; Lösche alle Blätter aus T , die keine Terminale sind , solange bis nur noch Terminale Blätter sind 29 René Trumpp Wasser für die Wüste 7.6 Schranke Bei vielen Näherungsverfahren kann man, je öfter man sie anwendet, sich dem exakten Ergebnis beliebig nähern. Bei allen Näherungsverfahren für das Steiner Baum Problem ist das leider nicht möglich. Es gibt immer ein ε, so dass gilt N äherung ≥ ExakteLösung + ε Der Beweis dafür (siehe [ProSte02]) ist nicht ganz einfach. Es wird dazu zuerst das s.g. Max3SAT Problem beschrieben und bewiesen, dass man sich dem exakten Ergebnis nicht beliebig nähern kann. Danach wird das Steiner Baum Problem auf das Max3SAT Problem reduziert. Zur besten im Moment bekannten untere Schranke 1,0074 ist leider noch kein effektiver Näherungsalgorithmus bekannt ([GrHoNiPr02]). 30 8 Zusammenfassung 8.1 Rückblick Auf den letzten Seiten habe ich versucht Ihnen das Steiner Problem näher zu bringen. Sie haben gesehen, wie sich ganz einfache Probleme durch kleine Änderungen der Fragestellung zu sehr schweren Problemen geändert haben. Es wurden verschiedene Näherungsfunktionen vorgestellt und gezeigt, dass es auch innerhalb der Näherungsfunktionen große Unterschiede gibt. Ich hoffe ich konnte Ihnen zeigen, warum es wichtig ist, sich auch mit den theoretischen Problemen der Informatik zu beschäftigen, denn es gibt kaum Probleme, die sich nicht in der realen Welt anwenden lassen. Ich konnte viele neue Kenntnisse aus meiner Arbeit gewinnen. Ich habe mich tief in das Thema eingearbeitet und mir gegen Ende eigene Gedanken dazu gemacht. Am schwersten fiel mir, die doch sehr tief geführten Beweise zu verstehen. Es wird in vielen der Quellen ein großes Basiswissen vorausgesetzt. Danach einen eigenen Ansatz zu finden war sehr schwer und der gefundene Ansatz ist bestimmt nicht optimal, doch war seine Entwicklung für mich sehr interessant und lehrreich. Die Implementierung einiger der vorgestellten Funktionen am Ende dieser Arbeit hat mir sehr viel Spass gemacht, ich konnte dadurch Strecken- und Zeitmessungen durchführen. So wurde mir bestätigt, wie sich die einzelnen Funktionen (nicht nur in der Theorie) unterscheiden. 31 9 Anhang 9.1 Implementierung Nachdem ich mich mit den Algorithmen in der Theorie beschäftigt hatte, wollte ich einige nun in Java programmieren, um ihr Laufzeitverhalten unter realen Bedingungen zu testen. Dabei stellte ich fest, dass sich manche Teile der Algorithmen zwar sehr leicht formulieren lassen, aber die Umsetzung in eine Programmiersprache nicht einfach ist. 9.1.1 Klassendiagramm Abbildung 9.1: Klassen Übersicht 32 René Trumpp Wasser für die Wüste 9.1.2 Quelltexte Graph Klasse 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 public class Graph { protected Vector nodeList ; protected double [][] edgeMatrix ; protected static int NO_EDGE = -1; public Graph () { nodeList = new Vector (); edgeMatrix = new double [0][0]; } protected void ge ne rat eRa nd om ( int numberOfNodes ) { int x , y ; boolean isTerminal ; int n u m b e r O f T e r m i n a l s = 0; Random rand = new Random (); nodeList . r e m o v e A l l E l e m e n t s (); if ( numberOfNodes < 2) numberOfNodes = 2; if ( numberOfNodes > 500) numberOfNodes = 500; // generate numberOfNodes -2 random nodes // can be terminals or steiner nodes for ( int i = 0; i < numberOfNodes - 2; i ++) { x = rand . nextInt (500); y = rand . nextInt (500); isTerminal = rand . nextBoolean (); addNode ( new Node (x , y , isTerminal )); if ( isTerminal ) n u m b e r O f T e r m i n a l s ++; } // We need at least 2 nodes for ( int i = 0; i < 2; i ++) { x = rand . nextInt (500); y = rand . nextInt (500); isTerminal = rand . nextBoolean (); if ( n u m b e r O f T e r m i n a l s < 2) { addNode ( new Node (x , y , true )); n u m b e r O f T e r m i n a l s ++; } else { addNode ( new Node (x , y , isTerminal )); } } g e n e r a t e A l l E d g e s (); } protected void g e n e r a t e A l l E d g e s () { for ( int i = 0; i < nodeList . size (); i ++) { for ( int j = i + 1; j < nodeList . size (); j ++) { addEdge (i , j ); } } } protected void addNode ( Node node ) { nodeList . add ( node ); e x p a n d E d g e M a t r i x (); } protected void e x p a n d E d g e M a t r i x () { double [][] newEdgeMatrix = new double [ edgeMatrix . length + 1][ edgeMatrix . length + 1]; for ( int i = 0; i < edgeMatrix . length ; i ++) { for ( int j = 0; j < edgeMatrix . length ; j ++) { newEdgeMatrix [ i ][ j ] = edgeMatrix [ i ][ j ]; } } for ( int i = 0; i < edgeMatrix . length + 1; i ++) { newEdgeMatrix [ edgeMatrix . length ][ i ] = NO_EDGE ; newEdgeMatrix [ i ][ edgeMatrix . length ] = NO_EDGE ; } edgeMatrix = newEdgeMatrix ; } 33 René Trumpp 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 Wasser für die Wüste protected void removeNode ( int nodeNr ) { if ( nodeNr < nodeList . size ()) nodeList . remove ( nodeNr ); } protected void addEdge ( int from , int to , double length ) { if ( from > nodeList . size () || from < 0 || to > nodeList . size () || to < 0 || length <= 0) return ; edgeMatrix [ from ][ to ] = length ; edgeMatrix [ to ][ from ] = length ; } protected void addEdge ( int from , int to ) { if ( from > nodeList . size () || from < 0 || to > nodeList . size () || to < 0) return ; double length = Math . sqrt ( Math . pow ( ((( Node ) nodeList . get ( from )). getX () - (( Node ) nodeList . get ( to )) . getX ()) , 2) + Math . pow ( ((( Node ) nodeList . get ( from )). getY () - (( Node ) nodeList . get ( to )). getY ()) , 2)); edgeMatrix [ from ][ to ] = length ; edgeMatrix [ to ][ from ] = length ; } protected void removeEdge ( int from , int to ) { if ( from > nodeList . size () || from < 0 || to > nodeList . size () || to < 0) return ; edgeMatrix [ from ][ to ] = NO_EDGE ; edgeMatrix [ to ][ from ] = NO_EDGE ; } public boolean load ( String fileName ) { if (!(( new File ( fileName )). exists ())) { System . out . println ( " Can not read file " + fileName + " , does not exist " ); return false ; } nodeList . r e m o v e A l l E l e m e n t s (); edgeMatrix = new double [0][0]; int numberOfEdges = 0; String line ; int linenumber = 0; try { F i l eI n p u t St r e a m fin = new F i l eI n p u tS t r e a m ( fileName ); Bu ffe re dRe ad er inputFile = new Bu ff ere dR ead er ( new I n p u t S t r e a m R e a d e r ( fin )); while (( line = inputFile . readLine ()) != null ) { linenumber ++; if (! line . toLowerCase (). startsWith ( " \\\\ " )) { // Node if ( line . toLowerCase (). startsWith ( " n " )) { line = line . trim (). substring (2 , line . length () - 1); String linesplit [] = line . split ( " ," ); if ( linesplit . length < 2 || linesplit . length > 3) { System . out . println ( " Malformed inputfile , line " + linenumber ); return false ; } double x ; double y ; try { x = new Double ( linesplit [0]). doubleValue (); y = new Double ( linesplit [1]). doubleValue (); } catch ( Exception e ) { System . out . println ( " Malformed inputfile , line " + linenumber ); return false ; } boolean isTerminal = true ; if ( linesplit . length == 3) { if ( linesplit [2]. e q u a l s I g n o r e C a s e ( " s " )) { isTerminal = false ; } } addNode ( new Node (x , y , isTerminal )); 34 René Trumpp 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 Wasser für die Wüste } else // Edge if ( line . toLowerCase (). startsWith ( " e " )) { line = line . trim (). substring (2 , line . length () - 1); String linesplit [] = line . split ( " ," ); if ( linesplit . length < 2 || linesplit . length > 3) { System . out . println ( " Malformed inputfile , line " + linenumber ); return false ; } int x ; int y ; double length ; try { x = new Integer ( linesplit [0]). intValue (); y = new Integer ( linesplit [1]). intValue (); } catch ( Exception e ) { System . out . println ( " Malformed inputfile , line " + linenumber ); return false ; } if ( x >= nodeList . size () || y >= nodeList . size ()) { System . out . println ( " Malformed inputfile , line " + linenumber ); return false ; } if ( linesplit . length == 3) { try { length = new Double ( linesplit [2]). doubleValue (); addEdge (x , y , length ); numberOfEdges ++; } catch ( Exception e ) { System . out . println ( " Malformed inputfile , line " + linenumber ); return false ; } } else { addEdge (x , y ); numberOfEdges ++; } } } } } catch ( Exception e ) { e . pr i n t St a c k T ra c e (); return false ; } if ( nodeList . size () == 0) { System . out . println ( " Malformed inputfile , no nodes in file " ); return false ; } int n u m b e r O f T e r m i n a l s = 0; for ( int i = 0; i < nodeList . size (); i ++) { if ((( Node ) nodeList . get ( i )). isTerminal ()) n u m b e r O f T e r m i n a l s ++; } if ( n u m b e r O f T e r m i n a l s < 2) { System . out . println ( " Malformed inputfile , not enough terminals in file " ); return false ; } if ( numberOfEdges == 0) { g e n e r a t e A l l E d g e s (); } return true ; } public boolean save ( String fileName ) { if ((( new File ( fileName )). exists ())) { System . out . println ( " Can not write file " + fileName + " , already exists " ); return false ; } F i l e O u t p u t S t r e a m fout ; try { fout = new F i l e O u t p u t S t r e a m ( fileName ); Bu ffe re dWr it er outFile = new Bu ff ere dWr it er ( new O u t p u t S t r e a m W r i t e r ( fout )); 35 René Trumpp 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 Wasser für die Wüste for ( int i = 0; i < nodeList . size (); i ++) { outFile . write ( " \\\\ Knoten Nr . " + i + " :\ n " ); Node node = ( Node ) nodeList . get ( i ); outFile . write ( " N ( " + node . getX () + " ," + node . getY ()); if ( node . isTerminal ()) { outFile . write ( " ,T " ); } else { outFile . write ( " ,S " ); } outFile . write ( " )\ n " ); } int edgecounter = 0; for ( int i = 1; i < nodeList . size (); i ++) { for ( int j = 0; j < i ; j ++) { if ( edgeMatrix [ i ][ j ] != NO_EDGE ) { outFile . write ( " \\\\ Kante Nr . " + edgecounter + " :\ n " ); outFile . write ( " E ( " + i + " ," + j + " ," + edgeMatrix [ i ][ j ] + " )\ n " ); edgecounter ++; } } } outFile . close (); fout . close (); } catch ( F i l e N o t F o u n d E x c e p t i o n e ) { e . pr i n t St a c k T ra c e (); System . out . println ( " Can not write file " + fileName ); return false ; } catch ( IOException e ) { e . pr i n t St a c k T ra c e (); System . out . println ( " Can not write file " + fileName ); return false ; } return true ; } public double getLength () { double length = 0; for ( int i = 1; i < nodeList . size (); i ++) { for ( int j = 0; j < i ; j ++) { if ( edgeMatrix [ i ][ j ] != NO_EDGE ) { length += edgeMatrix [ i ][ j ]; } } } return length ; } public int g e t N u m b e r O f E d g e s () { int numberOfEdges = 0; for ( int i = 1; i < nodeList . size (); i ++) { for ( int j = 0; j < i ; j ++) { if ( edgeMatrix [ i ][ j ] != NO_EDGE ) { numberOfEdges ++; } } } return numberOfEdges ; } public void draw ( String filepath ) { draw ( filepath , 1); } public void draw ( String filepath , int scalefactor ) { double minx = Double . MAX_VALUE , maxx = 0 , miny = Double . MAX_VALUE , maxy = 0; for ( int i = 0; i < nodeList . size (); i ++) { Node node = (( Node ) nodeList . get ( i )); if ( node . getX () < minx ) minx = node . getX (); if ( node . getY () < miny ) miny = node . getY (); if ( node . getX () > maxx ) maxx = node . getX (); if ( node . getY () > maxy ) maxy = node . getY (); } int shiftfactorx = (( int ) (0 - minx + 10)) * scalefactor ; int shiftfactory = (( int ) (0 - miny + 10)) * scalefactor ; int sizex = (( int ) ( maxx + 10)) * scalefactor ; int sizey = (( int ) ( maxy + 10)) * scalefactor ; BufferedImage img = new BufferedImage ( sizex , sizey , 36 René Trumpp 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 Wasser für die Wüste BufferedImage . TYPE_INT_RGB ); Graphics graphic = img . getGraphics (); graphic . setColor ( Color . WHITE ); graphic . fillRect (0 , 0 , sizex - 1 , sizey - 1); graphic . setFont ( new Font ( " Serif " , Font . BOLD , 4 * scalefactor )); // Draw Edges for ( int i = 1; i < nodeList . size (); i ++) { for ( int j = 0; j < i ; j ++) { if ( edgeMatrix [ i ][ j ] != NO_EDGE ) { Node from = ( Node ) nodeList . get ( i ); Node to = ( Node ) nodeList . get ( j ); double length = edgeMatrix [ i ][ j ]; from . drawLineTo ( to , graphic , scalefactor , shiftfactorx , shiftfactory , length ); } } } // Draw Nodes for ( int i = 0; i < nodeList . size (); i ++) { (( Node ) nodeList . get ( i )). draw ( graphic , shiftfactorx , shiftfactory , scalefactor , i ); } B y t e A r r a y O u t p u t S t r e a m out = new B y t e A r r a y O u t p u t S t r e a m (0 xfff ); J P E G I m a g e E n c o d e r encoder = JPEGCodec . c r e a t e J P E G E n c o d e r ( out ); J P EG E n c o de P a r a m param ; param = encoder . g e t D e f a u l t J P E G E n c o d e P a r a m ( img ); param . setQuality (1 f , true ); try { encoder . encode ( img , param ); F i l e O u t p u t S t r e a m fos = new F i l e O u t p u t S t r e a m ( filepath ); fos . write ( out . toByteArray ()); fos . close (); out . close (); } catch ( I m a g e F o r m a t E x c e p t i o n e ) { e . pr i n t St a c k T ra c e (); } catch ( IOException e ) { e . pr i n t St a c k T ra c e (); } } int g e t N u m b e r O f T e r m i n a l s () { int counter = 0; for ( int i = 0; i < nodeList . size (); i ++) { if ((( Node ) nodeList . get ( i )). isTerminal ()) counter ++; } return counter ; } int g e t N u m b e r O f S t e i n e r N o d e s () { return nodeList . size () - g e t N u m b e r O f T e r m i n a l s (); } } Node Klasse 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Node { double x ; double y ; boolean isTerminal ; public Node ( double x , double y ) { this (x , y , true ); } public Node ( double x , double y , boolean isTerminal ) { this . x = x ; this . y = y ; this . isTerminal = isTerminal ; } public void draw ( Graphics graphic , int shiftfactorx , int shiftfactory , int scalefactor , int nodeNr ) { if ( isTerminal ) { graphic . setColor ( Color . BLACK ); } else { graphic . setColor ( Color . GRAY ); } int x1 = (( int ) x ) * scalefactor + shiftfactorx - 5 * scalefactor ; int y1 = (( int ) y ) * scalefactor + shiftfactory - 5 * scalefactor ; graphic . fillOval ( x1 , y1 , 7 * scalefactor , 7 * scalefactor ); 37 René Trumpp 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 Wasser für die Wüste graphic . setColor ( Color . BLACK ); graphic . drawString ( " " + nodeNr , x1 , y1 ); } public boolean isTerminal () { return isTerminal ; } public double getX () { return x ; } public double getY () { return y ; } public void setX ( double x ) { this . x = x ; } public void setY ( double y ) { this . y = y ; } public void setTerminal ( boolean isTerminal ) { this . isTerminal = isTerminal ; } public void drawLineTo ( Node to , Graphics graphic , int scalefactor , int shiftfactorx , int shiftfactory , double length ) { graphic . setColor ( Color . BLACK ); int x1 = (( int ) x ) * scalefactor + shiftfactorx - 2 * scalefactor ; int y1 = (( int ) y ) * scalefactor + shiftfactory - 2 * scalefactor ; int x2 = (( int ) to . getX ()) * scalefactor + shiftfactorx - 2 * scalefactor ; int y2 = (( int ) to . getY ()) * scalefactor + shiftfactory - 2 * scalefactor ; graphic . drawLine ( x1 , y1 , x2 , y2 ); graphic . setColor ( Color . GRAY ); graphic . drawString ( " " + Math . rint ( length * 100) / 100.0 , ( x1 + x2 ) / 2 , ( y1 + y2 ) / 2); } } Minimal Spanning Tree Klasse 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class M i n i m a l S p a n n i n g T r e e extends Graph { Graph g ; public M i n i m a l S p a n n i n g T r e e ( Graph g ) { super (); this . g = g ; nodeList = g . nodeList ; edgeMatrix = new double [ nodeList . size ()][ nodeList . size ()]; for ( int i = 0; i < nodeList . size (); i ++) { for ( int j = 0; j < nodeList . size (); j ++) edgeMatrix [ i ][ j ] = NO_EDGE ; } int Vorgaenger [] = calculate (); generateEdges ( Vorgaenger ); } private void generateEdges ( int [] vorgaenger ) for ( int i = 1; i < nodeList . size (); edgeMatrix [ i ][ vorgaenger [ i ]] edgeMatrix [ vorgaenger [ i ]][ i ] } } { i ++) { = g . edgeMatrix [ i ][ vorgaenger [ i ]]; = g . edgeMatrix [ i ][ vorgaenger [ i ]]; private int [] calculate () { boolean S [] = new boolean [ g . nodeList . size ()]; for ( int i = 0; i < g . nodeList . size (); i ++) { S [ i ] = false ; } double Distanz [] = new double [ g . nodeList . size ()]; int Vorgaenger [] = new int [ g . nodeList . size ()]; // Waehle beliebigen Knoten s ; // S ={ s }; S [0] = true ; 38 René Trumpp 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 Wasser für die Wüste // for each v from V \{ s } do { for ( int i = 1; i < g . nodeList . size (); i ++) { // Distanz ( v )= Kante (s , v ); Distanz [ i ] = g . edgeMatrix [0][ i ]; if ( g . edgeMatrix [0][ i ] == NO_EDGE ) Distanz [ i ] = Double . MAX_VALUE ; // Vorgaenger ( v )= s ; Vorgaenger [ i ] = 0; } // Distanz ( s )=0; Distanz [0] = 0; int N u m b e r O f N o d e s I n S = 1; // while S != V do { while ( N u m b e r O f N o d e s I n S != g . nodeList . size ()) { // Finde u from V \ S mit Distanz ( u )= min { Distanz ( v )| v from V \ S }; int u = findMin (S , Distanz ); // S = S +{ u }; S [ u ] = true ; N u m b e r O f N o d e s I n S = 0; for ( int i = 0; i < g . nodeList . size (); i ++) { if ( S [ i ] == true ) N u m b e r O f N o d e s I n S ++; } // for each v from V \ S do { for ( int v = 0; v < g . nodeList . size (); v ++) { if ( S [ v ] == true || g . edgeMatrix [ u ][ v ] == NO_EDGE ) continue ; // if Kante (u , v ) < Distanz ( v ) then { if ( g . edgeMatrix [ u ][ v ] <= Distanz [ v ]) { // Distanz ( v )= Kante (u , v ); Distanz [ v ] = g . edgeMatrix [ u ][ v ]; // ı̈> 1 Vorgnger ( v )= u ; 2 Vorgaenger [ v ] = u ; } } } return Vorgaenger ; } private int findMin ( boolean [] S , double [] Distanz ) { int minNode = 0; double minDistanz = Double . MAX_VALUE ; // Finde u from V \ S mit Distanz ( u )= min { Distanz ( v )| v from V \ S }; for ( int u = 0; u < g . nodeList . size (); u ++) { if ( S [ u ] == true ) continue ; if ( Distanz [ u ] < minDistanz ) { minDistanz = Distanz [ u ]; minNode = u ; } } return minNode ; } } Steiner Baum Trivial Klasse 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class S t e i n e r B a u m T r i v i a l extends Graph { private boolean [] visited ; private double minimalsize ; public S t e i n e r B a u m T r i v i a l ( Graph g ) { minimalsize = Double . MAX_VALUE ; int n u m b e r O f S t e i n e r N o d e s = 0; for ( int i = 0; i < g . nodeList . size (); i ++) { if (!(( Node ) g . nodeList . get ( i )). isTerminal ) n u m b e r O f S t e i n e r N o d e s ++; } visited = new boolean [ n u m b e r O f S t e i n e r N o d e s ]; for ( int i = 0; i < visited . length ; i ++) { visited [ i ] = false ; } ge tS hor te stM ST (g , visited . length - 1); } 39 René Trumpp 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 Wasser für die Wüste private void ge tS hor te stM ST ( Graph g , int deph ) { visited [ deph ] = true ; if ( deph > 0) ge tSh or tes tM ST (g , deph - 1); if ( deph == 0) calculate ( g ); visited [ deph ] = false ; if ( deph > 0) ge tSh or tes tM ST (g , deph - 1); if ( deph == 0) calculate ( g ); } private void calculate ( Graph g ) { Graph g1 = new Graph (); boolean linesToUse [] = new boolean [ g . nodeList . size ()]; int counter = 0; for ( int i = 0; i < g . nodeList . size (); i ++) { Node node = ( Node ) g . nodeList . get ( i ); if ( node . isTerminal ()) { g1 . addNode ( node ); linesToUse [ i ] = true ; continue ; } if ( visited [ counter ]) { g1 . addNode ( node ); linesToUse [ i ] = true ; } else { linesToUse [ i ] = false ; } counter ++; } g1 . edgeMatrix = new double [ g1 . nodeList . size ()][ g1 . nodeList . size ()]; int column = 0; for ( int i = 0; i < g . nodeList . size (); i ++) { if (! linesToUse [ i ]) continue ; int row = 0; for ( int j = 0; j < g . nodeList . size (); j ++) { if (! linesToUse [ j ]) continue ; g1 . edgeMatrix [ row ][ column ] = g . edgeMatrix [ i ][ j ]; row ++; } column ++; } M i n i m a l S p a n n i n g T r e e mst = new M i n i m a l S p a n n i n g T r e e ( g1 ); if ( mst . getLength () < minimalsize ) { minimalsize = mst . getLength (); nodeList = mst . nodeList ; edgeMatrix = mst . edgeMatrix ; } } } Rechtwinkliger Steiner Graph Klasse 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class R e c h t w i n k l i g e r S t e i n e r G r a p h extends Graph { Graph g ; public R e c h t w i n k l i g e r S t e i n e r G r a p h ( Graph g ) { super (); this . g = g ; nodeList = new Vector (); edgeMatrix = new double [0][0]; for ( int i = 0; i < g . nodeList . size (); i ++) { if ((( Node ) g . nodeList . get ( i )). isTerminal ) { nodeList . add (( Node ) g . nodeList . get ( i )); } } int t e r m i n a l N o d e L i s t S i z e = nodeList . size (); double xPos [] = new double [ t e r m i n a l N o d e L i s t S i z e ]; double yPos [] = new double [ t e r m i n a l N o d e L i s t S i z e ]; double xPosSorted [] = new double [ t e r m i n a l N o d e L i s t S i z e ]; double yPosSorted [] = new double [ t e r m i n a l N o d e L i s t S i z e ]; for ( int i = 0; i < nodeList . size (); i ++) { xPos [ i ] = (( Node ) nodeList . get ( i )). getX (); yPos [ i ] = (( Node ) nodeList . get ( i )). getY (); 40 René Trumpp 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 Wasser für die Wüste xPosSorted [ i ] = xPos [ i ]; yPosSorted [ i ] = yPos [ i ]; } // sort entries double temp ; for ( int i = 0; i < xPosSorted . length ; i ++) { for ( int j = i + 1; j < yPosSorted . length ; j ++) { if ( xPosSorted [ i ] > xPosSorted [ j ]) { temp = xPosSorted [ i ]; xPosSorted [ i ] = xPosSorted [ j ]; xPosSorted [ j ] = temp ; } if ( yPosSorted [ i ] > yPosSorted [ j ]) { temp = yPosSorted [ i ]; yPosSorted [ i ] = yPosSorted [ j ]; yPosSorted [ j ] = temp ; } } } // Delete double entries int doubleXPos = xPosSorted . length ; int doubleYPos = yPosSorted . length ; for ( int i = 0; i < xPosSorted . length - 1; i ++) { if ( xPosSorted [ i ] == xPosSorted [ i + 1]) { xPosSorted [ i + 1] = -1; doubleXPos - -; } if ( yPosSorted [ i ] == yPosSorted [ i + 1]) { yPosSorted [ i + 1] = -1; doubleYPos - -; } } double x P o s S o r t e d W i t h o u t D o u b l e s [] = new double [ doubleXPos ]; double y P o s S o r t e d W i t h o u t D o u b l e s [] = new double [ doubleYPos ]; doubleXPos = 0; doubleYPos = 0; for ( int i = 0; i < xPosSorted . length ; i ++) { if ( xPosSorted [ i ] != -1) { x P o s S o r t e d W i t h o u t D o u b l e s [ doubleXPos ] = xPosSorted [ i ]; doubleXPos ++; } if ( yPosSorted [ i ] != -1) { y P o s S o r t e d W i t h o u t D o u b l e s [ doubleYPos ] = yPosSorted [ i ]; doubleYPos ++; } } // Generate Nodes nodeList = new Vector (); for ( int i = 0; i < x P o s S o r t e d W i t h o u t D o u b l e s . length ; i ++) { for ( int j = 0; j < y P o s S o r t e d W i t h o u t D o u b l e s . length ; j ++) { addNode ( new Node ( x P o s S o r t e d W i t h o u t D o u b l e s [ i ] , y P o s S o r t e d W i t h o u t D o u b l e s [ j ] , false )); } } // Mark Terminals for ( int i = 0; i < nodeList . size (); i ++) { Node node = ( Node ) nodeList . get ( i ); for ( int j = 0; j < xPos . length ; j ++) { if ( node . getX () == xPos [ j ] && node . getY () == yPos [ j ]) { node . setTerminal ( true ); } } } // Edges edgeMatrix = new double [ nodeList . size ()][ nodeList . size ()]; for ( int i = 0; i < nodeList . size (); i ++) { for ( int j = 0; j < nodeList . size (); j ++) { edgeMatrix [ i ][ j ] = NO_EDGE ; } } for ( int i = 0; i < x P o s S o r t e d W i t h o u t D o u b l e s . length ; i ++) { for ( int j = 0; j < y P o s S o r t e d W i t h o u t D o u b l e s . length - 1; j ++) { int from = 0; int to = 0; for ( int n = 0; n < nodeList . size (); n ++) { Node node = ( Node ) nodeList . get ( n ); if ( node . getX () == x P o s S o r t e d W i t h o u t D o u b l e s [ i ] && node . getY () == y P o s S o r t e d W i t h o u t D o u b l e s [ j ]) { from = n ; } if ( node . getX () == x P o s S o r t e d W i t h o u t D o u b l e s [ i ] && node . getY () == y P o s S o r t e d W i t h o u t D o u b l e s [ j + 1]) { to = n ; 41 René Trumpp 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 Wasser für die Wüste } } addEdge ( from , to ); } } for ( int i = 0; i < y P o s S o r t e d W i t h o u t D o u b l e s . length ; i ++) { for ( int j = 0; j < x P o s S o r t e d W i t h o u t D o u b l e s . length - 1; j ++) { int from = 0; int to = 0; for ( int n = 0; n < nodeList . size (); n ++) { Node node = ( Node ) nodeList . get ( n ); if ( node . getX () == x P o s S o r t e d W i t h o u t D o u b l e s [ j ] && node . getY () == y P o s S o r t e d W i t h o u t D o u b l e s [ i ]) { from = n ; } if ( node . getX () == x P o s S o r t e d W i t h o u t D o u b l e s [ j + 1] && node . getY () == y P o s S o r t e d W i t h o u t D o u b l e s [ i ]) { to = n ; } } addEdge ( from , to ); } } } } Naeherung Ratio 2 Klasse 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class N a eh e r u n gR a t i o 2 extends Graph { Graph g ; boolean [] visited ; public N a e he r u n gR a t i o 2 ( Graph g ) { this . g = g ; calculate (); } private void calculate () { // Berechne den Di st anz gra ph en D von G DistanceGraph d = new DistanceGraph ( g ); // Berechne den minimal aufspannenden Baum M0 von D M i n i m a l S p a n n i n g T r e e m0 = new M i n i m a l S p a n n i n g T r e e ( d ); // Berechne zu M0 den Teilgraphen G0 von G wie folgt Graph g0 = new Graph (); g0 . nodeList = g . nodeList ; g0 . edgeMatrix = new double [ g . nodeList . size ()][ g . nodeList . size ()]; for ( int i = 0; i < g . nodeList . size (); i ++) { for ( int j = 0; j < g . nodeList . size (); j ++) { g0 . edgeMatrix [ i ][ j ] = NO_EDGE ; } } // Bestimme zu jeder Kante (u , v ) aus M0 einen ^ A¨kurzesten for ( int i = 0; i < m0 . nodeList . size () - 1; i ++) { for ( int j = i + 1; j < m0 . nodeList . size (); j ++) { // Weg p (u , v ) in G , der ihre Laenge hat , setze dann // G0 = U p (u , v ) addPath ( g0 , g , i , j , m0 . edgeMatrix [ i ][ j ]); } } // Berechne den minimal aufspannenden Baum M von G0 M i n i m a l S p a n n i n g T r e e m = new M i n i m a l S p a n n i n g T r e e ( g0 ); // Loesche alle Blaetter aus M , die keine Terminale sind , solange boolean onlyTerminals ; // bis nur noch Terminale Blaetter sind do { onlyTerminals = true ; for ( int i = 0; i < m . nodeList . size (); i ++) { if ((( Node ) m . nodeList . get ( i )). isTerminal ) continue ; int n u m b e r O f N e i g h b o u r s = 0; for ( int j = 0; j < m0 . nodeList . size (); j ++) { if ( m . edgeMatrix [ i ][ j ] != NO_EDGE ) n u m b e r O f N e i g h b o u r s ++; } if ( n u m b e r O f N e i g h b o u r s == 1) { for ( int j = 0; j < m0 . nodeList . size (); j ++) { m . edgeMatrix [ i ][ j ] = NO_EDGE ; m . edgeMatrix [ j ][ i ] = NO_EDGE ; } onlyTerminals = false ; } } } while (! onlyTerminals ); 42 René Trumpp 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 Wasser für die Wüste boolean emptyLines [] = new boolean [ m . nodeList . size ()]; nodeList = new Vector (); for ( int i = 0; i < m . nodeList . size (); i ++) { emptyLines [ i ] = false ; int n u m b e r O f N e i g h b o u r s = 0; for ( int j = 0; j < m0 . nodeList . size (); j ++) { if ( m . edgeMatrix [ i ][ j ] != NO_EDGE ) n u m b e r O f N e i g h b o u r s ++; } if ( n u m b e r O f N e i g h b o u r s != 0) { nodeList . add ( m . nodeList . get ( i )); } else { emptyLines [ i ] = true ; } } = new double [ nodeList . size ()][ nodeList . size ()]; = 0; = 0; i < m . nodeList . size (); i ++) { ( emptyLines [ i ]) continue ; int row = 0; for ( int j = 0; j < m . nodeList . size (); j ++) { if ( emptyLines [ j ]) continue ; edgeMatrix [ row ][ column ] = m . edgeMatrix [ i ][ j ]; row ++; } column ++; edgeMatrix int column for ( int i if } } private void addPath ( Graph g0 , Graph g , int from , int to , double length ) { int vorgaenger [] = searchPath (g , from , to , length ); g0 . edgeMatrix [ to ][ vorgaenger [ to ]] = g . edgeMatrix [ to ][ vorgaenger [ to ]]; g0 . edgeMatrix [ vorgaenger [ to ]][ to ] = g . edgeMatrix [ to ][ vorgaenger [ to ]]; while ( to != from ) { to = vorgaenger [ to ]; g0 . edgeMatrix [ to ][ vorgaenger [ to ]] = g . edgeMatrix [ to ][ vorgaenger [ to ]]; g0 . edgeMatrix [ vorgaenger [ to ]][ to ] = g . edgeMatrix [ to ][ vorgaenger [ to ]]; } } private int [] searchPath ( Graph g2 , int from , int to , double maxlength ) { // Modifizierter MST Algorithmus boolean S [] = new boolean [ g2 . nodeList . size ()]; for ( int i = 0; i < g2 . nodeList . size (); i ++) { S [ i ] = false ; } double Distanz [] = new double [ g2 . nodeList . size ()]; int Vorgaenger [] = new int [ g2 . nodeList . size ()]; // Waehle Startknoten s ; // S ={ s }; S [ from ] = true ; // for each v from V \{ s } do { for ( int i = 0; i < g2 . nodeList . size (); i ++) { // Distanz ( v )= Kante (s , v ); Distanz [ i ] = g2 . edgeMatrix [ from ][ i ]; if ( g2 . edgeMatrix [ from ][ i ] == NO_EDGE ) Distanz [ i ] = Double . MAX_VALUE ; // Vorgaenger ( v )= s ; Vorgaenger [ i ] = from ; } // Distanz ( s )=0; Distanz [ from ] = 0; int N u m b e r O f N o d e s I n S = 1; // while S != V do { while ( N u m b e r O f N o d e s I n S != g2 . nodeList . size ()) { // Finde u from V \ S mit Distanz ( u )= min { Distanz ( v )| v from V \ S }; int u = findMin (S , Distanz , g2 ); // S = S +{ u }; S [ u ] = true ; N u m b e r O f N o d e s I n S = 0; for ( int i = 0; i < g2 . nodeList . size (); i ++) { if ( S [ i ] == true ) N u m b e r O f N o d e s I n S ++; } // for each v from V \ S do { for ( int v = 0; v < g2 . nodeList . size (); v ++) { if ( S [ v ] == true || g2 . edgeMatrix [ u ][ v ] == NO_EDGE ) continue ; // if Kante (u , v ) < Distanz ( v ) then { 43 René Trumpp 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 Wasser für die Wüste if ( Distanz [ u ] + g2 . edgeMatrix [ u ][ v ] <= Distanz [ v ]) { // Distanz ( v )= Kante (u , v ); Distanz [ v ] = Distanz [ u ] + g2 . edgeMatrix [ u ][ v ]; // Vorgaenger ( v )= u ; Vorgaenger [ v ] = u ; if ( Distanz [ v ] == maxlength && v == to ) return Vorgaenger ; } } } return Vorgaenger ; } private int findMin ( boolean [] S , double [] Distanz , Graph g ) { int minNode = 0; double minDistanz = Double . MAX_VALUE ; // Finde u from V \ S mit Distanz ( u )= min { Distanz ( v )| v from V \ S }; for ( int u = 0; u < g . nodeList . size (); u ++) { if ( S [ u ] == true ) continue ; if ( Distanz [ u ] < minDistanz ) { minDistanz = Distanz [ u ]; minNode = u ; } } return minNode ; } } Distance Graph Klasse 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class DistanceGraph extends Graph { public DistanceGraph ( Graph g ) { nodeList = g . nodeList ; edgeMatrix = new double [ g . nodeList . size ()][ g . nodeList . size ()]; for ( int i = 0; i < g . nodeList . size (); i ++) { for ( int j = 0; j < g . nodeList . size (); j ++) { edgeMatrix [ i ][ j ] = NO_EDGE ; } } for ( int i = 0; i < g . nodeList . size (); i ++) { double distance [] = calculate (g , i ); distance [ i ] = NO_EDGE ; for ( int j = 0; j < g . nodeList . size (); j ++) { edgeMatrix [ i ][ j ] = distance [ j ]; } } } private double [] calculate ( Graph g , int nodeNr ) { // Modifizierter MST Algorithmus boolean S [] = new boolean [ g . nodeList . size ()]; for ( int i = 0; i < g . nodeList . size (); i ++) { S [ i ] = false ; } double Distanz [] = new double [ g . nodeList . size ()]; int Vorgaenger [] = new int [ g . nodeList . size ()]; // Waehle Startknoten s ; // S ={ s }; S [ nodeNr ] = true ; // for each v from V \{ s } do { for ( int i = 0; i < g . nodeList . size (); i ++) { // Distanz ( v )= Kante (s , v ); Distanz [ i ] = g . edgeMatrix [ nodeNr ][ i ]; if ( g . edgeMatrix [ nodeNr ][ i ] == NO_EDGE ) Distanz [ i ] = Double . MAX_VALUE ; // Vorgaenger ( v )= s ; Vorgaenger [ i ] = nodeNr ; } // Distanz ( s )=0; Distanz [ nodeNr ] = 0; int N u m b e r O f N o d e s I n S = 1; // while S != V do { while ( N u m b e r O f N o d e s I n S != g . nodeList . size ()) { // Finde u from V \ S mit Distanz ( u )= min { Distanz ( v )| v from V \ S }; 44 René Trumpp 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 Wasser für die Wüste int u = findMin (S , Distanz , g ); // S = S +{ u }; S [ u ] = true ; N u m b e r O f N o d e s I n S = 0; for ( int i = 0; i < g . nodeList . size (); i ++) { if ( S [ i ] == true ) N u m b e r O f N o d e s I n S ++; } // for each v from V \ S do { for ( int v = 0; v < g . nodeList . size (); v ++) { if ( S [ v ] == true || g . edgeMatrix [ u ][ v ] == NO_EDGE ) continue ; // if Kante (u , v ) < Distanz ( v ) then { if ( Distanz [ u ] + g . edgeMatrix [ u ][ v ] <= Distanz [ v ]) { // Distanz ( v )= Kante (u , v ); Distanz [ v ] = Distanz [ u ] + g . edgeMatrix [ u ][ v ]; // Vorgaenger ( v )= u ; Vorgaenger [ v ] = u ; } } } return Distanz ; } private int findMin ( boolean [] S , double [] Distanz , Graph g ) { int minNode = 0; double minDistanz = Double . MAX_VALUE ; // Finde u from V \ S mit Distanz ( u )= min { Distanz ( v )| v from V \ S }; for ( int u = 0; u < g . nodeList . size (); u ++) { if ( S [ u ] == true ) continue ; if ( Distanz [ u ] < minDistanz ) { minDistanz = Distanz [ u ]; minNode = u ; } } return minNode ; } } 45 Literaturverzeichnis [ProSte02] Hans Jürgen Prömel und Angelika Steger The Steiner Tree Problem Vieweg 2002, ISBN 3-528-06762-4 [Wag03] Christian Wagenknecht Algorithmen und Komplexität Fachbuchverlag Leipzig 2003, ISBN 3-446-22314-2 [AstBai02] Alexander Asteroth und Christel Baier Theoretische Informatik Pearson Studium 2002, ISBN 3-8273-7033-7 [HoMoUl02] John E. Hopcroft, Rajeev Motwani und Jeffrey D. Ullmann Einführung in die Automatentheorie, Formale Sprachen und Komplexitätstheorie (2. Auflage) Pearson Studium 2002, ISBN 3-8273-7033-7 [CoLeRiSt01] Thomas H. Corman, Charles E. Leiserson, Ronald L. Rivest und Clifford Stein Introduction to Algorithms The MIT Press 2001, ISBN 0-2620-3293-7 [GrHoNiPr02] Clemens Gröpl, Stefan Hougardy, Till Nierhoff und Hans Jürgen Prömel Steiner Trees in Uniformly Quasi-Bipartite Graphs Information Processing Letters 83, 2002 www.informatik.hu-berlin.de/∼proemel/publikationen/GHNP01d.pdf 46 Abbildungsverzeichnis 1.1 Das Königreich von König Algorimas . . . 1.2 Landkarte als Graph . . . . . . . . . . . . 1.3 Gerichtete und ungerichtete Kante . . . . 1.4 Gewichtete Kante bzw. gewichteter Graph . . . . 3 4 5 5 2.1 2.2 Beispiel für einen aufspannenden Baum . . . . . . . . . . . . . . . Weiteres Beispiel für einen aufspannenden Baum . . . . . . . . . . 7 8 3.1 3.2 3.3 Lösung für König Algorimas erste Aufgabe bei 4 Punkten . . . . . Lösung für 4 Punkte mit zusätzlichem Punkt in der Mitte . . . . Landkarte mit zusätzlichen Verteilerstationen . . . . . . . . . . . 11 11 12 4.1 4.2 Eine mögliche Lösung des rechtwinkligen Steinerproblems . . . . . Gitternetz mit Höhe und Breite 3 . . . . . . . . . . . . . . . . . . 14 15 5.1 Landkarte mit beliebigen zusätzlichen Verteilerstationen . . . . . 16 6.1 Transformiertes 3SAT-Problem . . . . . . . . . . . . . . . . . . . 19 7.1 7.2 7.3 7.4 7.5 7.6 7.7 Zyklische Durchmusterung . . . . . Beispielgraph G . . . . . . . . . . . Distanzgraph D zu G (Schritt 1) . . M’ von D (Schritt 2) . . . . . . . . Teilgraph G’ (Schritt 3) . . . . . . M von G’ (Schritt 4) . . . . . . . . Fertiger Steiner Baum B (Schritt 5) . . . . . . . 25 27 27 27 27 27 27 9.1 Klassen Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47