Spezielle Algorithmen (SAl) Prof. Jürgen Sauer Spezielle Algorithmen Skriptum zur Vorlesung im SS 2010 1 Spezielle Algorithmen (SAl) 2 Spezielle Algorithmen (SAl) Inhaltsverzeichnis Literaturverzeichnis.............................................................................................................................................. 7 1. GRUNDLEGENDE KONZEPTE ......................................................................................................... 9 1.1 Datenstruktur und Algorithmus.................................................................................................................... 9 1.2 Algorithmische Grundkonzepte................................................................................................................... 10 1.2.1 Algorithmenbegriffe ................................................................................................................................ 10 1.2.2 Terminierung und Determinismus ........................................................................................................... 10 1.2.3 Algorithmenbausteine .............................................................................................................................. 12 1.2.4 Paradigmen der Algorithmenbeschreibung.............................................................................................. 14 1.2.5 Paradigmen und Programmiersprachen ................................................................................................... 15 1.2.6 Formale Eigenschaften von Algorithmen ................................................................................................ 16 1.2.7 Komplexität.............................................................................................................................................. 21 1.3 Daten und Datenstrukturen ......................................................................................................................... 30 1.3.1 Datentyp................................................................................................................................................... 30 1.3.2 Datenstruktur............................................................................................................................................ 30 1.3.3 Relationen und Ordnungen ...................................................................................................................... 35 1.3.4 Klassifikation von Datenstrukturen ......................................................................................................... 39 1.3.5 Definitionsmethoden für Datenstrukturen................................................................................................ 49 2. GRAPHEN UND GRAPHENALGORITHMEN.................................................................................. 53 2.1 Einführung .................................................................................................................................................... 53 2.1.1 Grundlagen............................................................................................................................................... 53 2.1.2 Definitionen ............................................................................................................................................. 57 2.1.3 Darstellung in Rechnerprogrammen ........................................................................................................ 63 2.2 Durchlaufen von Graphen ........................................................................................................................... 68 2.2.1 Tiefensuche (depth-first search)............................................................................................................... 68 2.2.2 Breitensuche (breadth-first search) .......................................................................................................... 88 2.2.3 Implementierung ...................................................................................................................................... 91 2.3 Topologischer Sort ........................................................................................................................................ 95 2.4 Transitive Hülle............................................................................................................................................. 98 2.4.1 Berechnung der Erreichbarkeit mittels Matrixmultiplikation .................................................................. 98 2.4.2 Warshalls Algorithmus zur Bestimmung der Wegematrix .................................................................... 100 2.4.3 Floyds Algorithmus zur Bestimmung der Abstandsmatrix.................................................................... 101 2.5 Kürzeste Wege............................................................................................................................................. 102 2.5.1 Die Datenstrukturen Graph, Vertex, Edge für die Berechnung kürzester Wege ................................... 102 2.5.2 Kürzeste Pfade in gerichteten, ungewichteten Graphen. ....................................................................... 103 2.5.3 Berechnung der kürzesten Pfadlängen in gewichteten Graphen (Algorithmus von Dijkstra) ............... 107 2.5.4 Berechnung der kürzesten Pfadlängen in gewichteten Graphen mit negativen Kosten......................... 112 2.5.5 Berechnung der kürzesten Pfadlängen in gewichteten, azyklischen Graphen ....................................... 113 2.5.6 All pairs shorted Path............................................................................................................................. 115 2.6 Minimale Spannbäume............................................................................................................................... 117 2.6.1 Der Algorithmus von Prim..................................................................................................................... 117 2.6.2 Der Algorithmus von Kruskal................................................................................................................ 120 2.7 Netzwerkflüsse............................................................................................................................................. 123 2.7.1 Maximale Flüsse .................................................................................................................................... 123 3 Spezielle Algorithmen (SAl) 2.7.2 Konsteminimale Flüsse .......................................................................................................................... 134 2.8 Matching ...................................................................................................................................................... 136 2.8.1 Ausgangspunkt, Motivierendes Beispiel, Definitionen, maximales Matching ...................................... 136 2.8.2 Bipartiter Graph ..................................................................................................................................... 140 2.8.3 Maximale Zuordnung im allgemeinen Fall............................................................................................ 145 3. PROBLEMLÖSUNG DURCH SUCHEN......................................................................................... 148 3.1 Lösen von Problemen ................................................................................................................................. 148 3.1.1 Repräsentation von Zustandsräumen ..................................................................................................... 148 3.1.2 Repräsentation von Problemreduktionen ............................................................................................... 150 3.1.3 And / Or-Graphen .................................................................................................................................. 151 3.2 Uninformierte Suchstrategien in Zustandsraumrepräsenta-tionen ....................................................... 152 3.3 Informierte (heuristische) Suche in Zustandsraumrepräsenta-tionen................................................... 155 3.3.1 Geordnete Zustandsraum-Suche ............................................................................................................ 155 3.3.2 Gierige Bestensuche............................................................................................................................... 156 3.3.3 A*-Suche ............................................................................................................................................... 156 3.4 Lokale Suchalgorithmen und Optimierungsprobleme ............................................................................ 157 3.4.1 Hillclimbing-Suche ................................................................................................................................ 157 3.4.2 Simulated Annealing und seine Geschwister ......................................................................................... 158 3.4.3 Genetische Algorithmen ........................................................................................................................ 162 3.5 Lokale Suche in stetigen Räumen.............................................................................................................. 163 3.5.1 Gradientenverfahren .............................................................................................................................. 163 3.5.2 Zufallsanstieg (Monte-Carlo Zufalls-Suche) ......................................................................................... 164 3.5.3 Newton’sches Verfahren........................................................................................................................ 165 3.6 Spiele ............................................................................................................................................................ 166 3.6.1 Algorithmen für das Suchen in Spielbäumen......................................................................................... 166 3.6.2 Spielbäume in Minimax-Notation.......................................................................................................... 166 3.6.3 Alpha-Beta-Pruning ............................................................................................................................... 172 3.7 Probleme unter Rand- oder Nebenbedingungen...................................................................................... 177 4. NEURONALE NETZE ..................................................................................................................... 178 4.1 Einführung .................................................................................................................................................. 178 4.1.1 Biologische bzw. psychologische Grundlagen ...................................................................................... 178 4.1.2 Überführung in ein datentechnisches Verarbeitungsmodell (NN)......................................................... 179 4.2 Wie arbeiten Neuronale Netzwerke?......................................................................................................... 180 4.2.1 Allgemeiner Aufbau neuronaler Netze .................................................................................................. 180 4.2.2 Informationsverarbeitung in neuronalen Netzen.................................................................................... 187 4.2.3 Mathematische Grundlagen zum Lernverhalten NN ............................................................................. 189 4.3 Topologien NN............................................................................................................................................. 191 4.3.1 NN-Modelle ........................................................................................................................................... 191 4.3.2 Netzwerktypen ....................................................................................................................................... 192 4.4 Das Perzeptron ............................................................................................................................................ 194 4.4.1 Singlelayer-Perzeptron (SLP) ................................................................................................................ 194 4.4.2 Multilayer-Perzeptron (MLP) ................................................................................................................ 201 4.5 Support Vector Machines........................................................................................................................... 205 4.5.1 Linear separierbare Probleme ................................................................................................................ 205 4 Spezielle Algorithmen (SAl) 4.5.2 Nicht linear trennbare Klassifizierer ...................................................................................................... 211 4.5.3 SVM....................................................................................................................................................... 217 4.6 Backpropagation Netzwerke...................................................................................................................... 220 4.6.1 Beschreibung.......................................................................................................................................... 220 4.6.2 Grundlagen............................................................................................................................................. 222 4.6.3 Festlegen von Parameterwerten: Lernrate ε und Momentum............................................................... 228 4.6.4 Testphase (Recall).................................................................................................................................. 229 5. KONZEPT UND ARBEITSWEISE VON GENETISCHE ALGORITHMEN .................................... 230 5.1 Evolution und Genetik................................................................................................................................ 230 5.2 Prinzipielle Arbeitsweise genetischer Algorithmen.................................................................................. 231 5.2.1 Arbeitsweise von genetischen Algorithmen........................................................................................... 231 4.2.2 Struktur eines genetischen Algorithmus ................................................................................................ 232 5.3 Phasen eines genetischen Suchalgorithmus .............................................................................................. 233 5.3.1 Modellierung.......................................................................................................................................... 233 5.3.2 Konfigurierung....................................................................................................................................... 233 5.3.3 Die Realisierungsphase .......................................................................................................................... 238 5.3.4 Verfahrensbewertung bzw. Verfahrensverbesserung ............................................................................ 242 5.3.5 Güte eines genetischen Algorithmus...................................................................................................... 242 5.5 Anwendungen.............................................................................................................................................. 243 5.5.1 Ein genetischer Algorithmus für das Problem des Handlungs-reisenden .............................................. 243 5.5.2 Ein genetischer Algorithmus für das Packproblem................................................................................ 245 5.5.3 Genetic Function Finder ........................................................................................................................ 246 5 Spezielle Algorithmen (SAl) 6 Spezielle Algorithmen (SAl) Literaturverzeichnis Sauer, Jürgen: Programmieren in Java, Skriptum zur Vorlesung im WS 2005/2007 http://fbim.fh-regensburg.de/~saj39122/pgj/index.html Sauer, Jürgen: Programmieren in C++, Skriptum zur Vorlesung im SS 2006 http://fbim.fh-regensburg.de/~saj39122/pgc/index.html Sauer, Jürgen: Datenbanken, Skriptum zur Vorlesung im SS 2007 http://fbim.fh-regensburg.de/~saj39122/dbnew/index.html Sauer, Jürgen: Operations Research, Skriptum zur Vorlesung im SS 2010 http://fbim.fh-regensburg.de/~saj39122/or/index.html Sauer, Jürgen: Algorithmen und Datenstrukturen, Skriptum zur Vorlesung im SS 2009 http://fbim.fh-regensburg.de/~saj39122/ad/index.html Sauer, Jürgen: Neuronale Netze, Fuzzy Control Systeme und Genetische Algorithmen, Skriptum zur Vorlesung im WS 2009 / 2010 http://fbim.fh-regensburg.de/~saj39122/NN/index.html Böhringer, Bernhard u. Chiopris, Carlo u. Futo, Ivan: Wissensbasierte System emit Prolog, Addison-Wesley, Bonn … 1988 Sedgewick, Robert: Algorithmen in Java, 3.überarbeitete Auflage, Pearson Studium, München …. , 2003 Sedgewick, Robert: Algorithmen in C++, Teil 1 bis 4, 3.überarbeitete Auflage, Pearson Studium, München …. , 2002 Wirth, Nicklaus: Algorithmen und Datenstrukturen, 2. duchgesehene Auflage, Teubner, Stuttgart 1979 Ottmann, Thomas und Widmayer, Peter: Algorithmen und Datenstrukturen, BI Wissenschaftsverlag, Mannheim /Wien /Zürich 1990 Weiss, Marc Allen: Data Structures and Algorithm Analysis in Java, Pearson, Boston …., 2007 Saake, Gunter und Sattler, Kai Uwe: Algorithmen und Datenstrukturen, dpunkt.verlag, 2. überarbeitete Auflage, Heidelberg, 2004 Maurer, H.: Datenstrukturen und Programmierverfahren, Teubner,Stuttgart 1974 Krüger, Guido und Stark, Thomas: Handbuch der Java-Programmierung, 5. Auflage, HTML-Ausgabe 5.0.1, Addison-Wesley, 2007 Ullenboom, Christian: Java ist auch eine Insel, 7. aktualisierte Auflage, HTMLVersion 7 Spezielle Algorithmen (SAl) Ammeraal, Leendert: Programmdesign und Algorithmrn in C, Hanser Verlag München Wien, 1989 Russel, Stuart und Norvig, Peter: Künstliche Intelligenz, 2. Auflage, Pearson Studium, 2003 Sedgewick, Robert und Wayne, Kevin: Introduction to Programming in Java, Pearson, Boston …, 2008 Ford, William und Topp, William: Data Structures with C++, Prentice Hall. Englewood Cliffs, 1996 8 Spezielle Algorithmen (SAl) 1. Grundlegende Konzepte 1.1 Datenstruktur und Algorithmus In den 50er Jahren bedeutete „Rechnen“ auf einem Computer weitgehend „numerisches Lösen“ wissenschaftlich-technischer Probleme. Kontroll- und Datenstrukturen waren sehr einfach und brauchten daher nicht weiter untersucht werden. Ein bedeutender Anstoß kam hier aus der kommerziellen Datenverarbeitung (DV). So führte hier bspw. die Frage des Zugriffs auf ein Element einer endlichen Menge zu einer großen Sammlung von Algorithmen 1, die grundlegende Aufgaben der DV lösen. Dabei ergab sich: Die Leistungsfähigkeit dieser Lösungen (Programme) ist wesentlich bestimmt durch geeignete Organisationsformen für die zu bearbeitenden Daten. Der Datentyp oder die Datenstruktur und die zugehörigen Algorithmen 2 sind demnach ein entscheidender Bestandteil eines leistungsfähigen Programms. Datenstrukturen 3 und Programmierverfahren bilden eine Einheit. Bei der Formulierung des Lösungswegs ist man auf eine bestimmte Darstellung der Daten festgelegt. Rein gefühlsmäßig könnte man sagen: Daten gehen den Algorithmen voraus. Programmieren führt direkt zum Denken in Datenstrukturen, um Datenelemente, die zueinander in Beziehung stehen, zusammen zu fassen. Mit Hilfe solcher Datenstrukturen ist es möglich, sich auf die relevanten Eigenschaften der Umwelt zu konzentrieren und eigene Modelle zu bilden. Die Leistung des Rechners wird dabei vom reinen Zahlenrechnen auf das weitaus höhere Niveau der „Verarbeitung von Daten“ angehoben Datenstrukturen und Algorithmen bilden die wesentlichen Bestandteile der Programmierung. Ein erster Versuch soll diese zentralen Begriffe so festlegen (bzw. abgrenzen): Datenstruktur Ein auf Daten anwendbares Ordnungsschema (z.B. ein Datensatz oder Array). Mit der Hilfe von Datenstrukturen lassen sich die Daten interpretieren und spezifische Operationen auf ihnen ausführen Algorithmus Verarbeitungsvorschrift, die angibt, wie Eingabe(daten) schrittweise mit Hilfe von Anweisungen auf Rechnern in Ausgabe(daten) umgewandelt werden. Für die Lösung eines Problems existieren meist mehrere Algorithmen, die sich in der Länge sowie der für die Ausführung benötigte Zeit unterscheiden. Programm und Programmiersprache Ein Programm ist die Formulierung eines Algorithmus und seiner Datenbereiche in einer Programmiersprache. Eine Programmiersprache erlaubt, Algorithmen präzise zu beschreiben. Insbesondere legen sie fest: 1 D. E. Knuth hat einen großen Teil dieses Wissens in "The Art of Computer Programming" zusammengefaßt http://de.wikipedia.org/wiki/Algorithmen 3 http://de.wikipedia.org/wiki/Datenstrukturen 2 9 Spezielle Algorithmen (SAl) - die elementaren Operationen - die Möglichkeiten zu ihrer Kombination - die zulässigen Datenbereiche 1.2 Algorithmische Grundkonzepte 1.2.1 Algorithmenbegriffe Algorithmen im Alltag Gegeben ist ein Problem. Eine Handlungsvorschrift, deren mechanisches Befolgen - ohne Verständnis des Problems - mit sinnvollen Eingabedaten - zur Lösung des Problems führt, wird Algorithmus genannt. Ein Problem, für dessen Lösung ein Algorithmus existiert, heißt berechenbar. Bsp.: - Zerlegung handwerklicher Arbeiten in einzelne Schritte - Kochrezepte - Verfahren zur schriftlichen Multiplikation - Algorithmen zur Bestimmung des größten gemeinsamen Teiles zweier natürlichen Zahlen - Bestimmung eines Schaltjahres - Spielregeln Der intuitive Algorithmenbegriff Ein Algorithmus ist eine präzise (d.h. in einer festgelegten Sprache abgefasste) endliche Verarbeitungsvorschrift, die genau festlegt, wie die Instanzen einer Klasse von Problemen gelöst werden. Ein Algorithmus liefert eine Funktion (Abbildung), die festlegt, wie aus einer zulässigen Eingabe die Ausgabe ermittelt werden kann. Ein Algorithmus (in der EDV) ist - ein Lösungsschritt für eine Problemklasse (konkretes Problem wird durch Eingabeparameter identifiziert) - geeignet für die Implementierung als Rechnerpogramm - endliche Folge von elementaren, ausführbaren Instruktionen Verarbeitungsschritten 1.2.2 Terminierung und Determinismus Abgeleitet vom intuitiven Algorithmenbegriff spielen bei der Konzeption von Algorithmen die Begriffe Terminierung, Determinismus und Vollständigkeit eine Rolle: Terminierung Ein Algorithmus heißt terminierend, wenn er (bei jeder erlaubten Eingabe von Parametern) nach endlich vielen Schritten abbricht. 10 Spezielle Algorithmen (SAl) Determinismus Ein Algorithmus hat einen deterministischen Ablauf 4, wenn er eine eindeutige Schrittfolge besitzt. Der Algorithmus läuft bei jedem Ablauf mit den gleichen Eingaben durch dieselbe Berechnung. Ein Algorithmus liefert ein determiniertes Ergebnis, wenn bei vorgegebener Eingabe (auch bei mehrfacher Durchführung) stets ein eindeutiges Ergebnis erreicht wird. Nicht deterministische Algorithmen mit determiniertem Ergebnis heißen determiniert. Nicht deterministische Algorithmen können zu einem determiniertem Ergebnis führen, z.B.: 1. Nimm eine Zahl x ungleich Null 2. Entweder: Addiere das Dreifache von x zu x und teile das Ergebnis durch den Anfangswert von x Oder: Subtrahiere 4 von x und subtrahiere das Ergebnis von x 3. Schreibe das Ergebnis auf Vollständigkeit Alle Fälle, die bei korrekten Eingabedaten auftreten können, werden berücksichtigt. Bsp.: Nichtvollständige Algorithmen (1) Wähle zufällig eine Zahl x (2) Wähle zufällig eine Zahl y (3) Das Ergebnis ist x/y Was ist, wenn y == 0 sein sollte Nicht terminierender Algorithmus (1) Wähle zufällig eine Zahl x (2) Ist die Zahl gerade, wiederhole ab (1) (3) Ist die Zahl ungerade, wiederhole ab (1) Nicht determinierter Algorithmus 60 64 (1) Wähle zufällig eine natürliche Zahl zwischen 2 und 2 (2) Prüfe, ob die Zahl eine Primzahl ist. (3) Falls nicht, wiederhole ab 1. Das Ergenis ist immer eine Primzahl, aber nicht die gleiche, daher ist der Algorithmus nicht determiniert. Deterministische, terminierende Algorithmen definieren jeweils eine Ein/Ausgabefunktion: f : Eingabewerte -> Ausgabewerte Algorithmen geben eine konstruktiv ausführbare Beschreibung dieser Funktion, die Funktion heißt Bedeutung (Semantik) des Algorithmus. Es kann mehrere verschiedene Algorithmen mit der gleichen Bedeutung geben. 4 http://de.wikipedia.org/wiki/Deterministischer_Algorithmus 11 Spezielle Algorithmen (SAl) 1.2.3 Algorithmenbausteine Gängige Bausteine zur Beschreibung bzw. Ausführung von Algorithmen sind: - elementare Operationen - sequentielle Ausführung (ein Prozessor) Der Sequenzoperator ist „;“. Sequenzen ohne Sequenzoperator sind häufig durchnummeriert und können schrittweise verfeinert werden, z.B: (1) Koche Wasser (2) Gib Kaffepulver in Tasse (3) Fülle Wasser in Tasse (2) kann verfeinert werden zu: Öffne Kaffeedose; Entnehme Löffel von Kaffee; Kippe Löffel in Tasse; Schließe Kaffeedose; - parallele Ausführung - bedingte Ausführung Die Auswahl / Selektion kann allgemein so formuliert werden: falls Bedingung, dann Schritt bzw. falls Bedingung dann Schritt a sonst Schritt b „falls ... dann ... sonst ...“ entspricht in Programmiersprachen den Konstrukten: if Bedingung then ... else … fi if Bedingung then … else …endif if (Bedingung) … else … - Schleife (Iteration) Dafür schreibt man allgemein wiederhole Schritte bis Abbruchkriterium Häufig findet man auch die Variante solange Bedingung führe aus Schritte bzw. die Iteration über festen Bereich wiederhole für Bereichsangabe Schleifenrumpf 12 Spezielle Algorithmen (SAl) Diese Schleifenkonstrukte Konstrukten: wiederhole ... bis ... solange … führe aus wiederhole für entsprechen jeweils den Programmiersprachen- repeat ... until … do … while ... while … do ... while ( ... ) ... for each ... do … for ... do … for ( ... ) ... - Unterprogramm (Teilalgoritmus) - Rekursion 5 Eine Funktion (mit oder ohne Rückgabewert, mit oder ohne Parameter) darf in der Deklaration ihres Rumpfes den eigenen Namen verwenden. Hierdurch kommt es zu einem rekursiven Aufruf. Typischerweise werden die aktuellen Parameter so modifiziert, daß die Problemgröße schrumpft, damit nach mehrmaligem Wiederholen dieses Prinzips kein weiterer Aufruf erforderlich ist und die Rekursion abbrechen kann. Übung 12_1: Rekursion 5 http://de.wikipedia.org/wiki/Rekursion 13 Spezielle Algorithmen (SAl) 1.2.4 Paradigmen der Algorithmenbeschreibung Ein Algorithmenparadigma legt Denkmuster fest, die einer Beschreibung eines Algorithmus zugrunde liegen. Faßt man einen Algorithmus als Beschreibung eines allgemeinen Verfahrens unter Verwendung ausführbarer elementarer Schritte auf, dann gibt es 2 grundlegende Arten, Schritte von Algorithmen zu notieren: - Applikative Algorithmen sind eine Verallgemeinerung der Funktionsauswertung mathematisch notierter Funktionen. In ihnen spielt die Rekursion 6 eine wesentliche Rolle. - Imperative Algorithmen basieren auf einem einfachen Maschinenmodell mit gespeicherten und änderbaren Werten. Hier werden primär Schleifen und Alternativen als Kontrollbausteine eingesetzt. In der Informatik sind darüber hinaus noch folgende Paradigmen wichtig: - Objektorientierte Algorithmen. In einem objektorientierten Algorithmus werden Datenstrukturen und Methoden zu einer Klasse zusammengefasst. Von jeder Klasse können Objekte gemäß der Datenstruktur erstellt und über die Methode manipuliert werden. Das objektorientierte Paradigma ist kein Algorithmenparadigma im engeren Sinne, da es sich um ein Paradigma zur Strukturierung von Algorithmen handelt, das sowohl mit applikativen, imperativen und logischen Konzepten zusammen eingesetzt werden kann. - logische (deduktive) Algorithmen. Ein logischer Algorithmus führt Berechnungen durch, indem er aus Fakten und Regeln durch Ableitungen in einem logischen Kalkül weitere Fakten ausweist. Deduktive Algorithmen bestehen aus einer Reihe logischer Aussagen und einem Auswertungsalgorithmus für Anfragen. Die Kombination „logisches Programm, Auswertungsalgorithmus und konkrete Anfrage, legen die Berechnungsfolge fest. Übung 12_2: Prolog - Genetische Algorithmen. Ein Genetischer Algorithmus (GA) ahmt die Strategien aus der Evolutionstheorie nach, um zu einem Problem eine möglicht gute Lösung zu finden. Vorrangig versuchen Genetsche Algorithmen 7 eine Analogie zu der biologischen Selektion und Genetik zum Entwurf robuster Suchmethoden herzustellen. Übung 12_3: Genetische Algorithmen - Neuronale Netze (NN) bilden das Analogon zur Informationsverarbeitung in Nervenzellen und damit insbesondere in Gehirnen. Neuronen eines NN entsprechen den Nervervenzellen. Ein NN ist aus vielen Neuronen zusammengesetzt, die miteinander durch Nervenbahnen verbunden sind. Übung 12_4: Neuronale Netze 8 6 vgl. Skriptum: Algorithmen und Datenstrukturen SS09, 3.3 http://de.wikipedia.org/wiki/Genetische_Algorithmen 8 vgl. 4. 7 14 Spezielle Algorithmen (SAl) 1.2.5 Paradigmen und Programmiersprachen Zu den Paradigmen korrespondieren jeweils Programmiersprachen, die diesen Ansatz realisieren. Moderne Programmiersprachen umfassen oft Ansätze mehrerer Paradigmen. So ist bspw. Java bzw. C++ objektorientiert 9, umfasst aber auch imperative und applikative Elemente. Bezogen auf die Umsetzung von Algorithmen durch Programmiersprachen unterscheidet man zwischen prinzipiellen Programmierstilen, die verschiedene Programmiersprachen mehr oder weniger unterstützen. - Imperative (prozedurale Sprachen), z.B. Fortran, Pascal, Modula und C, orientieren sich am Ablauf von Algorithmen, die einen (globalen Zustand transformieren. Modularisierung geschieht auf der Ebene einzelner Prozeduren. - Logische und funktionale Sprachen, z.B. Prolog 10 und LISP 11, stützen sich auf symbolische Grundlagen, Prädikate oder Funktionen, die formal ausgewertet werden. In Prolog ist ein Programm eine Menge logischer Formeln, der Ablauf eines Programms ein logischer Beweis im Resolutionskalkül. In LISP werden Funktionen aufgerufen - Objekt-orientierte Sprachen 12 C++, Smalltalk, Java) orientieren sich an den informationstragenden Objekten, die Methoden zur Verfügung stellen, lokal ihre Informationen zu ändern und Nachrichten an andere Objekte zusenden. Die kleinsten Einheiten sind Objekte, auf Programmiereinheiten Klassen. Algorithmische Funktionalitäten sind weitgehend in Methoden versteckt. Übung 12_5: Imperative und logische Lösung des 8-Damen Problems. - Genetische Algorithmen, neuronale Netze passen nicht direkt zum klassischen Algorithmenbegriff, obwohl sie häufig als Algorithmenparadigma bezeichnet werden. Eine passende Bezeichnung wäre hier Programmierparadigma, da Programme erzeugt werden, ohne dass ein Lösungsalgorithmus angegeben wird. 9 Vgl. Skriptum zur Vorlesung im WS 2005 / 2006: Programmieren in Java, 1. bzw. Skriptum zur Vorlesung im SS 2006: Programmieren in C++ 10 http://de.wikipedia.org/wiki/Prolog_(Programmiersprache) 11 http://de.wikipedia.org/wiki/LISP 12 http://de.wikipedia.org/wiki/Objektorientierte_Programmierung 15 Spezielle Algorithmen (SAl) 1.2.6 Formale Eigenschaften von Algorithmen 1.2.6.1 Korrektheit, Terminierung, Hoare-Kalkül, Halteproblem 1.2.6.1.1 Korrektheit, Terminierung Die wichtigste formale Eigenschaft eines Algorithmus ist die Korrektheit. Dazu muß gezeigt werden, daß der Algorithmus die jeweils gestellte Aufgabe richtig löst. Man kann die Korrektheit eines Algorithmus im Allg. nicht durch Testen an ausgewählten Beispielen nachweisen13: Durch Testen kann lediglich nachgewiesen werden, dass sich ein Programm für endlich viele Eingaben korrekt verhält. Durch eine Verifikation kann nachgewiesen werden, dass sich das Programm für alle Eingaben korrekt verhält. Bei der Zusicherungsmethode sind zwischen den Statements sogenannte Zusicherungen eingesetzt, die eine Aussage darstellen über die momentane Beziehung zwischen den Variablen. Typischerweise gibt man Zusicherungen als Kommentare vor. Programmverifikation ist der Nachweis, dass die Zusicherungen für ein Programm tatsächlich gelten. Sie entspricht der Durchführung eines mathematischen Beweises (einer Ableitung). Gezeigt wird damit: Das entsprechende Programm ist korrekt bzgl. seiner Spezifikation. /* P */ while (b) { /* P && b */ … /* P */ } /* P && !b */ Die Schleifeninvariante P muß eine Aussage über das in der Schleife errechnete Resultat R enthalten: P ∧ ¬B ⇒ R Zusicherungen enthalten boolsche Ausdrücke, von denen der Programmierer annimmt, dass sie an entsprechender Stelle gelten. Beginnend mit der ersten, offensichtlich richtigen Zusicherung lässt sich als letzte Zusicherung eine Aussage über das berechnete Ergebnis durch Anwendung der Korrektheitsformel 14 ableiten: { P } A { Q } P und Q sind Zusicherungen P ist die pre-condition (Vorbedingung), beschreibt die Bedingungen (constraints). Q ist die post-condition (Nachbedingung), beschreibt den Zustand nach Ausführung der Methode Die Korrektheitsformel bedeutet: Jede Ausführung von A, bei der zu Beginn P erfüllt ist, terminiert in einem Zustand, in dem Q erfüllt ist. 13 E. Dijkstra formulierte das so: Man kann durch Testen die Anwesenheit von Fehlern, aber nicht die Abwesenheit von Fehlern nachweisen. 14 Robert Floyd hatte 1967 die Idee den Kanten von Flussdiagrammen Prädikate zuzuordnen, um Korrektheitsbeweise zu führen. C.A.R. Hoare entwickelte die Idee weiter, indem er Programme mit "Zusicherungen" anreicherte. Er entwickelte das nach ihm benannte "Hoare Tripel" 16 Spezielle Algorithmen (SAl) Die Korrektheitsformel bestimmt partielle Korrektheit : "Wenn P beim Start von A erfüllt ist, und A terminiert, dann wird am Ende Q gelten". Für die Terminierung gilt folgende Formel: { P} A. Sie bedeutet: "Wenn P beim Start von A erfüllt ist, wird A terminieren. Partielle Korrektheit und Terminierung führen zur totale Korrektheit. Totale Korrektheit ist eine stärkere Anforderung an das Programm. Bsp.: 1. Partielle Korrektheit nicht aber totale Korrektheit zeigt {true} while (x!=0) x = x-1; {x==0}, da keine Terminierung bzgl. x < 0. 2. Die Hoare-Formel {x>0} while (x > 0) x = x+1; {false} terminiert nie. Sie ist partiell korrekt, aber nicht total korrekt. Generell drückt die Gültigkeit von {P} A {false} Nichtterminierung aus, d.h. {P} A {false} ist partiell korrekt, A terminmiert aber nicht, für alle Anfangszustände, die P erfüllen. 1.2.6.1.2 Hoare-Kalkül Das Hoare Kalkül umfasst eine Menge von Regeln, die sich aus Prämissen und Schlussfolgerung zusammensetzen: Prämisse1 Prämisse2 … Prämissen --------------Konklusion Mit dem Hoare Kalkül kann partielle (und evtl. totale) Korrektheit eines Programms nachgewiesen werden: - Zerlege den Algorithmus in seine einzelnen Anweisungen und füge vor (und nach) jeder Ausführung geeignete Vor- und Nachbedingungen ein. - Zeige, dass die einzelnen Anweisungen korrekt sind - Beweise die Korrektheit des gesamten Algorithmus aus der Korrektheit der einzelnen Aussagen. Die grundlegende Idee von Hoare zum konstruktiven Beweis partieller und totaler Korrektheit ist: Leite (rückwärts schreitend) ausgehend von der (gewünschten) Nachbedingung die Vorbedingung ab. 1.2.6.1.3 Halteproblem Das Halteproblem kann durch die folgende Fragestellung beschrieben werden: „Gibt es ein Programm, das für ein beliebiges anderes Programm entscheidet, ob es für eine bestimmte Eingabe in eine Endlosschleife gerät oder nicht?“ Das allgemeine Halteproblem drückt offenbar folgende Frage aus: „Hält Algorithmus x bei der Eingabe von y?“ 17 Spezielle Algorithmen (SAl) Anschaulicher Beweis der Unentscheidbarkeit des Halteproblems Annahme. Es gibt eine Maschine (Algorithmus) STOP mit 2 Eingaben: „Algorithmentext x und eine Eingabe y“ und 2 Ausgaben: - JA: x stoppt bei der Eingabe von y - NEIN: x stoppt nicht bei der Eingabe von y x JA STOP y NEIN Mit dieser Maschine STOP kann man eine Maschine SELTSAM konstruieren: SELTSAM JA x x x OK NEIN Die Eingabe von x wird getestet, ob x bei der Eingabe von x stoppt. Im JA-Fall wird in eine Endlosschleife gegangen, die nie anhält. Im NEIN-Fall hält SELTSAM mit der Anzeige OK an. Es folgt nun die Eingabe von SELTSAM (für sich selbst) mit der Frage: „Hält SELTSAM bei der Eingabe von SELTSAM?“ 1. Wenn JA, wird die JA-Anweisung von STOP angelaufen und SELTSAM gerät in eine Endlosschleife, hält also nicht (Widerspruch!) 2. Wenn NEIN, so wird der NEIN-Ausgang von STOP angelaufen, und SELTSAM stoppt mit OK (Widerspruch!) Der Widerspruch folgt aus der Annahme, dass eine STOP-Maschine existiert, was verneint werden muß. Nicht entscheidbare (berechenbare) Probleme Das Halteproblem ist ein Bsp. für ein „semantisches“ Problem von Algorithmen, nämlich ein Problem der folgenden Art: Kann man anhand eines Programmtextes entscheiden, ob die berechnete Funktion (Semantik) eine bestimmte Eigenschaft hat. Die Algorithmentheorie (Satz von Rice) hat dazu folgende Aussage gegeben: Jede nicht triviale semantische Eigenschaft von Algorithmen ist nicht entscheidbar. Nicht entscheidbar sind u.a. folgende Probleme: 1. Ist die Funktion überall definiert? 2. Berechnen 2 gegebene Algorithmen dieselbe Funktion? 3. Ist ein gegebener Algorithmus korrekt, d.h. berechnet er die gegebene (gewünschte) Funktion? Das bedeutet nicht, dass man solche Fragen nicht im Einzelfall entscheiden könnte. Es ist jedoch prinzipell unmöglich, eine allgemeine Methode hierfür zu finden, also 18 Spezielle Algorithmen (SAl) z.B. eine Algorithmus, der die Korrektheit aller Algorithmen nachweist (und damit auch seine eigene). 1.2.6.2 Effizienz Die zweite wichtige Eigenschaft eines Algorithmus ist seine Effizienz. Die wichtigsten Maße für die Effizienz sind der zur Ausführung des Algorithmus benötigte Speicherplatz und die benötigte Rechenzeit (Laufzeit): 1. Man kann die Laufzeit durch Implementierung des Algorithmus in einer Programmiersprache (z.B. C++) auf einem konkreten Rechner für eine Menge repräsentativer Eingaben messen. Bsp.: Implementierung eines einfachen Sortieralgorithmus in C++ mit Messen der CPU-Zeit 15. #include <time.h> // … clock_t start, finish; start = clock(); sort(…); finish = clock(); cout << "sort hat " << double (finish – start) / CLOCKS_PER_SEC << " Sek. benoetigt\n"; // … Solche experimentell ermittelten Meßergebnisse lassen sich nicht oder nur schwer auf andere Implementierungen und andere Rechner übertragen. 2. Aus dieser Schwierigkeit bieten sich 2 Auswege an: 1. Man benutzt einen idealiserenden Modellrechner als Referenzmaschine und mißt die auf diesem Rechner zur Ausführung des Algorithmus benötigte Zeit und benötigten Speicherplatz. Ein in der Literatur 16 zu diesem Zweck häufig benutztes Maschinenmodell ist das der RAM (Random-AccessMaschine). Eine solche Maschine verfügt über einige Register und eine (abzählbar unendliche) Menge einzeln addressierbarer Speicherzellen. Register und Speicherzellen können je eine (im Prinzip) unbeschränkt große (ganze oder reelle) Zahl aufnehmen. Das Befehlsrepertoire für eine RAM ähnelt einer einfachen, herkömmlichen Assemblersprache. Die Kostenmaße Speicherplatz und Laufzeit enthalten dann folgende Bedeutung: Der von einem Algorithmus benötigte Speicherplatz ist die Anzahl der zur Ausführung benötigten RAM-Speicherzellen. Die benötigte Zeit ist die Zahl der ausgeführten RAM-Befehle. 2. Bestimmung einiger für die Effizienz des Algorithmus besonders charakteristischer Parameter 17. Laufzeit und Speicherbedarf eines Algorithmus hängen in der Regel von der Größe der Eingabe ab 18. Man unterscheidet zwischen dem Verhalten im besten Fall, dem Verhalten im Mittel (average case) und dem Verhalten im schlechtesten Fall (worst case). In den meisten Fällen führt man eine worstcase Analyse für die Ausführung eines Algorithmus der Problengröße N durch. Dabei kommt es auf den Speicherplatz nicht an, lediglich die Größenordnung der Laufzeit- und Speicherplatzfunktionen in Abhängigkeit von der Größe der Eingabe N wird bestimmt. Zum Ausdruch dieser Größenordnung hat sich eine besondere Notation eingebürgert: die O-Notation bzw. Big-O-Notation. 15 In Java steht zur Zeitmessung die Methode currentTimeMillis() aus System zur Verfügung. currentTimeMillis bestimmt die Anzahl der Millisekunden, die seit Mitternacht des 1.1.1970 vergangen sind. 16 Vgl. Aho, Hopcroft, Ullman: The Design and Analysis of Computer Algorithms, Addison-Wesley Publishing Company 17 So ist es bspw. üblich, die Laufzeit eines Verfahrens zum Sortieren einer Folge von Schlüsseln durch die Anzahl der dabei ausgeführten Vergleichsoperationen zwischen Schlüsseln und die Anzahl der ausgeführten Bewegungen von den jeweiligen betroffenen Datensätzen zu messen. 18 die im Einheitskostenmaß oder im logarithmischen Kostenmaß gemessen wird 19 Spezielle Algorithmen (SAl) Laufzeit T(N): Die Laufzeit gibt exakt an, wieviel Schritte ein Algorithmus bei einer Eingabelänge N benötigt. T(N) kann man im Rahmen sog. assymptotischer Kostenmaße abschätzen. Für diese Abschätzung existieren die sog. Big-O-Notation (bzw. Ω - und Θ -Notation): Big-O-Notation: Ein Funktion f (N ) heißt von der Ordnung O ( g ( N )) , wenn 2 Konstante c0 und n0 existieren, so dass f ( N ) ≤ c o ⋅ g ( N ) für alle N > n0 . Die Big-O-Notation liefert eine Obergrenze für die Wachstumsrate von Funktionen: f ∈ O(g ) , wenn f höchstens so schnell wie g wächst. Man sagt dann: die Laufzeit eines Algorithmus "T(N) ist O(N)" oder "T(N) ist ein O(N)". Big- Ω -Notation: Ein Funktion f ( N ) heißt von der Ordnung Ω( g ( N )) , wenn 2 Konstante c0 und n0 existieren, so dass f ( N ) ≥ c o ⋅ g ( N ) für alle N > n0 . Die Big- Ω -Notation liefert eine Untergrenze für die Wachstumsrate von Funktionen: f ∈ Ω( g ) , wenn f mindestens so schnell wie g wächst. θ -Notation: Das Laufzeitverhalten eines Algorithmus ist θ ( N ) , falls O( N ) = Ω( N ) . Über θ ( N ) kann das Laufzeitverhalten exakt beschrieben werden. Damit lässt sich der Zeitbedarf eines Algorithmus darstellen als eine Zeitfunktion T ( N ) 19 aus dem Bereich der positiven reellen Zahlen: Ein Algorithmus hat die Komplexität O ( g ) , wenn T ( N ) ∈ O ( g ) gilt. Meistens erfolgt die Abschätzung hinsichtlich der oberen Schranken (Worst Case): Groß-O-Notation. T (N ) c1 g (n) f ∈ Θ(g ) c 2 g ( n) n0 N Abb. 1.2-71: Assymptotische Kostenmaße Zeitbedarf eines Algorithmus: Ist N die Problemgröße, A ein Algorithmus, dann hat ein Algorithmus die Komplexität O( g ) , wenn für den Zeitbedarf von A T A (n) ∈ O( g ) gilt. Wenn nicht explizit anders beschrieben, ist T A (n) maximale Laufzeit für die gegebene Faustregel in der O-Notation 19 falls nicht explizit anders beschrieben, ist T ( N ) die maximale Laufzeit für die gegebene Problemgröße N 20 Spezielle Algorithmen (SAl) Rechenregeln zur O-Notation. ⎧O( f ), falls g ∈ O( f ) Addition: f + g ∈ O(max( f , g )) = ⎨ ⎩O( g ), falls f ∈ O( g ) Die Additionsregel dient zur Bestimmung der Komplexität bei Hintereinanderausführung der Programme Multiplikation: f ⋅ g ∈ O( f ⋅ g ) Die Multiplikationsregel dient zur Bestimmung der Komplexität von ineinandergeschachtelten Schleifen Linearität: f (n) = a ⋅ g (n) + b ∧ Ω(1) ⇒ f ∈ O( g ) 1.2.7 Komplexität Für die algorithmische Lösung eines gegebenen Problems ist es unerläßlich, daß der gefundene Algorithmus das Problem korrekt löst. Darüber hinaus ist es natürlich wünschenswert, daß er dies mit möglichst geringem Aufwand tut. Die Theorie der Komplexität von Algorithmen 20 beschäftigt sich damit, gegebene Algorithmen hinsichtlich ihres Aufwands abzuschätzen und – darüber hinaus – für gegebene Problemklassen anzugeben, mit welchem Mindestaufwand Probleme dieser Klasse gelöst werden können. Meistens geht es bei der Ananlyse der Komplexität von Algorithmen (bzw. Problemklassen) darum, als Maß für den Aufwand eine Funktion anzugeben, wobei f ( Ν) = a bedeutet: „ Bei einem Problem der Größe N ist der Aufwand a“. Die Problemgröße „N“ bezeichnet dabei in der Regel ein grobes Maß für den Umfang einer Eingabe, z.B. die Anzahl der Elemente in der Eingabeliste oder die Größe eines bestimmten Eingabewertes. Der Aufwand „a“ ist in der Regel ein grobes Maß für die Rechenzeit. Die Rechenzeit wird häufig dadurch abgeschätzt, daß man zählt, wie häufig eine bestimmte Operation ausgeführt wird, z.B. Speicherzugriffe, Multiplikationen, Additionen, Vergleiche, etc. Bsp.: Wie oft wird die Wertzuweisung „x = x + 1“ in folgenden Anweisungen ausgeführt? 1. x = x + 1; ............ ..1-mal 2. for (i=1; i <= n; i++) x = x + 1;.. ..n-mal 3. for (i=1; i <= n; i++) for (j = 1; j <= n; j++) x = x + 1;................................... ......... n2-mal Die Aufwandfunktion läßt sich in den wenigsten Fällen exakt bestimmen. Vorherrschende Analysemethoden sind: - Abschätzungen des Aufwands im schlechtesten Fall - Abschätzungen des Aufwands im Mittel Selbst hierfür lassen sich im Allg. keine exakten Angaben machen. Man beschränkt sich dann auf „ungefähres Rechnen in Größenordnungen“. Bsp.: Gegeben: n ≥ 0 a1 , a 2 , a3 ,..., a n ∈ Z Gesucht: Der Index i der (ersten) größten Zahl unter den ai (i=1,...,n) Lösung: max = 1; 20 http://de.wikipedia.org/wiki/Komplexitätstheorie 21 Spezielle Algorithmen (SAl) for (i=2;i<=n;i++) if (amax < ai) max = i Wie oft wird die Anweisung „max = i“ im Mittel ausgeführt (abhängig von n)? Die gesuchte mittlere Anzahl sei Tn. Offenbar gilt: 1 ≤ Tn ≤ n . „max = i“ wird genau dann ausgeführt, wenn ai das größte der Elemente a1 , a 2 , a3 ,..., ai ist. Angenommen wird Gleichverteilung: Für jedes i = 1, ... , n hat jedes der Elemente a1 , a 2 , a3 ,..., a n die gleiche Chance das größte zu sein, d.h.: Bei N Durchläufen wird N/n-mal die Anweisung „max = i“ ausgeführt. Daraus folgt für N ⋅ Tn (Aufwendungen bei N Durchläufen vom „max = i“): N ⋅ Tn = N + N N N 1 1 1 + + ... + = N (1 + + + ... + ) 2 3 n 2 3 n Dies ist Hn, die n-te harmonische Zahl. Für Hn ist keine geschlossene Formel bekannt, jedoch eine ungefähre Abschätzung: Tn = H n ≈ ln n + γ 21. Interessant ist nur, daß Tn logarithmisch von n abhängt. Man schreibt Tn ist „von der Ordnung logn“, die multiplikative und additive Konstante sowie die Basis des Logarithmus bleiben unspezifiziert. Diese sog. (Landau'sche) Big-O-Notation läßt sich mathematisch exakt definieren: f ( n) f (n) = O( g (n)) :⇔ ∃c, n0 ∀n ≥ n0 : f (n) ≤ c ⋅ g (n) , d.h. ist für genügend große n g ( n) durch eine Konstante c beschränkt. „f“ wächst nicht stärker als „g“. Diese Begriffsbildung wendet man bei der Analyse von Algorithmen an, um Aufwandsfunktionen durch Eingabe einer einfachen Vergleichsfunktion abzuschätzen, so daß f (n) = O( g (n)) gilt, also das Wachstum von f durch das von g beschränkt ist. Gebräuchliche Vergleichsfunktionen sind: O-Notation O(1) Aufwand Konstanter Aufwand O(log n) Logarithmischer Aufwand O(n) Linearer Aufwand Problemklasse Einige Suchverfahren für Tabellen („Hashing“) Allgemeine Suchverfahren für Tabellen (Binäre Suche) Sequentielle Suche, Suche in Texten, syntaktische Analyse in Programmen „schlaues Sortieren“, z.B. Quicksort O(n ⋅ log n) O(n 2 ) Quadratischer Aufwand Einige dynamische Optimierungsverfahren, z.B. optimale Suchbäume); „dummes Sortieren“, z.B. Bubble-Sort Multiplikationen Matrix mal Vektor Exponentieller Aufwand Viele Optimierungsprobleme, automatisches Beweisen (im Prädikatenkalkül 1. Stufe) Alle Permutationen O(n k ) für k ≥ 0 O(2 n ) O(n!) Zur Veranschaulichung des Wachstums konnen die folgende Tabellen betrachtet werden: f(N) ldN N 21 N=2 1 2 Eulersche Konstante 24=16 4 16 25=256 8 256 γ = 0.57721566 22 210 10 1024 220 20 1048576 Spezielle Algorithmen (SAl) N ⋅ ldN N2 N3 2N 2 4 8 4 64 256 4096 65536 1808 65536 16777200 ≈ 1077 10240 1048576 ≈ 109 ≈ 10308 20971520 ≈ 1012 ≈ 1018 ≈ 10315653 Unter der Annahme „1 Schritt dauert 1 μs = 10 −6 s folgt für N= N N2 N3 2N 3N N! 10 10 μs 100 μs 1 ms 1 ms 59 ms 3,62 s 20 20 μs 400 μs 8 ms 1s 58 min 771 Jahre 30 30 μs 900 μs 27 ms 18 min 6.5 Jahre 1016 Jahre 40 40 μs 1.6 ms 64 ms 13 Tage 3855 Jahre 1032 Jahre 50 50 μs 2.5 ms 125 ms 36 Jahre 108 Jahre 1049 Jahre 60 60 μs 3.6 ms 216 ms 366 Jahre 1013 Jahre 1066 Jahre Abb. 1.2-2: Polynomial- und Exponentialzeit 1.2.7.1 Laufzeitberechnungen 1.2.7.1.1 Analyse der Laufzeit Die Laufzeit ist bestimmt durch die Anzahl der durchgeführten elementaren Operationen (Grundrechenarten, Vergleiche, Feldzugriffe, Zugriffe auf die Komponenten einer Struktur, etc.) Die Angabe der Laufzeit in Abhängigkeit von konkreten Eingabewerten ist im Allg. nicht möglich oder sehr aufwendig. Daher betrachtet man die Laufzeit häufig in Abhängigkeit von der Größe (dem Umfang) der Eingabe. Definition: T(n) = Anzahl der elementaren Operationen, die zur Bearbeitung einer Eingabe der Größe n bearbeitet werden. Eine Analyse der Laufzeit bezieht sich auf den besten, den schlechtesten und den mittleren Fall: -Tmin(n) = minimale Anzahl der Operationen, die durchgeführt werden, um eine Eingabe der Größe n zu bearbeiten. - Tmax(n) = maximale Anzahl der elementaren Operationen, die durchgeführt werden, um eine Eingabe der Größe n zu bearbeiten. Ist eine Wahrscheinlichkeitsverteilung der Eingabedaten gegeben, kann auch eine mittlere Laufzeit Tmit(n) ermittelt werden. Bsp.: Sequentielle Suche in Folgen Gegeben ist eine Zahl n ≥ 0 , n Zahlen a1, a2, …, an (alle verschieden), eine Zahl b. Gesucht ist der Index i = 1,2,…,n, so dass b == ai, falls ein Index existiert. Andernfalls ist i = n+1. Lösung: i = 1; while (i <= n && b != ai) i = i + 1; Ergebnis hängt von der Eingabe ab, d.h. von n, a1, …, an und b Aufwand der Suche: 1. erfolgreiche Suche (wenn b == ai): S = i Schritte 2. erfolglose Suche S = n+1 Schritte Ziel: globalere Aussagen, die nur von einer einfachen Größe abhängen, z.B. von der Länge n der Folge. 1) Wie groß ist S für gegebenes n im schlechtesten Fall? - im schlechtesten Fall: b wird erst im letzten Schritt gefunden: b = an , S = n im schlechtesten Fall 2) Wie groß ist S für gegebenes n im Mittel - im Mittel 23 Spezielle Algorithmen (SAl) - Wiederholte Anwendung mit verschiedenen Eingaben - Annahme über Häufigkeit: Wie oft wird b an erster, zweiter, … letzter Stelle gefunden? - Insgesamt für N-Suchvorgänge N N N N N n(n + 1) n +1 ⋅ 1 + ⋅ 2 + ... + ⋅ n = (1 + 2 + ... + n ) = ⋅ =N n n n n n 2 2 M n +1 Schritte, also S = im Mittel bei Gleichverteilung - für eine Suche S = N 2 M = 1.2.7.1.2 Asymptotische Analyse der Laufzeit („Big-O“) (Analyse der Komplexität durch Angabe einer Funktion f : N → N als Maß für den Aufwand) Definition: f (n) ist in der Größenordnung von g (n) „ f (n) = O( g (n)) “, falls Konstante c und n0 existieren 22, so dass f (n) ≤ c ⋅ g (n) für n ≥ n0 . f ( n) ist für genügend große n durch eine Konstante c beschränkt, d.h. f wächst g ( n) nicht schneller als g. Ziel der Charakterisierung T (n) = O( g (n)) ist es, eine möglichst einfache Funktion g (n) zu finden. Bspw. ist T (n) = O(n) besser als T (n) = O(5n + 10) . Wünschenswert ist auch die Charakterisierung der Laufzeit mit einer möglichst kleinen Größenordnung. Die O-Natation besteht in der Angabe einer asymptotischen oberen Schranke für die Aufwandsfunktion (Wachstumsgeschwindigkeit bzw. Größenordnung) Vorgehensweise bei der Analyse für Kontrollstrukturen Die Algorithmen werden gemäß ihrer Kontrollstruktur von innen nach außen analysiert. In der Laufzeit, die sich dann ergibt, werden anschließend die Konstanten durch den Übergang zur O-Notation beseitigt. Anweisungen: Anweisungen, die aus einer konstanten Anzahl von elementaren Operationen bestehen, erhalten eine konstante Laufzeit. Sequenz A1,A2,…,An. Werden für die einzelnen Anweisungen, die Laufzeiten T1, T2,…,Tn ermittelt, dann ergibt sich für die Sequenz die Laufzeit T=T1+T2+…+Tn Schleife, die genau n-mal durchlaufen wird, z.B. for-Schleife ohne break: for (i=1;i<=n;i++) A; Wird für A die Laufzeit Ti ermittelt, dann ergibt sich als n Laufzeit für die for-Schleife T = ∑ Ti . Eigentlich müsste zu Ti noch eine Konstante C1 i =1 für i <=n und i++ und C2 für i = 1 hinzugezählt werden. Beim späteren Übergang zur O-Notation würde die Konstanten jedoch wegfallen 23. Fallunterscheidung (mit else-Teil): if (B) A1, else A2; Hier muß zwischen der Laufzeit im besten und schlechtesten Fall unterschieden werden: Tmin=min(T1,T2), Tmax=max(T1,T2), wobei T1 die Laufzeit für A1 und T2 die Laufzeit für A2 ist. Man geht davon aus, dass die Bedingung B konstante Zeit benötigt und wegen des späteren Übergangs zur O-Notation einfachheitshalber nicht mitgezählt werden muß. 22 geeignetes n0 und c müssen angegeben werden, um zu zeigen, dass 23 vgl. Skriptum, 1.2.7 24 f (n) = O( g (n)) gilt Spezielle Algorithmen (SAl) Schleife mit k-maligen Durchläufen, wobei n1<=k<=n2. Diese tritt typischerweise bei while-Schleifen auf. Es muß dann eine Analyse für den besten Fall (k=n1) und den schlechtesten Fall (k=n2) durchgeführt werden. Rekursion mit n → n − 1 : Es ergeben sich rekursive Gleichungen für die Laufzeiten Bsp.: rekursive Fakultätsberechnung int fak(int n) { if (n == 0) return 1; else return n*fak(n-1); } Man erhält folgende Laufzeit: Tn = C 0 für n = 0 Tn = C1 + T (n − 1) ) für n > 0 Durch wiederholtes Einsetzen: T ( n) = C1 + C1 + ... + C1 + C 0 = O ( n) 1442443 n − mal Rekursion mit Teile und Herrsche. f ( x, n ) { if (n == 1) (1) { // Basisfall /* löse Pr oblem direkt , Ergebnis sei loes * / return loes; } else { // Teileschritt /* teile x in 2Teilprobleme x1und x 2 jeweils derGröße n / 2 * / (2) loe1 = f ( x1, n / 2); loe2 = f ( x 2, n / 2); // Herrscheschritt /* Setze Loesung loes für x aus loe1und loe2 zusammen * / (3) return loes; } } Für den Basisfall (1) wird eine konstante Anzahl C0 Operationen angesetzt. (2) und (3) benötigen linearen Aufwand und damit C1 ⋅ n Operationen. T ( n) = C 0 falls n = 1 T (n) = C1 ⋅ n + 2 ⋅ T (n / 2 ) 2425 Durch Einsetzen ergibt sich: T ( n) = C1 n + 2(C1 ⋅ n / 2 + 2T ( n / 4) ) = 2 ⋅ C1 ⋅ n + 4 ⋅ T (n / 4) ) Durch nochmaliges Einsetzen ergibt sich: T ( n) = 3 ⋅ C1 ⋅ n + 8 ⋅ T (n / 8) ) T (n) = log 2 (n) ⋅ C1 ⋅ n + 2 log n T (1) log n Mit 2 = n und T (1) = C 0 erhält man: T (n) = C1 ⋅ n ⋅ log 2 (n) + C 0 ⋅ n 24 n lässt sich ⎣log 2 n ⎦ -mal halbieren. Falls n eine Zweierpotenz ist (d.h. n = 2k), lässt sich n sogar exakt log 2 n = k oft halbieren. n soll der Einfachheit halber hier eine Zweierpotenz sein. 25 Rekurrenzgleichung: Die Analyse rekursiver Algorithmen führt meistens auf eine sog. Rekurrenzgleichung 25 Spezielle Algorithmen (SAl) Lösung von Rekurrenzgleichungen Eine Rekurrenzrelation (kurz Rekurrenz) ist eine Methode, eine Funktion durch einen Ausdruck zu definieren, der die zu definierende Funktion selbst enthält, z.B. Fibonacci-Zahlen 26. Wie löst man Rekurrenzgleichungen? Es gibt 2 Verfahren: Substitutionsmethode bzw. Mastertheorem. Zur Lösung von Rekurrenzgleichungen haben sind 2 Verfahrenstechniken bekannt: Substitutionsmethode bzw. Mastertheorem. Lösung mit der Substitutionsmethode: „Rate eine Lösung“ (z.B. über den Rekursionsbaum) Beweise die Korrektheit der Lösung per Induktion Lösung mit dem Mastertheorem: Mit dem Mastertheorem kann man sehr einfach Rekurrenzen der Form ⎛N⎞ T (n) = 2 ⋅ T ⎜ ⎟ + Θ(n) berechnen ⎝2⎠ Vollständige Induktion Das Beweisverfahren der vollständigen Induktion ist ein Verfahren, mit dem Aussagen über natürliche Zahlen bewiesen werden können. Neben Aussagen über natürliche Zahlen können auch damit gut Aussagen bewiesen werden die - rekursiv definierte Strukturen und - abzählbare Strukturen betreffen. Grundidee: Eine Aussage ist gültig für alle natürlichen Zahlen n ∈ N , wenn man nachweisen kann: Die Aussage gilt für die erste natürliche Zahl n = 1 (Induktionsanfang) Wenn die Aussage für eine natürliche Zahl n gilt, dann gilt sie auch für ihren Nachfolger n+1 (Induktionsschritt) n Einf. Bsp.: S (n) = ∑ i = 1 + 2 + 3 + ... + n = i =1 1 ⋅ n ⋅ (n + 1) 2 Beweis: Induktionsanfang: 1 ⋅ 1 ⋅ (1 + 1) = 1 2 Induktionsschritt: Induktionsvoraussetzung: 1 ⋅ k ⋅ (k + 1) 2 1 ⋅ (k + 1) ⋅ (k +)2 2 k +1 k 1 1 1 i = i + (k + 1) ) = ⋅ k ⋅ (k + 1) + k + 1 = ⋅ (k 2 + k ) + (2k + 2) ∑ ∑ 2 2 2 i =1 i =1 1 1 = k 2 + k + 2k + 2 = (k + 2) ⋅ (k + 1) 2 2 Zu zeigen, dass gilt: ( 26 ) vgl. Skriptum: Algorithmen und Datenstrukturen SS09, 3.2.3 26 Spezielle Algorithmen (SAl) Asymptotische Abschätzung mit dem Master-Theorem Das Mastertheorem hilft bei der Abschätzung der Rekurrenzen der Form T (n) = a ⋅ T (n / b) + f (n) 27. Leider hat es aber zwei Definitionslücken, d.h. es gibt einige Rekurrenzen dieser Form, die nicht mit dem Mastertheorem lösbar sind Master-Theorem - a ≥ 1 und b > 1 sind Konstanten. f (n) ist eine Funktion und T (n) ist über den nichtnegativen ganzen Zahlen durch folgende Rekurrenzgleichung definiert: T (n) = a ⋅ T (n / b ) + f (n) . Interpretiere n / b so, dass entweder ⎣n / b⎦ oder ⎡n / b⎤ - Dann kann T (n) folgendermaßen asymptotisch abgeschätzt werden: ⎧ Θ(n logb a ) falls gilt : ∃ε > 0 mit f (n) = O(n logb a −ε ) ⎪ T (n) = ⎨ Θ n logb a ⋅ log n falls gilt : f (n) = Θ(n logb a ) ⎪Θ( f (n)) falls : ∃ε > 0 mit f (n) = Ω(n logb a +ε ) ∧ ∃c < 1 : ∀n > n0 : a ⋅ f (n / b) ≤ c ⋅ f (n) ⎩ ( ) Anwendung des Theorems an einigen Beispielen 1. T (n) = 9 ⋅ T (n / 3) + n a = 9, b = 3, f (n) = n Da f (n) = O(n log3 9−ε ) mit ε = 1 gilt, kann Fall 1 des Master-Theorems angewendet werden. Somit gilt: T (n) = Θ n log3 9 = Θ n 2 2. T (n) = T (2n / 3) + 1 a = 1, b = 3 / 2 Da n logb a = n log3 / 2 1 = n 0 = 1 ist, gilt f (n) = Θ(n logb a ) = Θ(1) , und es kommt Fall 2 des Master-Theorems zur Anwendung. Somit gilt: T (n) = Θ(n log3 / 2 1 log n) = Θ(log n) 3. T (n) = 3 ⋅ T (n / 4) + n ⋅ log n a = 3, b = 4, f (n) = n log n Es ist n logb a = n log 4 3 = O(n 0.379 ). Somit ist f (n) = Ω(n log 4 3+ε ) mit ε ≈ 0.2 . Weiterhin gilt für hinreichend große n: a ⋅ f (n) = 3 ⋅ (n / 4) ⋅ log(n / 4) ≤ (3 / 4 )n log n . Fall 3 des MasterTheorems kann damit angewandt werden: T (n) = O( f (n)) = Θ(n ⋅ log n) ( ) ( ) Achtung! Es gibt Fälle, in denen die Struktur der Gleichung zu passen „scheint“, aber kein Fall des Master-Theorems existiert, für den alle Bedingungen erfüllt sind. - T ( n) = 2 ⋅ T (n 2 ) + n ⋅ log(n ) -- a = 2, b = 2, f ( n) = n ⋅ log(n) -- Da f ( n) = n ⋅ log(n) asymptotisch größer ist als n b = n ist man versucht, Fall 3 des Master-Theorems anzuwenden -- Allerdings ist f (n) nicht polynomial größer als n, da für alle positive Konstanten ε > 0 das log a Verhältnis f ( n) n logb a = n log n / n = log n asymptotisch kleiner ist als n ε -- Das Master-Theorem kann in diesem Fall nichtr angewendet werden. 27 Solche Rekurrenzen treten oft bei der Analyse sogenannter Divide-and-Conquer-Algorithmen auf. 27 Spezielle Algorithmen (SAl) 1.2.7.2 Berechnungsgrundlagen für rechnerische Komplexität 1.2.7.2.1 System-Effizienz und rechnerische Effizienz Effiziente Algorithmen zeichnen sich aus durch - schnelle Bearbeitungsfolgen (Systemeffizienz) auf unterschiedliche Rechnersystemen. Hier wird die Laufzeit der diversen Suchalgorithmen auf dem Rechner (bzw. verschiedene Rechnersysteme) ermittelt und miteinander verglichen. Die zeitliche Beanspruchung wird über die interne Systemuhr gemessen und ist abhängig vom Rechnertyp - Inanspruchnahme von möglichst wenig (Arbeits-) Speicher - Optimierung wichtiger Leistungsmerkmale, z.B. die Anzahl der Vergleichsbedingungen, die Anzahl der Iterationen, die Anzahl der Anweisungen (, die der Algorithmus benutzt). Die Berechnungskriterien bestimmen die sog. rechnerische Komplexität in einer Datensammlung. Man spricht auch von der rechnerischen Effizienz. 1.2.7.2.2 P- bzw. NP-Probleme Von besonderem Interesse für die Praxis ist der Unterschied zwischen Problemen mit polynomialer Laufzeit (d.h. T ( N ) = O( p( N )) , p = Polynom in N) und solchen mit nicht polynomialer Laufzeit. Probleme mit polynomialer Laufzeit nennt man leicht, alle übrigen Probleme heißen hart (oder unzugänglich). Harte Probleme sind praktisch nicht mehr (wohl aber theoretisch) algorithmisch lösbar, denn selbst für kleine Eingaben benötigt ein derartiger Algorithmus Rechenzeit, die nicht mehr zumutbar ist und leicht ein Menschenalter überschreitet 28. Viele wichtige Problemlösungsverfahren liegen in dem Bereich zwischen leichten und harten Problemen. Man kann nicht zeigen, dass diese Probleme leicht sind, denn es gibt für sie keinen Polynomialzeit-Algorithmus. Umgekehrt kann man auch nicht sagen, dass es sich um harte Probleme handelt. Der Fakt, dass kein PolynomialzeitAlgorithmus gefunden wurde, schließt die Existenz eines solchem Algorithmus nicht aus. Möglicherweise hat man sich bei der Suche danach bisher noch nicht klug genug angestellt. Es wird dann nach seit Jahrzenten erfogloser Forschung angenommen, dass es für diese Probleme keine polynomiellen Algorithmen gibt. Man spricht in diesem Fall von der Klasse der sog. NP-vollständigen Probleme. 29 Es ist heute allgemeine Überzeugung, daß höchstens solche Algorithmen praktikabel sind, deren Laufzeit durch ein Polynom in der Problemgröße beschränkt bleibt. Algorithmen, die exponentielle Schrittzahl erfordern, sind schon für relativ kleine Problemgrößen nicht mehr ausführbar. Problemklasse P Menge aller Probleme, die mit Hilfe deterministischer Algorithmen in polynomialer Zeit gelöst werden können. Bei Lösung durch eine deterministische Turingmaschine 30 kann für jedes Problem dieser Klasseein Polynom der Form n k angegeben werden, das die Zeitkomplexität im Verhältnis zur Eingabe nach oben beschränkt. 28 vgl. Abb. 1.2-2 nichtdeterministisch polynomial 30 ein häufig verwendetes Modell zur exakten Analyse der Zeitkomplexität ist die deterministische Turingmaschine, welche als Abstraktion eines realen Computers angesehen werden kann. 29 28 Spezielle Algorithmen (SAl) P wird auch als Klasse der praktisch lösbaren Probleme bezeichnet. Problemklasse EXP Anstelle eines Polynoms wird hier als obere Schranke für die Zeitkomplexität eine k Funktion 2 n in Abhängigkeit von der Eingabelänge n angegeben. Die Lösung der schwersten Probleme dieser Klasse benötigt also exponentielle Zeit. Weiterhin ist die Klasse P vollständig in EXP enthalten. Problemklasse NP Menge aller Probleme, die nur mit nichtdeterministischen 31 Algorithmen in polynomialer Zeit gelöst werden können. Die Komplexitätsklasse NP 32ist die Menge aller von nichtdeterministischen Turingmaschinen in Polynomialzeit lösbaren Probleme. Da sich Probleme aus P natürlich auch nichtdeterministisch in Polynomialzeit lösen lassen, ist P eine Teilmenge von NP. NP-Vollständigkeit - Allgemein: P ⊆ NP - Jedoch offenes Problem, ob sich NP-Probleme nicht jedoch mit polynomialen Aufwand lösen lassen (also P = NP ) - Beweisbar: Existenz einer Klasse von verwandten Problemen aus NP mit folgender Eigenschaft: Falls eines dieser Proble in polynomialer Zeit mit einem deterministischen Algorithmus gelöst werden könnte, so ist dies für alle Probleme aus NP möglich. - NP-vollständige Probleme: Problem des Handlungsreisenden (TSP), Rucksackproblem Viele praktische Anwendungen für NP-vollständige Probleme, wie z.B. TSP, Rucksackproblem, oder das Problem der Färbung von Graphen, wären im Fall P = NP theoretisch optimal in kurzer Zeit lösbar. P versus NP Unklar ist, ob die beiden Klassen identisch sind, und damit auch, ob die schwersten Probleme der Klasse NP ebenso effizient wie bei der Komplexitätsklasse P gelöst werden können 33. Lösung des Problems Bisher existieren zum exakten Lösen von NP-vollständigen Problemen nur Exponentialzeitalgorithmen auf deterministischen Rechenmaschinen. Andere Klassen von Problemen, welche garantiert mindestens exponentielle Laufzeit 31 Nichtdeterminismus: „Raten“ der richtigen Variante bei mehreren Lösungen http://de.wikipedia.org/wiki/Komplexit%C3%A4tsklasse_NP 33 Ein anschaulisches Problem aus NP, für das nicht klar ist, ob es in P enthalten ist, ist das Rucksackproblem. 32 29 Spezielle Algorithmen (SAl) benötigen, veranschaulichen die derzeit praktische Unlösbarkeit von NPvollständigen Problemen, d.h.: P ≠ NP . Mit dem Beweis von P ≠ NP wären NPProbleme endgültig als schwer lösbar klassifiziert. Übung 12_7: Grenzen der Berechenbarkeit 1.3 Daten und Datenstrukturen 1.3.1 Datentyp Ein Algorithmus verarbeitet Daten. Ein Datentyp soll gleichartige zusammenfassen und die nötigen Basisoperationen zur Verfügung stellen. Ein Datentyp ist durch 2 Angaben festgelegt: Daten 1. Eine Menge von Daten (Werte) 2. Eine Menge von Operationen auf diesen Daten Ein Datentyp 34ist demnach eine Zusammenfassung von Wertebereichen und Operationen zu einer Einheit. 1.3.2 Datenstruktur Komplexe Datentypen, sog. Datenstrukturen, werden durch Kombination primitiver Datentypen gebildet. Sie besitzen selbst spezifische Operationen. Eine Datenstruktur ist ein Datentyp und dient zur Organisation von Daten zur effizienten Unterstützung bestimmter Operationen. Betrachtet wird ein Ausschnitt aus der realen Welt, z.B. die Hörer dieser Vorlesung an einem bestimmten Tag: Juergen Josef Liesel Maria ........ Regensburg ......... ......... ......... ......... Bad Hersfeld ......... ......... ......... ......... 13.11.70 ........ ........ ........ ........ Friedrich-. Ebertstr. 14 .......... .......... .......... .......... Diese Daten können sich zeitlich ändern, z.B. eine Woche später kann eine veränderte Zusammensetzung der Zuhörerschaft vorliegen. Es ist aber deutlich erkennbar: Die Modelldaten entsprechen einem zeitinvarianten Schema: NAME WOHNORT GEBURTSORT GEB.-DATUM STRASSE Diese Feststellung entspricht einem Abstraktionsprozeß und führt zur Datenstruktur. Sie bestimmt den Rahmen (Schema) für die Beschreibung eines Datenbestandes. Der Datenbestand ist dann eine Ansammlung von Datenelementen (Knoten), der Knotentyp ist durch das Schema festgelegt. 34 Vgl. Skriptum Programmieren in Java WS 2005 / 2006: 1.3.4, 1.4.1.3, 2.3 30 Spezielle Algorithmen (SAl) Der Wert eines Knoten k ∈ K wird mit wk bezeichnet und ist ein n ≥ 0 -Tupel von Zeichenfolgen; w i k bezeichnet die i-te Komponente des Knoten. Es gilt wk = ( w1k , w2 k ,...., wn k ) Die Knotenwerte des vorstehenden Beispiels sind: wk1 = (Jürgen____,Regensburg,Bad Hersfeld,....__,Ulmenweg__) wk2 = (Josef_____,Straubing_,......______,....__,........__) wk3 = (Liesel____,....._____,......______,....__,........__) .......... wkn = (__________,__________,____________,______,__________) Welche Operationen sind mit dieser Datenstruktur möglich? Bei der vorliegenden Tabelle sind z.B. Zugriffsfunktionen zum Einfügen, Löschen und Ändern eines Tabelleneintrages mögliche Operationen. Generell bestimmen Datenstrukturen auch die Operationen, die mit diesen Strukturen ausgeführt werden dürfen. Zusammenhänge zwischen den Knoten eines Datenbestandes lassen sich mit Hilfe von Relationen bequem darstellen. Den vorliegenden Datenbestand wird man aus Verarbeitungsgründen bspw. nach einem bestimmten Merkmal anordnen (Ordnungsrelation). Dafür steht hier (im vorliegenden Beispiel) der Name der Studenten: Josef Juergen Liesel Abb. 1.3-1: Einfacher Zusammenhang zwischen Knoten eines Datenbestandes Datenstrukturen bestehen also aus Knoten(den einzelnen Datenobjekten) und Relationen (Verbindungen). Die Verbindungen bestimmen die Struktur des Datenbestandes. Bsp.: 1. An Bayerischen Fachhochschulen sind im Hauptstudium mindestens 2 allgemeinwissenschaftliche Wahlfächer zu absolvieren. Zwischen den einzelnen Fächern, den Dozenten, die diese Fächer betreuen, und den Studenten bestehen Verbindungen. Die Objektmengen der Studenten und die der Dozenten ist nach den Namen sortiert (geordnet). Die Datenstruktur, aus der hervorgeht, welche Vorlesungen die Studenten bei welchen Dozenten hören, ist: 31 Spezielle Algorithmen (SAl) STUDENT FACH DATEN DOZENT DATEN DATEN DATEN DATEN DATEN DATEN DATEN DATEN DATEN geordnet (z.B. nach Matrikelnummern) geordnet (z.B. nach Titel im Vorlesungsverzeichnis) geordnet (z.B. nach Namen) Abb. 1.3-3: Komplexer Zusammenhang zwischen den Knoten eines Datenbestands 2. Ein Gerät soll sich in folgender Form aus verschiedenen Teilen zusammensetzen: Anfangszeiger Analyse Anfangszeiger Vorrat G1, 5 B2, 4 B1, 3 B3, 2 B4, 1 Abb. 1.3-4: Darstellung der Zusammensetzung eines Geräts 32 Spezielle Algorithmen (SAl) 2 Relationen können hier unterschieden werden: 1) Beziehungsverhältnisse eines Knoten zu seinen unmittelbaren Nachfolgeknoten. Die Relation Analyse beschreibt den Aufbau eines Gerätes 2) Die Relation Vorrat gibt die Knoten mit w2k <= 3 an. Die Beschreibung eines Geräts erfordert in der Praxis eine weit komplexere Datenstruktur (größere Knotenzahl, zusätzliche Relationen). 3. Eine Bibliotheksverwaltung soll angeben, welches Buch welcher Student entliehen hat. Es ist ausreichend, Bücher mit dem Namen des Verfassers (z.B. „Stroustrup“) und die Entleiher mit ihrem Vornamen (z.B. „Juergen“, „Josef“) anzugeben. Damit kann die Bibliotheksverwaltung Aussagen, z.B. „Josef hat Stroustrup ausgeliehen“ oder „Juergen hat Goldberg zurückgegeben“ bzw. Fragen, z.B. „welche Bücher hat Juergen ausgeliehen?“, realisieren. In die Bibliothek sind Objekte aufzunehmen, die Bücher repäsentieren, z.B.: Buch „Stroustrup“ Weiterhin muß es Objekte geben, die Personen repräsentieren, z.B.: Person „Juergen“ Falls „Juergen“ Stroustrup“ ausleiht, ergibt sich folgende Darstellung: Person „Juergen“ Buch „Stroustrup“ Abb. 1.3-5: Objekte und ihre Beziehung in der Bibliotheksverwaltung Der Pfeil von „Stroustrup“ nach „Juergen“ zeigt: „Juergen“ ist der Entleiher von „Stroustrup“, der Pfeil von „Juergen“ nach „Stroustrup“ besagt: „Stroustrup“ ist eines der von „Juergen“ entliehenen Bücher. Für mehrere Personen kann sich folgende Darstellung ergeben: 33 Spezielle Algorithmen (SAl) Person Person „Juergen“ Person „Josef“ Buch Buch „Stroustrup“ „Goldberg“ Buch „Lippman“ Abb. 1.3-6: Objektverknüpfungen in der Bibliotheksverwaltung Zur Verbindung der Klasse „Person“ bzw. „Buch“ wird eine Verbindungsstruktur benötigt: Person buecher = Verbindungsstruktur „Juergen“ Buch „Stroustrup“ Abb. 1.3-7: Verbindungsstruktur zwischen den Objekttypen „Person“ und „Buch“ Ein bestimmtes Problem kann auf vielfätige Art in Rechnersystemen abgebildet werden. So kann das vorliegende Problem über verkettete Listen im Arbeitsspeicher oder auf Externspeicher (Dateien) realisiert werden. Die vorliegenden Beispiele können folgendermaßen zusammengefaßt werden: Die Verkörperung einer Datenstruktur wird durch das Paar D = (K,R) definiert. K ist die Knotenmenge (Objektmenge) und R ist eine endliche Menge von binären Relationen über K. 34 Spezielle Algorithmen (SAl) 1.3.3 Relationen und Ordnungen Relationen Zusammenhänge zwischen den Knoten eines Datenbestandes lassen sich mit Hilfe von Relationen bequem darstellen. Eine Relation ist bspw. in folgender Form gegeben: R = {(1,2),(1,3),(2,4),(2,5),(2,6),(3,5),(3,7),(5,7),(6,7)} Diese Relation bezeichnet eine Menge geordneter Paare oder eine Produktmenge M × N . Sind M und N also Mengen, dann nennt man jede Teilmenge M × N eine zweistellige oder binäre Relation über M × N (oder nur über M , wenn M = N ist). Jede binäre Relation auf einer Produktmenge kann durch einen Graphen dargestellt werden, z.B.: 1 3 2 5 6 4 7 Abb. 1.3-8: Ein Graph zur Darstellung einer binären Relation Bsp.: Gegeben ist S (eine Menge der Studenten) und V (eine Menge von Vorlesungen). Die Beziehung ist: x ∈ S hört y ∈V . Diese Beziehung kann man durch die Angabe aller Paare ( x , y ) beschreiben, für die gilt: Student x hört Vorlesung y . Jedes dieser Paare ist Element des kartesischen Produkts S × V der Mengen S und V. Für Relationen sind aus der Mathematik folgende Erklärungen bekannt: 1. Vorgänger und Nachfolger R ist eine Relation über der Menge M. Gilt ( a , b ) ∈ R , dann sagt man: „a ist Vorgänger von b, b ist Nachfolger von a“. Zweckmäßigerweise unterscheidet man in diesem Zusammenhang auch den Definitions- und Bildbereich Def(R) = { x | ( x , y ) ∈ R } Bild(R) = { y | ( x , y ) ∈ R } 2. Inverse Relation (Umkehrrelation) 35 Spezielle Algorithmen (SAl) Relationen sind umkehrbar. Die Beziehungen zwischen 2 Grössen x und y können auch als Beziehung zwischen y und x dargestellt werden, z.B.: Aus „x ist Vater von y“ wird durch Umkehrung „y ist Sohn von x“. Allgemein gilt: R-1 = { (y,x) | ( x , y ) ∈ R } 3. Reflexive Relation ∀ ( x , x ) ∈ R (Für alle Elemente x aus M gilt, x steht in Relation zu x) x ∈M Beschreibt man bspw. die Relation "... ist Teiler von ..." für die Menge M = {2,4,6,12} in einem Grafen, so erhält man: 12 4 6 2 Abb. 1.3-9: Die binäre Relation „... ist Teiler von ... “ Alle Pfeile, die von einer bestimmten Zahl ausgehen und wieder auf diese Zahl verweisen, sind Kennzeichen einer reflexiven Relation ( in der Darstellung sind das Schleifen). Eine Relation, die nicht reflexiv ist, ist antireflexiv oder irreflexiv. 4. Symmetrische Relation Aus (( ( x , y ) ∈ R ) folgt auch (( ( y , x ) ∈ R ). Das läßt sich auch so schreiben: Wenn ein geordnetes Paar (x,y) der Relation R angehört, dann gehört auch das umgekehrte Paar (y,x) ebenfalls dieser Relation an. Bsp.: a) g ist parallel zu h h ist parallel zu g b) g ist senkrecht zu h h ist senkrecht zu g 5. Asymmetrische Relation Solche Relationen sind auch aus dem täglichen Leben bekannt. Es gilt bspw. „x ist Vater von y“ aber nicht gleichzeitig „y ist Vater von x“. Eine binäre Relation ist unter folgenden Bedingungen streng asymetrisch: ∀ ( x , y ) ∈ R → (( y , x ) ∉ R ) ( x , y )∈R 36 Spezielle Algorithmen (SAl) Das läßt sich auch so ausdrücken: Gehört das geordnete Paar (x,y) zur Relation, so gehört das entgegengesetzte Paar (y,x) nicht zur Relation. Gilt für x <> y die vorstehende Relation und für x = y ∀( x , x ) ∈ R , so wird diese binäre Relation "unstreng asymmetrisch" oder "antisymmetrisch" genannt. 6. Transitive Relation Eine binäre Relation ist transitiv, wenn (( ( x , y ) ∈ R ) und (( ( y , z ) ∈ R ) ist, dann ist auch (( ( x , z ) ∈ R ). x hat also y zur Folge und y hat z zur Folge. Somit hat x auch z zur Folge. 7. Äquivalenzrelation Eine binäre Relation ist eine Äquivalenzrelation, wenn sie folgenden Eigenschaften entspricht: - Reflexivität - Transitivität - Symmetrie Bsp.: Die Beziehung "... ist ebenso gross wie ..." ist eine Äquivalenzrelation. 1. Wenn x1 ebenso groß ist wie x2, dann ist x2 ebenso groß wie x1. Die Relation ist symmetrisch. 2. x1 ist ebenso groß wie x1. Die Relation ist reflexiv. 3. Wenn x1 ebenso groß wie x2 und x2 ebenso gross ist wie x3, dann ist x1 ebenso groß wie x3. Die Relation ist transitiv. Klasseneinteilung - Ist eine Äquivalenzrelation R in einer Menge M erklärt, so ist M in Klassen eingeteilt - Jede Klasse enthält Elemente aus M, die untereinander äquivalent sind - Die Einteilung in Klassen beruht auf Mengen M1, M2, ... , Mx, ... , My Für die Teilmengen gilt: Mx ∩ M y = 0 (2) M1 ∪ M 2 ∪....∪ M y = M (1) (3) Mx <> 0 (keine Teilmenge ist die leere Menge) Bsp.: Klasseneinteilungen können sein: - Die Menge der Studenten der FH Regensburg: Äquivalenzrelation "... ist im gleichen Semester wie ..." - Die Menge aller Einwohner einer Stadt in die Klassen der Einwohner, die in der-selben Straße wohnen: Äquivalenzrelation ".. wohnt in der gleichen Strasse wie .." Aufgabe 1. Welche der folgenden Relationen sind transitiv bzw. nicht transitiv? 1) 2) 3) 4) 5) ... ... ... ... ... ist ist ist ist ist der Teiler von .... der Kamerad von ... Bruder von ... deckungsgleich mit ... senkrecht zu ... (transitiv) (transitiv) (transitiv) (transitiv) (nicht transitiv) 2. Welche der folgenden Relationen sind Aequivalenzrelationen? 1) 2) 3) 4) ... ... ... ... gehört dem gleichen Sportverein an ... hat denselben Geburtsort wie ... wohnt in derselben Stadt wie ... hat diesselbe Anzahl von Söhnen 37 Spezielle Algorithmen (SAl) Ordnungen 1. Halbordnung Eine binäre Relation ist eine "Halbordnung", wenn sie folgende Eigenschaften besitzt: "Reflexivität, Transitivität" 2. Strenge Ordnungsrelation Eine binäre Relation ist eine "strenge Ordnungsrelation", wenn sie folgende Eigenschaft besitzt: "Transitivität, Asymmetrie" 3. Unstrenge Ordnungsrelation Eine binäre Relation ist eine "unstrenge Ordnungsrelation", wenn sie folgende Eigenschaften besitzt: Transitivität, unstrenge Asymmetrie 4. Totale Ordnungsrelation und partielle Ordnungsrelation Tritt in der Ordnungsrelation x vor y auf, so verwendet man das Symbol < (x < y). Vergleicht man die Abb. 1.2-9, so kann man für (1) schreiben: e < a < b < d und c < d Das Element c kann man weder mit den Elementen e, a noch mit b in eine gemeinsame Ordnung bringen. Daher bezeichnet man diese Ordnungsrelation als partielle Ordnung (teilweise Ordnung). Eine totale Ordnungsrelation enthält im Gegensatz dazu Abb. 1.2-9 in (2): e < a < b<c<d Kann also jedes Element hinsichtlich aller anderen Elemente geordnet werden, so ist die Ordnungsrelation eine totale, andernfalls heißt sie partiell. (1) (2) a a b e e b d c d Abb.1.3-10: Totale und partielle Ordnungsrelationen 38 c Spezielle Algorithmen (SAl) 1.3.4 Klassifikation von Datenstrukturen Eine Datenstruktur ist ein Datentyp mit folgenden Eigenschaften 1. Sie besteht aus mehreren Datenelementen. Diese können - atomare Datentypen oder selbst Datenstrukturen sein 2. Sie setzt die Elemente durch eine Menge von Regeln (eine Struktur) in eine Beziehung (Relation). Elementare Strukturrelationen Menge lineare Struktur (gerichtete 1:1-Relation) Baum (hierarchisch) (gerichtete 1 : n – Relation) Graph (Netzwerk) ( n : m Relation) Abb. 1.3.-11: Elementare Datenstrukturen Eine Datenstruktur ist durch Anzahl und Eigenschaften der Relationen bestimmt. Obwohl sehr viele Relationstypen denkbar sind, gibt es nur 4 fundamentale Datenstrukturen 35, die immer wieder verwendet werden und auf die andere Datenstrukturen zurückgeführt werden können. Den 4 Datenstrukturen ist gemeinsam, daß sie nur binäre Relationen verwenden. 1.3.4.1 Lineare Ordnungsgruppen Sie sind über eine (oder mehrere) totale Ordnung(en) definiert. Die bekanntesten Verkörperungen der linearen Ordnung sind: - (ein- oder mehrdimensionale) Felder (lineare Felder) - Stapel - Schlangen - lineare Listen Lineare Ordnungsgruppen können sequentiell (seqentiell gespeichert) bzw. verkettet (verkettet gespeichert) angeordnet werden. 35 nach: Rembold, Ulrich (Hrsg.): "Einführung in die Informatik", München/Wien, 1987 39 Spezielle Algorithmen (SAl) 1. Sammlungen mit direktem Zugriff Ein „array“ (Reihung) ist eine Sammlung von Komponenten desselben Typs, auf den direkt zugegriffen werden kann. „array“-Kollektion Daten Eine Kollektion von Objekten desselben (einheitlichen) Typs Operationen Die Daten an jeder Stelle des „array“ können über einen ganzzahligen Index erreicht werden. Ein statisches Feld („array“) enthält eine feste Anzahl von Elementen und ist zur Übersetzungszeit festgelegt. Ein dynamisches Feld benutzt Techniken zur Speicherbeschaffung und kann während der Laufzeit an die Gegebenheiten angepaßt werden. Ein „array“ kann zur Speicherung einer Liste herangezogen werden. Allerdings können Elemente der Liste nur effizient am Ende des „array“ eingefügt werden. Anderenfalls sind für spezielle Einfügeoperationen Verschiebungen der bereits vorliegenden Elemente (ab Einfügeposition) nötig. Eine „array“-Klasse sollte Bereichsgrenzenüberwachung für Indexe und dynamische Erweiterungsmöglichkeiten erhalten. Implementierungen aktueller Programmiersprachen umfassen Array-Klassen mit nützlichen Bearbeitungsmethoden bzw. mit dynamischer Anpassung der Bereichsgrenzen zur Laufzeit. Eine Zeichenkette („character string“) ist ein spezialisierter „array“, dessen Elemente aus Zeichen bestehen: „character string“-Kollektion Daten Eine Zusammenstellung von Zeichen in bekannter Länge Operationen Sie umfassen Bestimmen der Länge der Zeichenkette, Kopieren bzw. Verketten einer Zeichenkette auf eine bzw. mit einer anderen Zeichenkette, Vergleich zweier Zeichenketten (für die Musterverarbeitung), Ein-, Ausgabe von Zeichenketten In einem „array“ kann ein Element über einen Index direkt angesprochen werden. In vielen Anwendungen ist ein spezifisches Datenelement, der Schlüssel (key) für den Zugriff auf einen Datensatz vorgesehen. Behälter, die Schlüssel und übrige Datenelemente zusammen aufnehmen, sind Tabellen. Ein Dictionary ist eine Menge von Elementen, die über einen Schlüssel identifiziert werden. Das Paar aus Schlüsseln und zugeordnetem Wert heißt Assoziation, man spricht auch von „assoziativen Arrays“. Derartige Tabellen ermöglichen den Direktzugriff über Schlüssel so, wie in einem Array der Direktzugriff über den Index erreicht wird, z.B.: Die Klasse Hashtable in Java Der Verbund (record in Pascal, struct in C) ist in der Regel eine Zusammenfassung von Datenbehältern unterschiedlichen Typs: „record“-Kollektion Daten 40 Spezielle Algorithmen (SAl) Ein Element mit einer Sammlung von Datenfeldern mit möglicherweise unterschiedlichen Typen. Operationen Der Operator . ist für den Direktzugriff auf den Datenbehälter vorgesehen. Eine Datei (file) ist eine extern eingerichtete Sammlung, die mit einer Datenstruktur („stream“) 36 genannt verknüpft wird. „file“-Kollektion Daten Eine Folge von Bytes, die auf einem externen Gerät abgelegt ist. Die Daten fließen wie ein Strom von und zum Gerät. Operationen Öffnen (open) der Datei, einlesen der Daten aus der Datei, schreiben der Daten in die Datei, aufsuchen (seek) eines bestimmten Punkts in der Datei (Direktzugriff) und schließen (close) der Datei. Bsp.: Die RandomAccessFile-Klasse in Java 37 dient zum Zugriff auf RandomAccess-Dateien. 2. Sammlungen mit sequentiellem Zugriff Darunter versteht man lineare Listen (linear list) 38, die Daten in sequentieller Anordnung aufnehmen: „list“-Kollektion Daten Ein meist größere Objektsammlung von Daten gleichen Typs. Operationen Das Durchlaufen der Liste mit Zugriff auf die einzelnen Elemente beginnt an einem Anfangspunkt, schreitet danach von Element zu Element fort bis der gewünschte Ort erreicht ist. Operationen zum Einfügen und Löschen verändern die Größe der Liste. Stapel (stack) und Schlangen (queue) 39 sind spezielle Versionen linearer Listen, deren Zugriffsmöglichkeiten eingeschränkt sind. „Stapel“-Kollektion Daten Eine Liste mit Elementen, auf die man nur über die Spitze („top“) zugreifen kann. Operationen Unterstützt werden „push“ und „pop“. „push“ fügt ein neues Element an der Spitze der Liste hinzu, „pop“ entfernt ein Element von der Spitze („top“) der Liste. 36 vgl. Programmieren in C++, Skriptum zur Vorlesung im SS 2006, 3.4.1, 3.4.2, 3.4.6 Implementiert das Interface DataInput und DataOutput mit eigenen Methoden. 38 Vgl. Programmieren in C++, Skriptum zur Vorlesung im SS 2006, 5.2 39 http://www.galileocomputing.de/openbook/javainsel7/javainsel_12_007.htm Stand: März 2008 bzw. Programmieren in C++, Skriptum zur Vorlesung im SS 2006, 5.2 37 41 Spezielle Algorithmen (SAl) „Schlange“-Kollektion Daten Eine Sammlung von Elementen mit Zugriff am Anfang und Ende der Liste. Operationen Ein Element wird am Ende der Liste hinzugefügt und am Anfang der Liste entfernt. Eine Schlange 40 ist besonders geeignet zur Verwaltung von „Wartelisten“ und kann zur Simulation von Wartesystemen eingesetzt werden. Eine Schlange kann ihre Elemente nach Prioritäten bzgl. der Verarbeitung geordnet haben (priority queue) 41. Entfernt wird dann zuerst das Element, das die höchste Priorität besitzt. „prioritätsgesteuerte Schlange“-Kollektion Daten Eine Sammlung von Elementen, von denen jedes Element eine Priorität besitzt. Operationen Hinzufügen von Elementen zur Liste. Entfernt wird immer das Element, das die höchste (oder niedrigste) Priorität besitzt. 1.3.4.2 Nichtlineare Kollektion 1.3.4.2.1 Hierarchische angeordnete Sammlung (Bäume) Bäume sind im wesentlichen durch die Äquivalenzrelation bestimmt. Bsp.: Gliederung zur Vorlesung Algorithmen und Datenstrukturen Algorithmen und Datenstrukturen Kapitel 1: Datenverarbeitung und Datenorganisation Abschnitt 1: Ein einführendes Beispiel Kapitel 2: Suchverfahren Abschnitt 2: Begriffe Abb.: Gliederung zur Vorlesung Datenorganisation Die Verkörperung dieser Vorlesung ist das vorliegende Skriptum. Diese Skriptum {Seite 1, Seite 2, ..... , Seite n} teilt sich in einzelne Kapitel, diese wiederum gliedern sich in Abschnitte. Die folgenden Äquivalenzrelationen definieren diesen Baum: 1. Seite i gehört zum gleichen Kapitel wie Seite j 2. Seite i gehört zum gleichen Abschnitt von Kapitel 1 wie Seite j 3. ........ 40 Vgl. Programmieren in C++, Skriptum zur Vorlesung im SS 2006, 5.2 http://java.sun.com/j2se/1.5.0/docs/api/java/util/PriorityQueue.html Stand März 2008 41 42 Spezielle Algorithmen (SAl) Die Definitionen eines Baums mit Hilfe von Äquivalenzrelationen regelt ausschließlich "Vorgänger/Nachfolger" - Beziehungen (in vertikaler Richtung) zwischen den Knoten eines Baums. Ein Baum ist auch hinsichtlich der Baumknoten in der horizontalen Ebene geordnet, wenn zur Definition des Baums neben der Äquivalenzrelation auch eine partielle Ordnung (Knoten Ki kommt vor Knoten Kj, z.B. Kapitel1 kommt vor Kapitel 2) eingeführt wird. Eine hierarchisch angeordnete Sammlung von Datenbehältern ist gewöhnlich ein Baum mit einem Ursprungs- bzw. Ausgangsknoten, der „Wurzel“ genannt wird. Von besonderer Bedeutung ist eine Baumstruktur, in der jeder Baumknoten zwei Zeiger auf nachfolgende Knoten aufnehmen kann. Diese Binärbaum-Struktur kann mit Hilfe einer speziellen angeordneten Folge der Baumknoten zu einem binären Suchbaum erweitert werden. Binäre Suchbäume bilden die Ausgangsbasis für das Speichern großer Datenmengen. „Baum“-Kollektion Daten Eine hierarchisch angeordnete Ansammlung von Knotenelementen, die von einem Wurzelknoten abgeleitet sind. Jeder Knoten hält Zeiger zu Nachfolgeknoten, die wiederum Wurzeln von Teilbäumen sein können. Operationen Die Baumstruktur erlaubt das Hinzufügen und Löschen von Knoten. Obwohl die Baumstruktur nichtlinear ist, ermöglichen Algorithmen zum Ansteuern der Baumknoten den Zugriff auf die in den Knoten gespeicherten Informationen. Ein „heap“ ist eine spezielle Version, in der das kleinste bzw. größte Element den Wurzelknoten besetzt. Operationen zum Löschen entfernen den Wurzelknoten, dabei wird, wie auch beim Einfügen, der Baum reorganisiert. Basis der Heap-Darstellung ist ein „array“ (Feldbaum), dessen Komponenten eine Binärbaumstruktur überlagert ist. In der folgenden Darstellung ist eine derartige Interpretation durch Markierung der Knotenelemente eines Binärbaums mit Indexpositionen sichtbar: [1] wk1 [2] [3] wk2 wk3 [4] [5] wk4 [8] [0] wk5 [9] [10] wk9 wk8 [6] wk6 [11] wk10 [7] [12] wk11 wk7 [13] wk12 wk13 [14] wk14 [15] wk15 wk1 wk2 wk3 wk4 wk5 wk6 wk7 wk8 wk9 wk10 wk11 wk12 wk13 wk14 wk15 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] Abb. 1.3-10: Darstellung eines Feldbaums 43 Spezielle Algorithmen (SAl) Liegen die Knoten auf den hier in Klammern angegebenen Positionen, so kann man von jeder Position I mit Pl = 2 * I Pr = 2 * I + 1 Pv = I div 2 auf die Position des linken (Pl) und des rechten (Pr) Nachfolgers und des Vorgängers (Pv) gelangen. Ein binärer Baum kann somit in einem eindimensionalen Feld (array) gespeichert werden, falls folgende Relationen eingehalten werden: X[I] <= X[2*I] X[I] <= X[2*I+1] X[1] = min(X[I] .. X[N]) Anstatt auf das kleinste kann auch auf das größte Element Bezug genommen werden Implementierung des Heap in C++ 42. // // // // // // // // // // // // // BinaryHeap class Erzeugung mit optionaler Angabe zur Kapazitaet (Defaultwert: 100) ******************PUBLIC OPERATIONS********************* void insert( x ) --> Insert x deleteMin( minItem ) --> Remove (and optionally return) smallest item Comparable findMin( ) --> Return smallest item bool isEmpty( ) --> Return true if empty; else false void makeEmpty( ) --> Remove all items ******************ERRORS******************************** Throws UnderflowException as warranted Anwendung Der Binary Heap kann zum Sortieren 43 herangezogen werden. Ein Heap kann aber auch in Simulationsprogrammen und vor allem zur Implementierung von Priority Queues verwendet werden. Hier wird vielfach der einfache Heap durch komplexere Datenstrukturen ( Binomial Heap, Fibonacci Heap) ersetzt. 42 43 Vgl. pr13_421 http://fbim.fh-regensburg.de/~saj39122/AD/projekte/heapsort/html/index.html 44 Spezielle Algorithmen (SAl) 1.3.4.2.2 Gruppenkollektionen Menge (Set) Eine Gruppe umfaßt nichtlineare Kollektionen ohne jegliche Ordnungsbeziehung. Eine Menge (set) einheitlicher Elemente ist. bspw. eine Gruppe. Operationen auf die Kollektion „Set“ umfassen Vereinigung, Differenz und Schnittmengenbildung. „Set“-Kollektion Daten Eine ungeordnete Ansammlung von Objekten ohne Ordnung Operationen Die binäre Operationen über Mitgliedschaft, Vereinigung, Schnittmenge und Differenz bearbeiten die Strukturart „Set“. Weiter Operationen testen auf Teilmengenbeziehungen. Graph Ein Graph (graph) ist eine Datenstruktur, die durch eine Menge Knoten und eine Menge Kanten, die die Knoten verbinden, definiert ist. In seiner einfachsten Form besteht eine Verkörperung dieser Datenstruktur aus einer Knotenmenge K (Objektmenge) und einer festen aber beliebigen Relation R über dieser Menge 44. Die folgende Darstellung zeigt einen Netzplan zur Ermittlung des kritischen Wegs: Die einzelnen Knoten des Graphen sind Anfangs- und Endereignispunkte der Tätigkeiten, die an den Kanten angegeben sind. Die Kanten (Pfeile) beschreiben die Vorgangsdauer und sind Abbildungen binärer Relationen. Zwischen den Knoten liegt eine partielle Ordnungsrelation. Bestelle A 50 Tage Baue B 1 Teste B 4 20 Tage Korrigiere Fehler 2 25 Tage 3 15 Tage Handbucherstellung 60 Tage Abb. 1.3-11: Ein Graph der Netzplantechnik „graph“-Kollektion Daten Eine Menge von Knoten und eine Menge verbindender Kanten. Operationen Der Graph kann Knoten hinzufügen bzw. löschen. Bestimmte Algorithmen starten an einem gegebenen Knoten und finden alle von diesem Knoten aus erreichbaren Knoten. Andere Algorithmen erreichen jeden Knoten im Graphen über „Tiefen“ bzw. „Breiten“ - Suche. 44 vgl. 1.2.2.2, Abb. 1.2-7 45 Spezielle Algorithmen (SAl) Ein Netzwerk ist spezielle Form eines Graphen, in dem jede Kante ein bestimmtes Gewicht trägt. Den Gewichten können Kosten, Entfernungen etc. zugeordnet sein. 1.3.4.3 Dateien und Datenbanken Datei Damit ist eine Datenstruktur bestimmt, bei der Verbindungen zwischen den Datenobjekten durch beliebige, binäre Relationen beschrieben werden. Die Anzahl der Relationen ist somit im Gegensatz zur Datenstruktur Graph nicht auf eine beschränkt. Verkörperungen solcher assoziativer Datenstrukturen sind vor allem Dateien. In der Praxis wird statt mehrere binärer Relationen eine n-stellige Relation (Datensatz) gespeichert. Eine Datei ist dann eine Sammlung von Datensätzen gleichen Typs. Bsp.: Studenten-Datei Sie faßt die relevanten Daten der Studenten 45 nach einem ganz bestimmten Schema zusammen. Ein solches Schema beschreibt einen Datensatz. Alle Datensätze, die nach diesem Schema aufgestellt werden, ergeben die Studenten-Datei. Es sind binäre Relationen (Student - Wohnort, Student Geburtsort, ...), die aus Speicheraufwandsgründen zu einer n-stelligen Relation (bezogen auf eine Datei) zusammengefaßt werden. Datenbanken Eine Datenbank ist die Sammlung von verschiedenen Datensatz-Typen. Fachbereich 1 betreut M Student Abb. 1.3-14: „ER“-Diagramm zur Darstellung der Beziehung „Fachbereich-Student“ Auch hier zeigt sich: Knoten bzw. Knotentypen und ihre Beziehungen bzw. Beziehungstypen stehen im Mittelpunkt der Datenbank-Beschreibung. Statt Knoten spricht man hier von Entitäten (bzw. Entitätstypen) und Beziehungen werden Relationen genannt. Dies ist dann Basis für den Entity-Relationship (ER) -Ansatz von P.S. Chen. Zur Beschreibung der Entitätstypen und ihrer Beziehungen benutzt der ER-Ansatz in einem ER-Diagramm rechteckige Kanten bzw. Rauten: Die als „1“ und „M“ an den Kanten aufgeschriebenen Zahlen zeigen: Ein Fachbereich betreut mehrere (viele) Studenten. Solche Beziehungen können vom Typ 1:M, 1:1, M:N sein. Es ist auch die Bezugnahme auf gleiche Entitätstypen möglich, z.B.: 45 vgl. 1.2.2.1 46 Spezielle Algorithmen (SAl) Person 1 1 Heirat Abb.: 1.3-15: Bezugnahme auf den gleiche Entitätstyp „Person“ Zur Verwaltung großer Datenbestände nutzen Datenbanken - die Speicherung von Daten in Tabellenform mit effizienten Zugriffsalgorithmen nach wahlfreien Kriterien und SQL als Datenzugriffssprache Datenbanken zeigen den praktischen Einsatz vieler vorgestellten Methoden, Algorithmen und Datenstrukturen, z.B. die Speicherung in Tabellen hinsichtlich - - der Dateiorganisationsform o sortiert nach Schlüssel o B-Baum nach Schlüssel o Hash-Tabelle nach Schlüssel Zugriffsunterstützung nach Index o Primärindexe verweisen auf Hauptdatei o B –Bäume für Nichtschlüsselattribute zur Bescheunigung des Zugriffs (Sekundärindex) Die folgende Darstellung einer Datenbank in einem ER-Diagramm Abt_ID Bezeichnung Job_ID Titel Abteilung Gehalt Job Abt-Ang Job-Ang Qualifikation Angestellte Ang_ID Name GebJahr Abb.: ER-Diagramm zur Datenbank Personalverwaltung führt zum folgenden Schemaentwurf einer relationalen Datenbank - Abteilung(Abt_ID,Bezeichnung) - Angestellte(Ang_ID,Name,Gebjahr,Abt_ID,Job_ID) - Job(Job_ID,Titel,Gehalt) - Qualifikation(Ang_ID,Job_ID) und resultiert in folgender relationalen Datenbank für das Personalwesen: 47 Spezielle Algorithmen (SAl) ABTEILUNG ABT_ID KO OD PA RZ VT BEZEICHNUNG Konstruktion Organisation und Datenverarbeitung Personalabteilung Rechenzentrum Vertrieb ANG_ID A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 NAME Fritz Tom Werner Gerd Emil Uwe Eva Rita Ute Willi Erna Anton Josef Maria ANGESTELLTE GEBJAHR 2.1.1950 2.3.1951 23.4.1948 3.11.1950 2.3.1960 3.4.1952 17.11.1955 02.12.1957 08.09.1962 7.7.1956 13.10.1966 5.7.1948 2.8.1952 17.09.1964 ABT_ID OD KO OD VT PA RZ KO KO OD KO OD OD KO PA JOB_ID SY IN PR KA PR OP TA TA SY IN KA SY SY KA JOB JOB_ID KA TA SY PR OP TITEL Kaufm. Angestellter Techn. Angestellter Systemplaner Programmierer Operateur GEHALT 3000,00 DM 3000,00 DM 6000,00 DM 5000,00 DM 3500,00 DM QUALIFIKATION ANG_ID A1 A1 A1 A2 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 JOB_ID SY PR OP IN SY PR KA PR OP TA IN SY IN KA SY IN KA Abb. 1.3-17: Tabellen zur relationalen Datenbank 48 Spezielle Algorithmen (SAl) Die relationale Datenbank besteht, wie die vorliegende Darstellung zeigt aus einer Datenstruktur, die Dateien (Tabellen) vernetzt. Die Verbindung besorgen Referenzen (Fremdschlüssel). Übung 13_4: Deduktive Datenbanken 1.3.5 Definitionsmethoden für Datenstrukturen 1.3.5.1 Der abstrakte Datentyp Anforderungen an die Definition eines Datentyps 46 - Die Spezifikation eines Datentyps sollte unabhängig von seiner Implementierung sein. Daurch kann die Spezifikation für unterschiedliche Implementierungen verwendet werden. Reduzierung der von außen sichtbaren (zugänglichen) Aspekte auf der Schnittstelle des Datentyps. Dadurch kann die Implementierung später verwendet werden, ohne dass Programmteile, die den Datentyp benutzen, angepasst werden. Aus diesen Anforderungen ergeben sich zwei Prinzipien: - Kapselung (ecucapsultaion): Alle Zugriffe gehen immer nur über die Schnittstelle des Datentyps Geheimnisprinzip (programming by contract). Die interne Realisierung des Datentyps bleibt dem Benutzer verborgen Ein Datentyp, dem nur Spezifikation und Eigenschaften (bspw. in Form von Regeln oder Gesetzmäßigkeiten bekannt sind, heißt abstrakt. Man abstrahiert hier von der konkreten Implementierung. Ein abstrakter Datentyp wird als ADT bezeichnet. Die Definition einer Datenstruktur ist bestimmt durch Daten (Datenfelder, Aufbau, Wertebereiche) und die für die Daten gültigen Rechenvorschriften (Algorithmen, Operationen). Datenfelder und Algorithmen bilden einen Typ, den abstrakten Datentyp (ADT). Eine Spezifikation eines ADT besteht aus: - Angabe der Signatur. Sie legt die Namen der Typen sowie die Funktionstypen fest und bildet die Schnittstelle eines ADT. Mengen und Funktionen, die zur Signatur passen, heißen Algebren Gleichungen dienen als Axiome zur Einschränkung möglicher Algebren als Modell. Zusätzlich erfolgt evtl. ein Import anderer Spezifikationen. Daten und Algorithmen als Einheit zu sehen, war bereits schon vor 1980 bekannt. Da damals noch keine allgemein einsetzbare Implementierung vorlag, hat man Methoden zur Deklaration von ADT 47 bereitgestellt. Damit sollte dem Programmierer wenigstens durch die Spezifikation die Einheit von Daten und zugehörigen Operationen vermittelt werden. Algorithmen für abstrakte Datentypen 48 46 vgl. 1.3.1 vgl. Guttag, John: "Abstract Data Types and the Development of Data Structures", CACM, June 1977 48 wichtige abstrakte Datentypen: 1.3.4 47 49 Spezielle Algorithmen (SAl) Abstrakte Datentypen sind im Gegensatz zu primitiven Datentypen komplexe Typen mit typischen Operationen, die für diese Datentypen bereitgestellt werden. Diese Typen werden auschließlich durch generische Typen 49 verändert. 1.3.5.2 Die axiomatische Methode Die axiomatische Methode beschreibt abstrakte (Daten-)Typen über die Angabe einer Menge von Operationen und deren Eigenschaften, die in der Form von Axiomen präzisiert werden. Problematisch ist jedoch: Die Axiomenmenge ist so anzugeben, daß Widerspruchsfreiheit, Vollständigkeit und möglichst Eindeutigkeit erzielt wird. Algebraische Spezifikation Eine spezielle axiomatische Methode ist die algebraische Spezifikation von Datenstrukturen. Sie soll hier stellvertretend für axiomatische Definitionsmethoden an einem Beispiel vorgestellt werden. 1. Bsp.: Die algebraische Spezifikation des (ADT) Schlange Konventionell würde die Datenstruktur Schlange so definiert werden: Eine Schlange ist ein lineares Feld, bei dem nur am Anfang Knoten entfernt und nur am Ende Knoten hinzugefügt werden können. Die Definition ist ungenau. Operationen sollten mathematisch exakt als Funktionen und Beziehungen der Operationen als Gleichungen angegeben sein. Erst dann ist die Prüfung auf Konsistenz und der Nachweis der korrekten Implementierung möglich. Die algebraische Spezifikation bestimmt den ADT Schlange deshalb folgendermaßen: ADT Schlange Typen Schlange<T>, boolean Funktionen (Protokoll) NeueSchlange FuegeHinzu : Vorn : Entferne : Leer : → Schlange<T> T,Schlange<T> → Schlange<T> → T Schlange<T> Schlange<T> → Schlange<T> → boolean Schlange<T> Axiome Für alle t : T bzw. s : Schlange<T> gilt: 1. 2. 3. 4. 5. 6. Leer(NeueSchlange) = wahr Leer(FuegeHinzu(t,s)) = falsch Vorn(NeueSchlange) = Fehler Vorn(FuegeHinzu(t,s)) = Wenn Leer(s), dann t; andernfalls Vorn(s) Entferne(NeueSchlange) = Fehler Entferne(FuegeHinzu(t,s)) = Wenn Leer(s), dann NeueSchlange; andernfalls FuegeHinzu(t,Entferne(s)) 49 Einen Algorithmus, der unabhängig von einem Datentyp programmiert werden kann, nennt man generisch, die Möglichkeit in Java mit generischen Typen zu arbeiten Generics. 50 Spezielle Algorithmen (SAl) Der Abschnitt Typen zeigt die Datentypen der Spezifikation. Der 1. Typ ist der spezifizierte ADT. Von anderen Typen wird angenommen, daß sie an anderer Stelle definiert sind. Der Typ „Schlange<T>“ wird als generischer ADT bezeichnet, da er "übliche Schlangeneigenschaften" bereitstellt. Eigentliche Schlangentypen erhält man durch die Vereinbarung eines Exemplars des ADT, z.B.: Schlange<integer> Der Abschnitt Funktionen zeigt die auf Exemplare des Typs anwendbaren Funktionen: f : D1 , D2 ,...., Dn → D . Einer der Datentypen D1 , D2 ,...., Dn oder D muß der spezifizierte ADT sein. Die Funktionen können eingeteilt werden in: - Konstruktoren (constructor functions) (Der ADT erscheint nur auf der rechten Seite des Pfeils.) Sie liefern neue Elemente (Instanzen) des ADT. - Zugriffsfunktionen (accessor functions) (Der ADT erscheint nur auf der linken Seite des Pfeils.) Sie liefern Eigenschaften von existierenden Elementen des Typs (vgl. Die Funktion: Leer) - Umsetzungsfunktionen (transformer functions) (Der ADT erscheint links und rechts vom Pfeil.) Sie bilden neue Elemente des ADT aus bestehenden Elementen und (möglicherweise) anderen Argumenten (vgl. FuegeHinzu, Entferne). Der Abschnitt Axiome beschreibt die dynamischen Eigenschaften des ADT. 2. Bsp.: Die „algebraische Spezifikation“ des ADT Stapel ADT Stapel<T>, integer, boolean 1. Funktionen (Operationen, Protokoll) NeuerStapel PUSH POP Top Stapeltiefe Leer : : : : : T,Stapel<T> Stapel<T> Stapel<T> Stapel<T> Stapel<T> → → → → → → Stapel<T> Stapel<T> Stapel<T> T integer boolean 2. Axiome Für alle t:T und s:Stapel<T> gilt: (POP(PUSH(t,s)) = s Top(PUSH(t,s)) = t Stapeltiefe(NeuerStapel) = 0 Stapeltiefe(PUSH(i,s)) = Stapeltiefe + 1 Leer(NeuerStapel) = wahr ¬ Leer(PUSH(t,s) = wahr 3. Restriktionen (conditional axioms) Wenn Stapeltiefe(s) = 0, dann führt POP(s) auf einen Fehler Wenn Stapeltiefe(s) = 0, dann ist Top(s) undefiniert Wenn Leer(s) = wahr, dann ist Stapeltiefe(s) Null. Wenn Stapeltiefe(s) = 0, dann ist Leer(s) wahr. Für viele Programmierer ist eine solche Spezifikationsmethode zu abstrakt. Die Angabe von Axiomen, die widerspruchsfrei und vollständig ist, ist nicht möglich bzw. nicht nachvollziehbar. 51 Spezielle Algorithmen (SAl) 3. Bsp.: Die algebraische Spezifikation für einen binären Baum ADT Binaerbaum<T>, boolean 1. Funktionen (Operationen, Protokoll) NeuerBinaerbaum -> Binaerbaum<T> bin : Binaerbaum<T>, T, Binaerbaum<T> → Binaerbaum<T> → Binaerbaum<T> links : Binaerbaum<T> → Binaerbaum<T> rechts : Binaerbaum<T> wert : Binaerbaum<T> -> T → boolea istLeer : Binaerbaum<T> 2. Axiome Für alle t:T und x:Binaerbaum<T>, y:Binaerbaum<T> gilt: links(bin(x,t,y)) = x rechts(bin(x,t,y)) = y wert(bin(x,t,y)) = t istLeer(NeuerBinaerbaum) = true istLeer(bin(x,t,y)) = false Der direkte Weg zur Deklaration von ADT im Rahmen der objektorientierten Programmierung ist noch nicht weit verbreitet. Der konventionelle Programmierstil, Daten und Algorithmen getrennt zu behandeln, bevorzugt den konstruktiven Aufbau der Daten aus elementaren Datentypen. Realisierung von abstrakten Datentypen Die folgenden Elemente müssen im Programm abgebildet werden: - Name des ADT wird üblicherweise der Klassenname Importierte ADTs werden sowohl zur Definition mit dem entsprechenden importierten Typ, als auch zu Importanweisungen innerhalb des Programms benutzt Objekterzeugende Operatoren: sog. Konstruktoren werden in den (meist speziellen) Klassenmethoden abgebildet, die ein neues Objekt des gewünschten Typ zurückliefern Lesende Operatoren: sog. Selektoren werden zu Methoden, die auf die Attribute nur lesend zugreifen. Schreibende Operatoren: sog. Manipulatoren werden zu Methoden, die den Zustand des Objekts verändern Axiome müssen sichergestellt sein. 52 Spezielle Algorithmen (SAl) 2. Graphen und Graphenalgorithmen 2.1 Einführung 2.1.1 Grundlagen Viele Objekte und Vorgänge in verschiedenen Bereichen besitzen den Charakter eines Systems, d.h.: Sie setzen sich aus einer Anzahl von Bestandteilen, Elementen zusammen, die in gewisser Weise in Beziehung stehen. Sollen an einem solchen System Untersuchungen durchgeführt werden, dann ist es oft zweckmäßig, den Gegenstand der Betrachtungen durch ein graphisches Schema (Modell) zu veranschaulichen. Dabei stehen grundsätzlich immer 2 Elemente untereinander in Beziehung, d.h.: Die Theorie des graphischen Modells ist ein Teil der Mengenlehre, die binäre Relationen einer abzählbaren Menge mit sich selbst behandelt. Bsp.: Es ist K = {A, B, C, D} eine endliche Menge. Es ist leicht die Menge aller geordneten Paare von K zu bilden: K × K = {(A,A),(A,B),(A,C),(A,D),(B,A),(B,B),(B,C),(B,D),(C,A),(C,B),(C,C),(C,D),(D,A),(D,B),D,C),(D,D)} Gegenüber der Mengenlehre ist die Graphentheorie nicht autonom. Die Graphentheorie besitzt ein eigenes, sehr weites und spezifisches Vokabular. Sie umfaßt viele Anwendungsungsmöglichkeiten in der Physik, aus dem Fernmeldewesen und dem Operations Research (OR). Im OR sind es vor allem Organisations- bzw. Verkehrs- und Transportprobleme, die mit Hilfe von Graphenalgorithmen untersucht und gelöst werden. Generell dienen Graphenalgorithmen in der Praxis zum Lösen von kombinatorischen Problemen. Dabei geht man folgendermaßen vor: 1. Modelliere das Problem als Graph 2. Formuliere die Zielfunktion als Eigenschaft des Graphen 3. Löse das Problem mit Hilfe eines Graphenalgorithmus Bsp.: Es ist K = {A,B,C,D} ein endliche Menge. Es ist leicht die Menge aller geordneten Paare von K zu bilden: K × K = {(A,A),(A,B),(A,C),(A,D),(B,A),(B,B),(B,C),(B,D),(C,A),(C,B),(C,C),(C,D),(D,A),(D,B),D,C),(D,D)} Die Menge dieser Paare kann auf verschiedene Arten dargestellt werden: 53 Spezielle Algorithmen (SAl) 1. Koordinatendarstellung A B C D A B C D Abb.: 2. Darstellung durch Punkte (Kreise) und Kanten (ungerichteter Graph) A B C D Abb.: ungerichteter Graph Eine Kante (A,A) nennt man Schlinge. Ein schlingenfreier Graph heißt schlicht. Ein ungerichteter Graph G = (V , E ) besteht aus - einer endlichen Knotenmenge (vertices) V und - einer endlichen Kantenmenge (edges) E 3. Darstellung durch Punkte (Kreise) und Pfeile (gerichteter Graph) A B D C Abb.: 54 Spezielle Algorithmen (SAl) Einen Pfeil (A,A) nennt man eine Schlinge. Zwei Pfeile mit identischem Anfangsund Endknoten nennt man parallel. Analog lassen sich parallele Kanten definieren. Ein Graphen ohne parallele Kanten bzw. Pfeile und ohne Schlingen bezeichnet man als schlichte Graphen. ( ) 4. G = (V , E , φ ), φ : E → ℜ meist φ : E → ℜ + heißt bewerteter (weighted) Graph mit Bewertung φ (Bewertungen geben z.B. Abstände, Kosten, Kapazitäten oder Wahrscheinlichkeiten an. 5. Darstellung durch paarweise geordnete Paare A A B B C C D D Abb Ein bipartiter Graph ist ein Graph, dessen Knoten so in zwei Mengen zerteilt werden können, dass jede Kante je einen Knoten aus beiden Mengen verbindet. Probleme: 1. Herausfinden, ob ein Graph bipartit ist 2. Welches sind die Partitionen 55 Spezielle Algorithmen (SAl) Bipartites Matching: Bipartite Graphen dienen häufig zur Lösung Zuordnungsproblemen, z.B. für Männer und Frauen in einem Tanzkurs. Heini von Eva Martin Klaus Maria Pia gematcht Lilo Uwe Abb.: Jeder Teilnehmer im Tanzkurs ist ein Knoten im Graphen zugeordnet, jede Kante beschreibt mögliche Tanzpartner. Drei Paare sind gefunden, aber nicht jeder Knoten hat einen Partner, und es sind keine weiteren Paarungen möglich. 6. Darstellung mit Hilfe einer Matrix A 1 1 1 1 A B C D B 1 1 1 1 C 1 1 1 1 D 1 1 1 1 Einige der geordneten Paare aus der Produktmenge K × K sollen eine bestimmte Eigenschaft haben, während die anderen sie nicht besitzen. Eine solche Untermenge von K × K ist : G = {(A,B),(A,D),(B,B),(B,C),(B,D),(C,C),(D,A),(D,B),(D,C),(D,D)} Üblicherweise wird diese Untermenge (Teilgraph) so dargestellt: A B D C Abb.: Betrachtet man hier die Paare z.B. (A,B) bzw. (A,D), so kann man feststellen: Von A erreicht man, den Pfeilen folgend, direkt B oder D. B und D heißt auch die "Inzidenzabbildung" von A und {B,D} das volle Bild von A. Verwendet man das Symbol Γ zur Darstellung des vollen Bilds, dann kann man das vorliegende Beispiel (vgl. Abb.:) so beschreiben: Γ ( A) = ( B, D ) Γ ( B ) = ( B, C, D ) Γ( C ) = C Γ ( D ) = ( A, B, C, D ) 56 Spezielle Algorithmen (SAl) Zwei Kanten (Pfeile) werden benachbart oder adjazent genannt, wenn es einen Knoten gibt, der Endknoten einen Kante und Anfangsknoten der anderen Kante ist. Zwei Knoten heißen benachbart oder adjazent, wenn sie durch einen Kante (Pfeil) unmittelbar verbunden sind. Kanten (Pfeile), die denselben Anfangs- und Endknoten haben, heißen parallel. 2.1.2 Definitionen Gegeben ist eine endliche (nicht leere) Menge K 50. Ist G eine Untermenge der Produktmenge K × K , so nennt man ein Element der Menge K einen Knoten von G. Die Elemente der Knotenmenge K können auf dem Papier durch Punkte (Kreise) markiert werden. A B D C Abb.: Ein Element von G selbst ist eine (gerichtete) Kante. Im vorstehenden Bsp 51. sind (A,B), (A,D), (B,B), (B,C), (B,D), (C,C), (D,A), (D,B), (D,C), (D,D) (gerichtete) Kanten. Ein Graph wird durch die Menge seiner Knoten K und die seiner Inzidenzabbildungen beschrieben: G=(K, Γ ) Ein Graph kann aber auch folgendermaßen beschrieben werden: G = (K,E) bzw. G = (V , E ) . E ist die Menge der Kanten (gerichtet, ungerichtet, gewichtet). In gewichteten Graphen werden jeder Kante ganze Zahlen (Gewichte, z.B. zur Darstellung von Entfernungen oder Kosten) zugewiesen. Gewichtete gerichtete Graphen werden auch Netzwerke genannt. Falls die Anzahl der Knoten in einem Graphen "n" ist, dann liegt die Anzahl der n ⋅ ( n − 1) Kanten zwischen 0 und im ungerichten Graphen. Ein gerichteter Graph 2 kann bis zu n ⋅ ( n − 1) Pfeile besitzen. In einem vollständigen Graphen existiert zwischen jedem Knotenpaar eine Kante. 50 51 Anstatt K schreibt man häufig auch V (vom englischen Wort Vertex abgeleitet) vgl. 2.1.1 57 Spezielle Algorithmen (SAl) Abb. Vollständiger Graph Ein Graph G = (V , E ) heißt bipartit, wenn 2 disjunkte Knotenmengen V1 , V2 ⊆ V gibt, so dass E ⊆ {{v1 , v 2 }v1 ∈ V1 , v 2 ∈ V2 } gilt. Abb. Ein bipartiter Graph Der Grad eines Knoten bezeichnet die Zahl der Kanten, die in Knoten enden. Eingangsgrad: Zahl der ankommenden Kanten. 1 3 2 1 2 1 1 2 0 Abb.: Eingangsgrad Ausgangsgrad: Zahl der abgehenden Kanten 2 1 0 1 2 0 1 2 3 Abb.: Ausgangsgrad 58 Spezielle Algorithmen (SAl) Bei ungerichteten Graphen ist der Ausgangsgrad gleich dem Eingangsgrad. Man spricht dann nur von Grad. Ein Pfad vom Knoten k1 zum Knoten kk ist eine Folge von Knoten k1, k2, ... , kk, wobei (k1,k2), ... ,(kk-1,kk) Kanten sind. Die Länge des Pfads ist die Anzahl der Kanten im Pfad. Auch Pfade können gerichtet oder ungerichtet sein. kk k1 Abb.: Ein Graph ist zusammenhängend, wenn von jedem Knoten zu jedem anderen Knoten im Graph ein Weg (Pfad) existiert. X1 X5 X2 X4 X3 Abb.: Dieser Graph ist streng zusammenhängend. Man kann sehen, daß es zwischen je 2 Knoten mindestens einen Weg gibt. Dies trifft auf den folgenden Grafen nicht zu: X1 X6 X2 X3 X5 X4 Abb.: Hier gibt es bspw. keinen Weg von X4 nach X1. Ein Graph, der nicht zusammenhängend ist, setzt sich aus zusammenhängenden Komponenten zusammen. Ein Knoten in einem zusammenhängenden Netzwerk heißt Artikulationspunkt, wenn durch sein Entfernen der Graph zerfällt, z.B. 59 Spezielle Algorithmen (SAl) Artikulationspunkte sind dunkel eingefärbt. Abb.: Erreichbarkeit: Der Knoten B ist in dem folgenden Graphen erreichbar vom Knoten G, wenn es einen Pfad von G nach B gibt. C E B D F I G A H Abb.: Knoten B ist erreichbar von Knoten G Ein Graph heißt Zyklus, wenn sein erster und letzter Knoten derselbe ist. Abb.: Zyklus (manchmal auch geschlossener Pfad genannt) Ein Zyklus ist ein einfacher Zyklus, wenn jeder Knoten (außer dem ersten und dem letzten) nur einmal vorkommt. 60 Spezielle Algorithmen (SAl) Abb.: Einfacher Zyklus (manchmal auch geschlossener Pfad genannt) Ein gerichteter Graph heißt azyklisch, wenn er keine Zyklen enthält. Ein azyklischer Graph kann in Schichten eingeteilt werden (Stratifikation). Bäume sind Graphen, die keine Zyklen enthalten. Graphen, die keine Zyklen enthalten heißen Wald. Zusammenhängende Graphen, die keine Zyklen enthalten, heißen Bäume. Wenn ein gerichteter Graph ein Baum ist und genau einen Knoten mit Eingangsgrad 0 hat, heißt der Baum Wald. Ein spannender Baum (Spannbaum) eines ungerichteten Graphen ist ein Teilgraph des Graphen, und ist ein Baum der alle seine Knoten enthält. Abb. Einen spannenden Baum mit minimaler Summe der Kantenbewegungen bezeichnet man als minimalen spannenden Baum. Zu dem folgenden Graphen 3 4 2 5 4 3 4 4 5 6 gehört der folgende minimale spannende Baum Abb.: Minimaler spannender Baum 61 Spezielle Algorithmen (SAl) Ein Pfad wird als Circuit (Rundgang) bezeichnet, wenn der erste und letzte Knoten des Pfads identisch sind. Ein Circuit wird als einfach (oder als Kreis) bezeichnet, falls alle Knoten außer dem ersten und letzten genau einmal auftreten Eulersche Pfade bzw. Eulerscher Kreis: Ausgangspunkt dieses Problems ist das sog. Königsberger Brückenproblem, das Leonard Euler 1736 gelöst hat. Euler interpretierte dabei die Brücken über den Fluß Pregel in Königsberg als Kanten und Ufer bzw. Inseln als Knoten. neuer Pregel Pregel alter Pregel Abb.: Königsberger Brückenproblem mit Darstellung als Graph Königsberger Brückenproblem: Existiert ein Eulerscher Pfad? Lösung: Da man, wenn man in einen Knoten hineinkommt, auf anderem Weg wieder herauskommen muß, gilt als Bedingung: Der Grad jedes Knoten muß durch 2 teilbar sein. Neuformulierung des Problems: Gibt es einen Zyklus im Graphen, der alle Kanten genau einmal enthält (Eulerkreis) 52. Bedingung für die Existenz eines Eulerkreises: Der Grad jedes Knoten muß durch 2 teilbar und zusammenhängend sein. Das Königsberger Brückenproblem stellt offenbar keinen Eulerkreis dar. Bekanntes Bsp.: Kann das Häuschen der folgenden Abbildung in einem Strich gezeichnet werden? Abb. Haus des Nikolaus 52 falls JA, wird der Graph eulersch genannt. 62 Spezielle Algorithmen (SAl) Hamiltonsche Pfade: Gegeben ist eine Landkarte mit Orten und Verbindungen. Gesucht ist ein Rundgang einmal durch jeden Ort. Abb.: Hamiltonscher Kreis Verschärfung: jede Verbindung ist mit Kosten gewichtet. Gesucht ist der billigste Rundgang. Ein Hamiltonscher Pfad ist ein einfacher Zyklus, der jeden Knoten eines Graphen enthält. Ein Algorithmus für das Finden eines Hamiltonschen Graphen ist relativ einfach (modifizierte Tiefensuche) aber sehr aufwendig. Bis heute ist kein Algorithmus bekann, der eine Lösung in polynomialer Zeit findet. 2.1.3 Darstellung in Rechnerprogrammen 1. Der abstrakte Datentyp (ADT) für gewichtete Graphen Ein gewichteter Graph besteht aus Knoten und gewichteten Kanten. Der ADT beschreibt die Operationen, die einem solchen gewichteten Graphen Datenwerte hinzufügen oder löschen. Für jeden Knoten Ki definiert der ADT alle benachbarten Knoten, die mit Ki durch eine Kante E(Ki,Kj) verbunden sind. ADT Graph Daten Sie umfassen eine Menge von Knoten {Ki} und Kanten {Ei}. Eine Kante ist ein Paar (Ki, Kj), das anzeigt: Es gibt eine Verbindung vom Knoten Ki zum Knoten Kj. Verbunden ist mit jeder Kante die Angabe eines Gewichts. Es bestimmt den Aufwand, um entlang der Kante vom Knoten Ki nach dem Knoten Kj zu kommen. Operationen Konstruktor Eingabe: keine Verarbeitung: Erzeugt den Graphen als Menge von Knoten und Kanten Einfuegen_Knoten Eingabe: Ein neuer Knoten Vorbedingung: keine Verarbeitung: Füge den Knoten in die Menge der Knoten ein Ausgabe: keine Nachbedingung: Die Knotenliste nimmt zu Einfügen_Kante Eingabe: Ein Knotenpaar Ki und Kj und ein Gewicht Vorbedingung: Ki und Kj sind Teil der Knotenmenge Verarbeitung: Füge die Kante (Ki,Kj) mit dem gewicht in die Menge der Kanten ein. Ausgabe: keine 63 Spezielle Algorithmen (SAl) Nachbedingung: Die Kantenliste nimmt zu Loesche_Knoten Eingabe: Eine Referenz für den Knoten Kl Vorbedingung: Der Eingabewert muß in der Knotenmenge vorliegen Verarbeitung: Lösche den Knoten aus der Knotenliste und lösche alle Kanten der Form (K,Kl) bzw. (Kl,K), die eine Verbindung mit Knoten Kl besitzen Loesche_Kante Eingabe: Ein Knotenpaar Ki und Kj Vorbedingung: Der Eingabewert muß in der Kantenliste vorliegen Verarbeitung: Falls (Ki,Kj) existiert, loesche diese Kante aus der Kantenliste Ausgabe: keine Nachbedingung: Die Kantenmenge wird modifiziert Hole_Nachbarn: Eingabe: Ein Knoten K Vorbedingung: keine Verarbeitung: Bestimme alle Knoten Kn, so daß (K,Kn) eine Kante ist Ausgabe: Liste mit solchen Kanten Nachbedingung: keine Hole_Gewichte Eingabe: Ein Knotenpaar Ki und Kj Vorbedingung: Der Eingabe wert muß zur Knotenmenge gehören Verarbeitung: Beschaffe das Gewicht der Kante (Ki, Kj), falls es existiert Ausgabe: Gib das Gewicht dieser Kante aus (bzw. Null, falls die Kante nicht existiert Nachbedingung: keine 2. Abbildung der Graphen Es gibt zahlreiche Möglichkeiten zur Abbildung von Knoten und Graphen in einem Rechnerprogramm. Eine einfache Abbildung speichert die Knoten in einer sequentiellen Liste. Die Kanten werden in einer Matrix beschrieben (Adjazenzmatrix), in der Zeile i bzw. Spalte j den Knoten Ki und Kj zugeordnet sind. Jeder Eintrag in der Matrix gibt das Gewicht der Kante Eij = (Ki,Kj) oder den Wert 0 an, falls die Kante nicht existiert. In ungewichteten, gerichteten Graphen hat der Eintrag der (booleschen) Wert 0 oder 1, je nachdem, ob die Kante zwischen den Knoten existiert oder nicht, z.B.: 64 Spezielle Algorithmen (SAl) 2 A B 3 5 1 4 E C 7 D 0 2 1 0 0 0 0 5 0 0 0 4 0 0 0 0 0 7 0 0 0 3 0 0 0 B A C D E 0 1 1 1 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 Abb.: Besonders einfach kann einer Adjazenzmatrix A[i,j] geprüft werden, ob es eine Kante von i nach j gibt. Die Laufzeit für diese Operation ist O(1). Will man alle Nachbarn eines Knoten i in einem ungerichteten Graphen ermitteln, muß man hingegen alle Einträge der i-ten Zeile oder der i-ten Spalte überprüfen (n Schritte). Bei gerichteten Graphen findet man in der i-ten Zeile die Knoten, die von i aus erreichbar sind (Nachfolger), in der j-ten Spalte hingegen die Knoten, von denen aus eine Kante nach i führt (Vorgänger). Der Speicherplatzbedarf für eine Adgazenzmatrix ist –unabhängig von der Anzahl der Kanten – immer n2. 53 Gibt es wenige Kanten im Graphen, enthält die zugehörige Adjazenzmatrix hauptsächlich Nullen. Ungerichtete Graphen können etwas effizienter gespeichert werden, da ihre Matrix symmetrisch ist, somi müssen die Einträge nur n ⋅ ( n − 1) oberhalb der Diagonalen gespeichert werden. Dafür werden Speicherplätze 2 53 kompakt bei dichten Graphen, also Graphen mit vielen Kanten 65 Spezielle Algorithmen (SAl) benötigt, wenn es keine Schlingen gibt. n ⋅ (n + 1) Speicherplätze benötigt man. Wenn 2 es Schlingen gibt 54. In der Darstellungsform „Adjazenzliste“ werden für jeden Knoten alle mit ihm verbundenen Knoten in eine Adjazenzliste für diese Knoten aufgelistet. Das läßt sich leicht über verkettete Listen realisieren. In einem gewichteten Graph kann zu jedem Listenelement ein Feld für das Gewicht hinzugefügt werden, z.B.: 2 A B 3 5 1 4 E C 7 Knoten: 54 D Liste der Nachbarn: A B 2 B C 5 C B 4 D C 7 E B 3 C 1 in diesem Fall muß die Diagonale mitgespeichert werden. 66 Spezielle Algorithmen (SAl) B A C D E A B C B A C C A D E E C D Abb.: Adjazenzlisten verbrauchen nur linear viel Speicherplatz, was insbesonders bei dünnen Grafen (also Graphen mit wenig Kanten) von Vorteil ist. Viele graphentheoretischen Probleme lassen sich mit Adjazenzlisten in linearer Zeit lösen. Für einen gerichteten Graphen benötigt eine Adjazenzliste n+m Speicherplätze, für einen ungerichteten Gaphen n+2m mit n als Knotenanzahl und m als als Kantenanzahl. 3. Lösungsstrategien Für die Lösung der Graphenprobleme stattet man die Algorithmen mit verschiedenen Strategien aus: - Greedy (sukzessive bestimmung der Lösungsvariablen) - Divide and Conquer (Aufteilen, Lösen, Lösungen vereinigen) - Dynamic Programming (Berechne Folgen von Teillösungen) - Enumeration (Erzeuge alle Permutationen und überprüfe sie) - Backtracking (Teillösungen werden systematisch erweitert) - Branch and Bound (Erweitere Teillösungen an der vielversprechenden Stelle) 67 Spezielle Algorithmen (SAl) 2.2 Durchlaufen von Graphen Für manche Probleme ist es wichtig, alle Knoten in einem Graphen zu betrachten. So kann man etwa einer in einem Labyrinth eingeschlossenen Person nachfühlen, dass sie sämtliche Kreuzungen von Gängen in Augenschein nehmen will. Die Gänge des Labyrinths sind hier die Kanten des Graphen, und Kreuzungen sind die Knoten. Es gibt zwei Suchstrategien für Graphen: Tiefensuche und Breitensuche. Diese Verfahren bilden die Grundlage für graphentheoretische Algorithmen, in denen alle Ecken oder Kanten eines Graphen systematisch durchlaufen werden müssen. Für die meisten Suchverfahren gilt folgendes algorithmisches Grundgerüst (Markierungsalgorithmus): 1. Markiere den Startknoten 2. Solange es noch Kanten von markierten zu unmarkierten Knoten gibt, wähle eine solche Kante und markiere deren Endknoten. Die beiden hier angegebenen Verfahren (Tiefensuche unterscheiden sich in der Auswahl der Kanten in Schritt 2. und Breitensuche) 2.2.1 Tiefensuche (depth-first search) Bei der Tiefensuche (DFS) bewegt man sich möglichst weit vom Startknoten weg, bevor man die restlichen Knoten besucht. Trifft man auf einen Knoten, der keine unbesuchten Nachbarn hat, so erfolgt "backtracking", d.h. die Suche wird beim Vorgänger fortgesetzt. Dadurch werden alle vom Startknoten erreichbaren Knoten gefunden. 2.2.1.1 Algorithmus Als Eingabe benötigt der Algorithmus einen Graphen und einen Startknoten. - color[v]: repräsentiert den aktuellen Bearbeitungsstatus weiß = unbesucht/unbearbeitet schwarz = abgearbeitet (v und alle Nachbarn von v wurden besucht). grau = in Bearbeitung (v wurde besucht, kann aber noch unbesuchte Nachbarn haben) - p[v]: Vorgänger (predecessor) von v - b[v]: Beginn der Suche (Einfügen des Knotens in den Stack bzw. Zeitpunkt des rekursiven Aufrufs) - f[v]: Ende der Suche (Löschen des Knotens aus dem Stack bzw. Ende des rekursiven Aufrufs) Die Knoten, die in Bearbeitung sind, werden in einem Stack K (LIFO) verwaltet. for each vertex u ∈ V [G ] − {s} color[u ] ← WHITE b[u ] ← ∞ f [u ] ← ∞ p[u ] ← NIL time ← 1 color[ s ] ← GRAY PUSH ( K , s ) b[ s] ← time do 68 Spezielle Algorithmen (SAl) p[ s] ← NIL while K ≠ 0 do u ← TOP( K ) if ∃v ∈ Adj[u ] : color[v] = WHITE then color[v ] ← GRAY PUSH ( K , v) b[v] ← time ← time + 1 else POP( K ) color[u ] ← BLACK f [u ] ← time ← time + 1 Komplexität: Das Initialisieren des Graphen dauert O( | V | ) Zugriffe auf den Stack und die "Arrays" brauchen konstante Zeit (insgesamt O( | V | )), die Adjazenzliste wird genau einmal durchlaufen (O( | E | ). Damit ergibt sich eine Gesamtlaufzeit von O( | V | + | E | ). Bsp.: Tiefensuche in ungerichteten Graphen Anfangsschritt: für alle v ∈ V : color[v] ← WHITE , b[v] ← ∞ , f [v] ← ∞ , p[v] ← NIL Stack 1.Schritt: u v w x y z b[u ] = 1 Stack u v w x y z u 2. Schritt: b[v ] = 2 Stack u v w x y z v u 69 Spezielle Algorithmen (SAl) 3. Schritt: b[ w] = 3 Stack u v w w x y v u z 4. Schritt: b[ y ] = 4 Stack u v w y w x y v u z 5. Schritt: b[ x] = 5 Stack x u v w y w x y v u z 6. Schritt: f [ x] = 6 , back edge zu u und v Stack u v w y w x 7. Schritt: backtracking zu y, y v u z f [ y] = 7 Stack u v w w x y v u z 70 Spezielle Algorithmen (SAl) 8. Schritt: backtracking zu w, f [ z] = 8 Stack u v w z w x y v u z 9. Schritt: f [ z ] = 9 Stack u v w w x y v u z 10. Schritt: backtracking zu w, f [ w] = 10 Stack u v w x y z v u 11. Schritt: backtracking zu v, f [ w] = 10 Stack u v w x y z 12. Schritt: backtracking zu w, u f [u ] = 10 Stack u v w x y z 71 Spezielle Algorithmen (SAl) 2.2.1.2 Eigenschaften von DFS Laufzeit Die Laufzeit von DFS für einen Graphen G = (V , E ) mit n Knoten und m Kanten ist O ( n + m) = O ( V + O E ) . Predecessor-Graph Gegeben ist G = (V , E ) . Der Predecessor-Graph von G wird definiert zu G p = (V , E p ) mit E p = {( parent[ s ], s ) s ∈ V ∧ parent[ s ] ≠ nil }. Der Predecessor-Graph von DFS bildet einen „Depth-First Forest“, der sich aus mehreren „Depth-First Trees“ zusammensetzen kann. Die Kanten von E p nennt man die Baumkanten. Im Algorithmus zur Tiefensuche 55 ist dafür eine Zeitmessung eingeführt: - b[v]: Beginn der Suche - f[v]: Ende der Suche Nach Anwendung von DFS auf einen Graphen G gilt für 2 beliebige Knoten u und v eine der 3 Bedingungen. 1. Die Zeitintervalle b[u] … f[u] und b[v] … f[v] sind disjunkt und weder v noch u sind Nachfahren des jeweils anderen Knoten im DF Forest 2. Ein Intervall b[u] … f[u] ist vollständig im Intervall b[v] … f[v] enthalten, und u ist ein Nachfahre von v in einem Baum des DF Forest. 3. Ein Intervall b[v] … f[v] ist vollständig im Intervall b[u] … f[u] enthalten, und v ist ein Nachfahre von v in einem Baum des DF Forest. Darau folgt direkt Knoten v ist genau dann ein Nachfahre von Knoten u im DF Forest, wenn gilt: f[u] < f[v] < b[v] < b[u] Im DF Forest eines gerichteten oder ungerichteten Graphen G = (V , E ) ist ein Knoten v genau dann ein Nachfahre von Knoten u, falls zu der Zeit, wenn er entdeckt wird (b[u]), ein Pfad von u nach v existiert, der ausschließlich unentdeckte Knoten enthält. Falls der Graph nicht zusammenhängend ist, dann erfordert die Verarbeitung aller Knoten (und Kanten) einige Aufrufe von DFS, jeder Aufruf erzeugt einen Baum. Die ganze Sammlung ist ein depth first spanning forest. 55 vgl. 2.2.1.1 72 Spezielle Algorithmen (SAl) 2.2.1.3 Kantenklassenfikation mit DFS Die Tiefensuche kann für eine Kantenklassifikation eines Graphen G = (V , E ) verwendet werden, mit der wichtige Informationen über G gesammelt werden können. Defintion von 4 Kantentypen (, die bei einem DFS-Durchlauf für G produziert werden): 1. Tree Edges (Baumkanten) sind Kanten des DF Forest G p . Die Kante (u,v) ist eine Baumkante, falls v über die Kante (u,v) entdeckt wurde. 2. Back Edges (Rückwärtskanten) sind Kanten (u,v), die einen Knoten u mit einem Vorfahren v in einem DF Forest verbinden. Rückwärtskanten verbinden mit den Vorfahren 3. Forward Edges (Vorwärtskanten) sind Kanten (u,v), die nicht zum DF Forest gehören und einen Knoten u mit einem Nachfolger v verbinden. Vorwärtskanten verbinden mit den Nachkommen. 4. Cross Edges (Querkanten) sind die anderen (nicht direkt verwandten) Kanten. Der angegebene DFS Algorithmus kann so modifiziert werden, dass die Kanten entsprechend der vorstehenden Aufzählung klassifiziert werden. Bsp. 1. Gegeben ist folgender ungerichteter Graph A B D E C Der Graph wird mit folgendem Algorithmus zur Tiefensuche untersucht: void dfs( Vertex v) { v.visited = true; for each Vertex w adjacent to v if (!w.visited) dfs(w); } Abb.: Schablone zu depth-first-search in Pseudocode Für jeden Knoten ist das Feld visited mit false initialisiert. Bei den rekursiven Aufrufen werden nur nicht besuchte Knoten aufgesucht. Start: Knoten A, der als besucht markiert wird. Rekursiver Aufruf dfs(B), B wird als „besucht“ markiert. Rekursiver Aufruf dfs(C), C wird als „besucht“ markiert. Rekursiver Aufruf dfs(D), A und B sind markiert, C ist benachbart aber markiert, Rückkehr zu dfs(C), B ist nun Nachbar aber markiert, nicht besucht (von C aus) ist der Nachbar E. 73 Spezielle Algorithmen (SAl) Rekursiver Aufruf dfs(E), E wird als „besucht“ markiert. A und C werden ignoriert, Rückkehr zu dfs(C), Rückkehr zu dfs(B), A und B werden ignoriert, dfs(A) ignoriert D und E und kehrt zurück. Diese Schritte kann man graphisch mit einem „depth-first spanning treee“ dokumentieren. A B C D E Jede Kante (v,w) im Graph ist im Baum present. Falls ((v,w) bearbeitet wird und w nicht markiert bzw. (w,v) bearbeitet wird und v ist nicht markiert, dann wird das mit einer Baumkante markiert Falls (v,w) bearbeitet wird und w ist schon markiert bzw. (w,v) wird bearbeitet und v ist schon markiert, dann wird eine gestrichelte Linie aufgezeichnet (Rückwärtskante 56). Der Baum simuliert die Präorder Traverse. Die DFS-Klassifizierung eines ungerichteten Graphen G = (V , E ) ordnet jeder Kante zu G entweder in die Klasse Baumkante oder in die Klasse Rückwärtskante ein. 2. Gegeben ist der folgende gerichtete Graph A B D C E G F J I Start: Knoten B Von B aus Besuch der Knoten B, C, A, D, E und F Restart aus einem noch nicht besuchten Knoten, z.B. H Rückwärtskanten: (A,B) (I,H) forward edges: (C,D) (C,E) 57 cross edges: (F,C) (G,F) 56 57 H kein Bestandteil des Baums führen von einem Baumknoten zu einem Nachfolger 74 Spezielle Algorithmen (SAl) B H C G F A J I D E Abb. Tiefensuche Ein Nutzen der Tiefensuche ist die Überprüfungsmöglichkeit auf Zyklen. Gerichtete Graphen sind dann und nur dann azyklisch, wenn sie keine Rückwärtskanten besitzen. Der vorstehende Graph besitzt Rückwärtskanten und ist deshalb azyklisch. 75 Spezielle Algorithmen (SAl) 2.2.1.4 Zusammenhangskomponenten 1. Connected Ein ungerichteter Graph G = (V , E ) heißt genau dann zusammenhängend (connected), wenn es für ein Knotenpaar (v, v' ) ∈ V einen Weg von v nach v' gibt. Ein gerichteter Graph G = (V , E ) heißt stark zusammenhängend, wenn es einen Weg von jedem Knoten zu jedem anderen Knoten im Graphen gibt. Eine Komponente eines ungerichteten Graphen ist ein maximaler Teilgraph in dem jeder Knoten von jedem anderen Knoten aus, erreichbar ist. Die Komponenten können beim Traversieren mittels Tiefen- oder Breitensuche ermittelt werden. Ein ungerichteter Graph G = (V , E ) heißt zweifach zusammenhängend (biconnected), wenn nach dem Entfernen eines beliebigen Knoten v aus G der verbliebene Graph G − v zusammenhängend ist. Eine zweifache Zusammenhangskomponente (biconnected component) eines ungerichteten Graphen ist ein maximaler, zweifach zusammenhängender Untergraph. In einem zweifach zusammenhängenden Graphen kann man einen beliebigen Knoten samt allen inzidenten Kanten entfernen, ohne dass der Graph zerfällt. 2. Artikulationspunkte Falls ein Graph nicht zweifach zusammenhängend ist, werden die Knoten, deren Entfernung den Graphen trennen würden, Artikulationspunkte genannt. B A C D F G E Die Entfernung vom Knoten C trennt den Knoten G vom Graphen Die Entfernung vom Knoten D trennt die Knoten E und F vom Graphen Abb.: Ein Graph mit den Artikulationspunkten C und D Kritische Knoten und kritische Kanten Kanten und Knoten eines ungerichteten Graphen sind dann kritisch, wenn sich bei ihrer Entfernung die Anzahl der Komponenten des Graphen erhöht. 76 Spezielle Algorithmen (SAl) a b a c b d c d kritische Kante kritische Knoten e f e g f g Abb.: Zur Bestimmung der Artikulationspunkte werden die Knoten des Graphen während der Tiefensuche in „preorder“ Reihenfolge durchnummeriert und bei der Rückkehr aus der Tiefensuche jeder Kante die kleinste Nummer aller über die Kante erreichbaren Knoten zugewiesen. Bsp. a b c d e f g Eine Kante ist genau dann kritisch, wenn die kleinste über sie erreichbare Knotennummer größer ist als die Nummer des Knotens von dem aus si während der Tiefensuche traversiert wird. Ein Knoten (mit Ausnahme des Startknoten) ist genau dann kritisch, wenn bei der Tiefensuche für mindestens eine der von ihm ausgehenden Kanten die kleinste über diese während der Tiefensuche erreichbare Knotennummer größer oder gleich der Nummer dieses Knoten ist, Der Startknoten der Tiefensuche ist genau dann kritisch, wenn von der Wurzel des bei der Tiefensuche generierten spannenden Baums mehr als eine Kante ausgeht. 77 Spezielle Algorithmen (SAl) Algorithmus Mit Hilfe der DFS kann ein Algorithmus (mit linearer Laufzeit) zur Bestimmung aller Artikulationspunkte in einem zusammenhängenden Graphen gefunden werden. 1. Start mit irgendeinem Knoten und Ausführen der Tiefensuche, Durchnummerieren der Knoten, wie sie bei der Suche anfallen (preorder-number Num(v). 2. Für jeden Knoten im „depth-first search spanning tree“, der von v mit 0 oder mehr Baumkanten und dann möglicherweise über eine Rückwärtskante erreichbar ist, Berechnung des am niedrigsten nummerierten Knoten ( Low(v) ). 3. Low(v ) ist das Minimum von 1. Num(v) 2. das niedrigste Num( w) unter allen Rückwärtskanten (v,w) 3. das niedrigste Low( w) unter allen Baumkanten (v,w). Low kann nur bewertet werden, wenn alle Kinder von v bei der Berechnung von Low(v) berücksichtigt sind, d.h. eine Postorder-Traverse ist nötig. Für jede Kante (v,w) kann bestimmt werden, ob eine Baumkante oder eine Rückwärtskante vorliegt ( Num(v ) bzw. Num( w) ). Low(v ) kann also leicht über das Durchlaufen der Adjazenzliste von v über das Feststellen des Minimums errechnet werden. Die Laufzeit liegt bei O ( E + V ). A 1/1 B 2/1 C 3/1 D 4/1 G 7/7 E 5/4 F 6/4 Abb.: DFS-Baum mit Num und Low Bestimmen der Artikulationspunkte - die Wurzel ist dann und nur dann ein Artikulationspunkt, wenn sie mehr als ein Kind besitzt. Das Entfernen der Wurzel im Bsp. kettet lediglich die Wurzel aus. - irgendein anderer Knoten v ist dann und nur dann Artikulationspunkt, wenn v ein Kind w hat, so dass Low( w) ≥ Num(v) . Im Bsp. bestimmt der Algorithmus zu Artikulationspunkten C und D. Low( E ) ≥ Num( D) . Es gibt nur einen Weg von E aus, das ist der Weg durch D - C ist ein Artikulationspunkt, weil Low(G ) ≥ Num(C ) ist. - D hat Kind E, 78 Spezielle Algorithmen (SAl) C 1/1 D 2/1 E 3/2 G 7/7 A 5/1 F 4/2 B 6/1 Abb.: depth-first Baum, falls Start der DFS im Punkt C Der Algorithmus kann implementiert werden durch 1. Ausführen einer Preorder-Traverse zur Berechnung von Num 2. Ausführen einer Postorder-Traverse zur Berechnung von Low 3. Überprüfen, welche Knoten Artikulationspunkte sind. Der erste Durchgang wird beschrieben durch folgenden Algorithmus: void assignNum(Vertex v) { v.num = counter++; v.visited = true; for each Vertex w adjacent to v if (!w.visited) { w.parent = v; assignNum(w); } } Abb.: Pseudocode für Zuweisen Num 58 Der zweite und dritte Durchgang mit Postorder-Traversen nimmt dann folgende Gestalt an: void assignLow( Vertex v) { v.low = v.num; // Regel 1 for each Vertex w adjacent to v { if (w.num > v.num) // Vorwärts-Kante { assignLow(w); if (w.low >= v.num ) System.out.println(v + “ ist ein Artikulationspunkt”); v.low = min(v.low,w.low); // Regel 3 } else if (v.parent != w) // Rückwärtskante v.low = min(v.low, w.num); // Regel 2 } } Abb.: Pseudocode für Berechnung von Low und Test auf Artikulationspunkte 59 58 59 vgl. Weiss, Mark Allen: Data Structures and Algorithms Analysis in Java, Second Edition, Seite 361 vgl. Weiss, Mark Allen: Data Structures and Algorithms Analysis in Java, Second Edition, Seite 362 79 Spezielle Algorithmen (SAl) 3. Starke Zusammenhangskomponenten 1. Zwei Knoten v, w eines gerichteten Graphen G = (V , E ) heißen stark verbunden, falls es einen Weg von v nach w und von w nach v gibt. 2. Eine starke Zusammenhangskomponente ist ein Untergraph von G mit maximaler Knotenzahl, in der alle Paare von Knoten stark verbunden sind 3. Eine starke Zusammenhangskomponente (strongly connected components) eines gerichteten Graphen G = (V , E ) ist eine maximale Knotenmenge , so dass für jedes Paar gilt: Der Knoten u kann von v aus über einen Pfad, der vollständig zu C gehört, erreicht werden. 4. Ein gerichteter Graph wird als stark zusammenhängend bezeichnet, wenn er aus einer einzigen starken Zusammenhangskomponenten besteht. Besitzt G genau eine starke Zusammenhangskomponente, so ist G stark verbunden. Algorithmus für Strongly-Connected Components (SCC(G)) 1. Berechne die “finishing time” f für jeden Knoten mit DFS(G). G T = (V , E T ) G = (V , E ) , wobei E = {(v, u ) | (u, v ) ∈ E}. E besteht also aus den umgedrehten Kanten von G . G und G T haben 2. Berechne den T transponierten Graphen von T die gleichen starken Zusammenhangskomponenten. 3. Berechne dfs (G T ) , wobei die Knoten in der Reihenfolge ihrer „finishing-time“-Einträge aus der dfs(G ) -Berechnung in Schritt 1 (fallend) in der Hauptschleife von dfs (G T ) abgearbeitet wurden. Bsp.: Gegeben ist A B D C E G F J H I Abb.: Ein gerichteter Graph G 80 Spezielle Algorithmen (SAl) A, 3 B, 6 D, 2 C, 4 G, 10 F, 5 E, 1 Abb.: G T H, 9 J, 8 I, 7 I durchnummeriert in Postorder-Traversal von G Eine Tiefensuche zu G T wird mit dem Knoten begonnen, der die größte Nummer besitzt (Knoten G). Das führt aber nicht weiter, die nächste Suche wird bei H gestartet und I bzw. J aufgesucht. Der nächste Aufruf startet bei B, besucht A, C und F. Danach werden dfs ( D) und schließlich dfs ( E ) aufgerufen. Es ergibt sich folgender depth-first spanning forest. G H B D E A I C J F Abb.: Tiefensuche mit G - starke Komponenten sind {G}, {H , I , J }, {B, A, C , F }, {D}, {E} T Jeder dieser Bäume im depth-first zusammenhängende Komponente spanning tree bildet eine stark Komponenten-Graph Der Komponenten-Graph G SCC = (V SCC , E SCC ) wird folgendermassen aufgebaut: 4. Schwach zusammenhängende Komponenten eines gerichteten Graphen (weakly connected components) Die schwach zusammenhängenden Komponenten eines gerichteten Graphen entsprechen den Komponenten jenes Graphen der entsteht, wenn sämtliche gerichtete Kanten durch ungerichtete Kanten ersetzt werden. 81 Spezielle Algorithmen (SAl) 5. Erreichbarkeit (reachability) Für jedes Paar Knoten in einem Graph, z.B. (vi , v j ) , ist v j erreichbar von vi , wenn ein direkter Pfad von vi nach v j besteht. Dies definiert die Erreichbarkeitsrelation R . Für jeden Knoten vi bestimmt die Tiefensuche die Liste aller Knoten, die von vi aus erreichbar sind. Wendet man die Tiefensuche auf jeden Knoten des Graphen an, gibt es eine Reihe von Erreichbarkeitslisten, die die Relation R ausmachen. Diese Relation kann mit Hilfe einer (n × n ) -Erreichbarkeitsmatrix beschrieben werden, die eine 1 an der Stelle (i, j ) hat (vorgesehen für vi Rv j ). Bsp.: Für den Graphen A B C D sind Erreichbarkeitsliste bzw. Erreichbarkeitsmatrix A: B: C: D: A B C B B D C B D D 1 0 0 0 1 1 1 0 1 0 1 0 1 1 1 1 Die Erreichbarkeitsmatrix dient zur Bestimmung, ob es einen Pfad zwischen 2 Knoten gibt. Tiefensuche-Algorithmus für das Verbindungsproblem Der folgende Algorithmus 60 bestimmt alle Knoten, die mit einem gegebenen verbunden sind: typedef vertex<string> node; typedef node::vertex_list nodeList; // Knotenliste void findReachable(node& quelle, nodeList& reachable) { // finde alle Knoten, die von quelle aus erreichbar sind // mit Hilfe der Tiefensuche reachable.insert(&quelle); nodeList::iterator itr = quelle.neighbors().begin(), stop = quelle.neighbors().end(); for( ; itr != stop; ++itr) if (reachable.count(*itr) == 0) findReachable(**itr, reachable); } 60 vgl. Algorithmen und Datenstrukturen, Skript SS09: /pgc/pr52_144/erreichbar.cpp 82 Spezielle Algorithmen (SAl) 2.2.1.5 Topologisches Sortieren mittels Tiefensuche Gegeben: Ein gerichteter, azyklischer Graph (directed acyclic graph, DAG)). Eine topologische Sortierung eines DAG ist eine (lineare) Ordnung aller Knoten, so dass für alle Kanten (u, v) des Graphen gilt: Der knoten (u ) erscheint in der Ordnung vor v . Eine topologische Sortierung 61 eines gerichteten azyklischen Graphen kann man sich als Aufreihung aller seiner Knoten entlang einer horizontalen Linie vorstellen, wobei alle gerichteten Kanten von links nach rechts führen. Bsp.: Gegeben ist der folgende, gerichtete und azyklische Graph: 2 10 1 4 9 6 8 3 7 5 Im vorliegenden Fall zeigt die Ausgabe 7 9 1 2 4 6 3 5 8 10 an, daß eine lineare Ordnung erreicht wurde. Abb.: Man kann feststellen, daß das vorliegende Ergebnis dem Eintragen der vorgängerlosen Elemente in einen Stapel entspricht. Allerdings muß der Stapel in umgekehrter Reihenfolge für den Erhalt der linearen Ordnung interpretiert werden. Der Algorithmus, der zum topologischen Sortieren führt, ist offensichtlich rekursiv. Der folgende Pseudo-Code berechnet eine topologische Sortierung für einen DAG: 1. Starte DFS und berechne die „finishing time“ (f) für alle Knoten 2. Wenn ein Knoten abgearbeitet ist, dann füge ihn am Anfang der (Ordnungs-) Liste ein. 3. Gib die Liste zurück 61 http://de.wikipedia.org/wiki/Topologisches_Sortieren 83 Spezielle Algorithmen (SAl) Bsp.: Gegeben ist der folgende gerichtete Graph 0 1 2 3 4 5 6 7 Gib die topologische Sortierung mit Hilfe der Tiefensuche für diesen Graphen an. Jeder Knoten erhält eine Farbe 62. 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 5 6 7 8 4 62 - weiße Knoten wurden noch nicht behandelt - schwarze Knoten wurden vollständig abgearbeitet - Graue Knoten sind noch in Bearbeitung 84 Spezielle Algorithmen (SAl) 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 7 8 7 8 7 8 7 8 7 6 8 4 5 6 85 7 Spezielle Algorithmen (SAl) 0 1 2 7 3 6 8 4 5 6 7 0 1 2 3 7 6 8 4 5 6 7 0 1 2 3 7 6 8 4 5 6 7 0 1 2 3 6 5 7 8 4 5 6 7 0 1 2 3 6 5 7 8 4 4 4 5 6 86 7 Spezielle Algorithmen (SAl) 0 1 2 3 7 6 5 8 3 4 4 5 6 7 0 1 2 3 6 5 7 8 2 3 4 4 5 6 7 0 1 2 3 7 1 6 5 8 2 3 4 4 5 6 7 Eine umgekehrte postorder-Beziehung entspricht einer topologischen Sortierung. Bsp.: a b c d e f g h i Eine in a beginnende Tiefensuche liefert die „postorder“-Reihenfolge: i h e f d a g b c . Die umgekehrte Reihenfolge c b g a d f c h i ist topologisch sortiert. Eine bei c brginnrnde Tiefensuche liefert die „postorder“-Reihenfolge i g c h e f d b a. Die umgekehrte Reihenfolge a b d f e h c g a ist ebenfalls topologisch sortiert. 87 Spezielle Algorithmen (SAl) 2.2.2 Breitensuche (breadth-first search) Die Suche beginnt beim Startknoten, danach werden die Nachbarn der Startknoten besucht, danach die Nachbarn der Nachbarn usw. Dadurch kann die Knotenmenge – entsprechend ihrer minimalen Anzahl von Kanten zum Startknoten – in Level unterteilt werden. In Level 0 befindet sich nur der Startknoten, Level 1 besteht aus allen Nachbarn des Startknoten, usw. Algorithmus Eingaben sind ein Graph G=(V,E) und ein Startknoten s. Zu jedem Knoten speichert man einige Daten: color[v]: repräsentiert den aktuellen Bearbeitungsstatus weiß = unbearbeitet / unbesucht schwarz = abgearbeitet (v und alle Nachbarn von v wurden besucht) grau = in Bearbeitung (v wurde besucht, kann aber noch unbesuchte Nachbarn haben p[v]: Vorgänger (predecessor) von v d[v]: Distanz zum Startknoten bzgl. der minimalen Kantenzahl Zum Speichern der Knoten, die in Bearbeitung sind, wird eine Warteschlange Q (FIFO) verwendet. u ∈ V [G ] − {s} do color[u ] ← WHITE d [u ] ← ∞ p[u ] ← NIL color[ s ] ← GRAY d [ s] ← 0 p[ s ] ← NIL Q←s while Q ≠ 0 // u ist erstes Element in Q do u ← first[Q ] // Nachbarn von u for v ∈ Adj[u ] do if color[v] ← WHITE then color[v ] ← GRAY d [v] ← d [u ] + 1 p[v] ← u for each vertex ENQUEUE(Q,v) DEQUE(Q) color[v] ← BLACK // füge in Q ein // lösche erstes Element aus Q Komplexität Das Initialisieren der Arrays dauert insgesamt O( | V | ). Die Operationen auf der Liste (Einfügen und Löschen) und den Arrays brauchen konstante Zeit, insgesamt O( | V | ). Das Durchsuchen der Adjazensliste dauert O( | E | ). Damit ergibt sich eine Gesamtlaufzeit von O( | V | + | E | ). 88 Spezielle Algorithmen (SAl) BFS-Baum Die Breitensuche konstruiert einen Baum, der die Zusammenhangskomponente des Startknotens aufspannt. Der Weg im Baum vom Startknoten (Wurzel) zu den Nachfolgern entspricht dem kürzesten Weg bzgl. der Kantenzahl im Graphen. Die Levelnummer des Knotens entspricht der Höhe im Baum. Bsp.: Breitensuche im ungerichteten Graphen Anfangsschritt: für alle v ∈ V : color[v] ← WHITE , d [v ] = ∞ , p[v] ← NIL r s t u v w x y 1. Schritt: Startknoten wird grau markiert, Q = (s), d[s] = 0 r s t u v w x y 2. Schritt: Q = (w,r), Level 0 abgearbeitet r s t u v w x y r s t u v w x y t u 3. Schritt: Q = (r,t,x) 4. Schritt: Q = (t,x,v), Level 1 abgearbeitet r s v w x y 89 Spezielle Algorithmen (SAl) 5. Schritt: Q = (x,v,u) r s v w t u x y 6. Schritt: Q = (v,u,y) r s v w t u x y 7. Schritt: Q = (u,y) r s v w r s v w t u x y 8. Schritt: Q = (y) t u x y 9. Schritt: Q = (), Level 3 abgearbeitet r s v w t u x y 90 Spezielle Algorithmen (SAl) 10. Schritt: 1 0 2 3 r s t u v w x y 2 1 2 3 2.2.3 Implementierung In der Klasse Graph sind Tiefensuche (Methode traverseDFS) und Breitensuche (traverseBFS) implementiert 63. import java.util.*; /** Graphrepräsentation. */ /** Repräsentiert einen Knoten im Graphen. */ class Vertex { Object key = null; // Knotenbezeichner LinkedList edges = null; // Liste ausgehender Kanten /** Konstruktor */ public Vertex(Object key) { this.key = key; edges = new LinkedList(); } /** Ueberschreibe Object.equals-Methode */ public boolean equals(Object obj) { if (obj == null) return false; if (obj instanceof Vertex) return key.equals(((Vertex) obj).key); else return key.equals(obj); } /** Ueberschreibe Object.hashCode-Methode */ public int hashCode() { return key.hashCode(); } } /** Repraesentiert eine Kante im Graphen. */ class Edge { Vertex dest = null; // Kantenzielknoten int weight = 0; // Kantengewicht /** Konstruktor */ public Edge(Vertex dest, int weight) { this.dest = dest; this.weight=weight; } } class GraphException extends RuntimeException { public GraphException( String name ) { super( name ); } } public class Graph 63 http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr52220 91 Spezielle Algorithmen (SAl) { protected Hashtable vertices = null; // enthaelt alle Knoten des Graphen /** Konstruktor */ public Graph() { vertices = new Hashtable(); } /** Fuegt einen Knoten in den Graphen ein. */ public void addVertex(Object key) { if (! vertices.containsKey(key)) // throw new GraphException("Knoten existiert bereits!"); vertices.put(key, new Vertex(key)); } /** Fuegt eine Kante in den Graphen ein. */ public void addEdge(Object src, Object dest, int weight) { Vertex vsrc = (Vertex) vertices.get(src); Vertex vdest = (Vertex) vertices.get(dest); if (vsrc == null) throw new GraphException("Ausgangsknoten existiert nicht!"); if (vdest == null) throw new GraphException("Zielknoten existiert nicht!"); vsrc.edges.add(new Edge(vdest, weight)); } /** Liefert einen Iterator ueber alle Knoten. */ public Iterator getVertices() { return vertices.values().iterator(); } /** Liefert den zum Knotenbezeichner gehoerigen Knoten. */ public Vertex getVertex(Object key) { return (Vertex) vertices.get(key); } /** Liefert die Liste aller erreichbaren Knoten in Breitendurchlauf. */ public List traverseBFS(Object root) { LinkedList list = new LinkedList(); Hashtable d = new Hashtable(); Hashtable pred = new Hashtable(); Hashtable color = new Hashtable(); Integer gray = new Integer(1); Integer black = new Integer(2); Queue q = new Queue(); Vertex v, u = null; Iterator eIter = null; //v = (Vertex)vertices.get(root); color.put(root, gray); d.put(root, new Integer(0)); q.enqueue(root); while (! q.isEmpty()) { v = (Vertex) vertices.get(((Vertex)q.firstEl()).key); eIter = v.edges.iterator(); while(eIter.hasNext()) { u = ((Edge)eIter.next()).dest; // System.out.println(u.key.toString()); if (color.get(u) == null) { color.put(u, gray); d.put(u, new Integer(((Integer)d.get(v)).intValue() + 1)); pred.put(u, v); q.enqueue(u); } } q.dequeue(); list.add(v); color.put(v, black); } 92 Spezielle Algorithmen (SAl) return list; } /** Liefert die Liste aller erreichbaren Knoten im Tiefendurchlauf. */ public List traverseDFS(Object root) { // Loesungsvorschlag: H. Auer LinkedList list = new LinkedList(); // Hashtable d = new Hashtable(); // Hashtable pred = new Hashtable(); Hashtable color = new Hashtable(); Integer gray = new Integer(1); Integer black = new Integer(2); Stack s = new Stack(); Vertex v, u = null; Iterator eIter = null; //v = (Vertex)vertices.get(root); color.put(root, gray); // d.put(root, new Integer(0)); s.push(root); while (! s.empty()) { v = (Vertex) vertices.get(((Vertex)s.peek()).key); eIter = v.edges.iterator(); u = null; Vertex w; while(eIter.hasNext()) { w = ((Edge)eIter.next()).dest; // System.out.println(u.key.toString()); if (color.get(w) == null) { u = w; break; } } if (u != null) { color.put(u, gray); s.push(u); } else { v = (Vertex) s.pop(); list.add(v); color.put(v, black); } } return list; } } Anstatt einen Stapel explizit in die Tiefensuche einzubeziehen, kann man Tiefensuche rekursiv so formulieren: LinkedList liste = new LinkedList(); Hashtable color = new Hashtable(); Integer gray = new Integer(1); Integer black = new Integer(2); // Iterator eIter = null; public List traverseDFSrek(Object root) { // LinkedList list = new LinkedList(); // Hashtable d = new Hashtable(); // Hashtable pred = new Hashtable(); // Hashtable color = new Hashtable(); // Integer gray = new Integer(1); // Integer black = new Integer(2); // Stack s = new Stack(); Vertex v = (Vertex) root; Vertex u = null; Iterator eIter = null; //v = (Vertex)vertices.get(root); color.put(root, gray); // d.put(root, new Integer(0)); // s.push(root); // while (! s.empty()) // { 93 Spezielle Algorithmen (SAl) // v = (Vertex) vertices.get(((Vertex)s.pop()).key); // liste.add(v); // color.put(v,black); eIter = v.edges.iterator(); while(eIter.hasNext()) { u = ((Edge)eIter.next()).dest; // System.out.println(u.key.toString()); if (color.get(u) == null) { color.put(u, gray); traverseDFSrek(u); } } liste.add(v); // s.pop(); // list.add(v); color.put(v, black); //} return liste; } Abb.: Durchläufe zur Breiten- bzw. Tiefensuche 94 Spezielle Algorithmen (SAl) 2.3 Topologischer Sort Sortieren bedeutet Herstellung einer totalen (vollständigen) Ordnung. Es gibt auch Prozesse zur Herstellung von teilweisen Ordnungen 64, d.h.: Es gibt eine Ordnung für einige Paare dieser Elemente, aber nicht für alle. Die Kanten eines gerichteten Graphen bilden eine Halbordnung (die Ordnungsrelation ist nur für solche Knoten definiert, die auf dem gleichen Pfad liegen). y x< y y < z y<z x z Eine strenge Halbordnung ist irreflexiv und transitiv ( x < y ∧ y < z ⇒ x < z ). Topologisches Sortieren 65 bringt die Kanten eines gerichteten, zyklenfreien Graphen in eine Reihenfolge, die mit der Halbordnung verträglich ist. In Graphen für die Netzplantechnik ist die Feststellung partieller Ordnungen zur Berechnung der kürzesten (und längsten) Wege erforderlich. Bsp.: Die folgende Darstellung zeigt einen Netzplan zur Ermittlung des kritischen Wegs. Die einzelnen Knoten des Graphen sind Anfangs- und Endereignispunkte der Tätigkeiten, die an den Kanten angegeben sind. Die Kanten (Pfeile) beschreiben die Vorgangsdauer und sind Abbildungen binärer Relationen. Zwischen den Knoten liegt eine partielle Ordnungsrelation. Bestelle A 50 Tage Baue B 1 Teste B 4 20 Tage Korrigiere Fehler 2 25 Tage 15 Tage 3 Handbucherstellung 60 Tage Abb. : Ein Graph der Netzplantechnik Zur Berechnung des kürzesten Wegs sind folgende Teilfolgen, die partiell geordnet sind, nötig: 1 -> 3: 50 Tage 1->4->2->3: 60 Tage 1->4->3: 80 Tage (kürzester Weg) 64 65 vgl. 1.2.2.2 http://de.wikipedia.org/wiki/Topologisches_Sortieren 95 Spezielle Algorithmen (SAl) Eindeutig ist das Bestimmen der topologischen Folgen nicht. Zu dem folgenden Graphen 2 1 4 3 kann es mehrere topologische Folgen geben.Zwei dieser topologischen Folgen sind 1 2 1 3 3 4 2 4 Abb.: Bezugspunkt zur Ableitung eines Algorithmus für den topologischen Sort ist ein gerichteter, azyklischer Graph, z.B. 0 1 1 2 2 3 1 3 4 5 3 2 6 7 Über der Knotenidentifikationen ist zusätzlich die Anzahl der Vorgänger vermerkt. Dieser Zähler wird in die Knotenbeschreibung aufgenommen. Der Zähler soll festhalten, wie viele unmittelbare Vorgänger der Knoten hat. Hat ein Knoten keine Vorgänger, dann wird der Zähler auf 0 gesetzt. Alle Knoten, die den Eingangsgrad 0 aufweisen, werden in einem Stapel oder in einer Schlange abgelegt. Ist z.B. die Schlange nicht leer, wird der Knoten, z.b. v, entfernt. Die Eingangsgrade aller Knoten, die zu v benachbart sind, werden um eine Einheit erniedrigt.. Sobald Eingangsgrade zu 0 werden, werden sie in die Schlange aufgenommen. Die topologische Sortierung ist dann so gestaltet, wie die Knoten aus der Schlange entfernt werden. Damit kann der Algorithmus 66 durch folgende Pseudocode-Darstellung beschrieben werden. 66 Der hier angegebene Algorithmus setzt voraus, dass der Graph in einer Adjazenzliste abgebildet ist, Eingangsgrade berechnet wurdem und zusammen mit den Knoten abgespeichert wurden. 96 Spezielle Algorithmen (SAl) void topsort() { Queue<Vertex> q = new Queue<Vertex>(); int zaehler = 0; Vertex v, w; for each v if (v.indegree 67 == 0) q.enqueue(v); while (!q.isEmpty()) { v = q.dequeue(); zaehler++; for each w adjacent to v if (--w.indegree == 0) q.enqueue(w); } if (zaehler >= anzahlKnoten) throw new CycleFoundException; } Abb.: Pseudocode zur Durchführung einer topologischen Sortierung Zur Bestimmung der gewünschten topologischen Folge wird mit den Knotenpunktnummern begonnen, deren Zähler den Wert 0 enthalten. Sie verfügen über keinen Vorgänger und erscheinen in der topologischen Folge an erster Stelle. Schreibtischtest. Die folgende Tabelle soll anhand des folgenden Graphen 1 2 3 4 5 6 7 die Veränderung des Zählers für unmittelbare Vorgänger zeigen und über die Knotenidentifikationen das Ein- bzw. Ausgliedern aus der Schlange (Queue) q. Vertex 1 2 3 4 5 6 7 Enqueue Dequeue 1 0 1 2 3 1 3 2 1 1 2 0 0 1 2 1 3 2 2 2 3 0 0 1 1 0 3 2 5 5 4 0 0 1 0 0 3 1 4 4 5 0 0 0 0 0 2 0 3,7 3 Abschätzung der Laufzeit (Komplexität): O( E + V 6 0 0 0 0 0 1 0 7 ) 7 0 0 0 0 0 0 0 6 6 (, falls Adjazenzlisten benutzt werden. Das ist einleuchtend, da man davon ausgehen kann 67 indegree (Eingangsgrad) ist der Zähler für die jeweilige Anzahl von Vorgängerknoten 97 Spezielle Algorithmen (SAl) - der Schleifenkörper wird einmal je Kante ausgeführt - die Schlangenoperationen werden meistens einmal je Knoten ausgeführt - der Zeitbedarf für die Initialisierung ist proportional zur Größe des Graphen Übung 23_1: Topologischer Sort, Projektplanung 2.4 Transitive Hülle Welche Knoten sind von einem gegeben Knoten aus erreichbar? Gibt es Knoten, von denen aus alle anderen Knoten erreicht werden können? Die Bestimmung der transitiven Hülle ermöglicht die Beantwortung solcher Fragen 68. S. Warshall hat 1962 einen Algorithmus entwickelt, der die Berechnung der transitiven Hülle über seine Adjazenzmatrix ermöglicht und nach folgenden Regeln arbeitet: Falls ein Weg existiert, um von einem Knoten x nach einem Knoten y zu gelangen, und ein Weg, um vom Knoten y nach z zu gelangen, dann existiert auch ein Weg, um vom Knoten x nach dem Knoten z zu gelangen. Bsp.: Der folgende Graph enthält gestrichelte Kanten, die die Erreichbarkeit markieren A B C D E Die zu diesem Graphen errechnete transitive Hülle beschreibt die folgende Erreichbarkeitsmatrix (Wegematrix): 1 0 0 1 0 1 1 0 1 0 1 1 1 1 1 0 0 0 1 0 1 1 0 1 1 2.4.1 Berechnung der Erreichbarkeit mittels Matrixmultiplikation Eine häufig vorkommende Frage ist die nach dem Zusammenhang zweier Knoten. Das kann man aus der Wegematrix 69 sofort ablesen. Die Wegematrix kann aus Adjazenzmatrix und Kantenfolgen mit Matrixoperationen leicht bestimmt werden: - In einem unbewerteten Graphen mit Adjazenzmatrix A beschreibt A = A ⋅ A ⋅ A ⋅ ... ⋅ A = ( aij 68 69 http://de.wikipedia.org/wiki/Transitive_Hülle vgl. 2.2.1.4 98 (r ) ) Spezielle Algorithmen (SAl) (r ) aij : Anzahl der Kantenfolgen von xi nach x j der Länge r (Beweis über vollständige Induktion r nach Definition des Matrixprodukts) - Ein gerichteter Graph ist genau dann azyklisch, wenn A r = 0 für ein geeignetes r ≤ n = V , denn A r ≠ 0 und in einem r ayklischen Graphen hat eine Kantenfolge höchstens die Länge n − 1 also A = 0 für r ≥ n . - Die Wegematrix W ergibt sich aus der Adjazenzmatrix A , indem man in in einem zyklischen Graphen gibt es Kantenfolgen beliebiger Länge, also n −1 ∑A r = A 0 + A1 + ... + A n −1 alle von 0 verschiedene Elemente setzt. A 0 ist die Einheitsmatrix. r =0 Folgerung: Anstatt der Tiefensuche zur Ermittlung der Erreichbarkeitsmatrix kann man den bekannten Algorithmus von Stephan Warshall benutzen. Bsp.: Gegeben ist A B C D Bestimme W ⎛0 ⎜ ⎜0 A=⎜ 0 ⎜ ⎜0 ⎝ 1 0 1 0 1 0 0 0 0⎞ ⎟ 1⎟ 0⎟ ⎟ 0 ⎟⎠ ⎛0 ⎜ ⎜0 2 A =⎜ 0 ⎜ ⎜0 ⎝ 1 0 1 0 1 0 0 0 0⎞ ⎛0 ⎟ ⎜ 1⎟ ⎜0 ⋅ 0⎟ ⎜0 ⎟ ⎜ 0 ⎟⎠ ⎜⎝ 0 1 0 1 0 1 0 0 0 0⎞ ⎛ 0 ⎟ ⎜ 1⎟ ⎜0 = 0⎟ ⎜ 0 ⎟ ⎜ 0 ⎟⎠ ⎜⎝ 0 0 0 0 0 0 0 0 0 1⎞ ⎟ 0⎟ 1⎟ ⎟ 0 ⎟⎠ ⎛0 ⎜ ⎜0 3 A =⎜ 0 ⎜ ⎜0 ⎝ 1 0 1 0 1 0 0 0 0⎞ ⎛ 0 ⎟ ⎜ 1⎟ ⎜0 ⋅ 0⎟ ⎜ 0 ⎟ ⎜ 0 ⎟⎠ ⎜⎝ 0 0 0 0 0 0 0 0 0 1⎞ ⎛0 ⎟ ⎜ 0⎟ ⎜0 = 1⎟ ⎜0 ⎟ ⎜ 0 ⎟⎠ ⎜⎝ 0 0 0 0 0 0 0 0 0 1⎞ ⎟ 0⎟ 0⎟ ⎟ 0 ⎟⎠ ⎛0 ⎜ ⎜0 4 A =⎜ 0 ⎜ ⎜0 ⎝ 1 0 1 0 1 0 0 0 0⎞ ⎛0 ⎟ ⎜ 1⎟ ⎜0 ⋅ 0⎟ ⎜0 ⎟ ⎜ 0 ⎟⎠ ⎜⎝ 0 0 0 0 0 0 0 0 0 1⎞ ⎛0 ⎟ ⎜ 0⎟ ⎜ 0 = 0⎟ ⎜ 0 ⎟ ⎜ 0 ⎟⎠ ⎜⎝ 0 0 0 0 0 0 0 0 0 0⎞ ⎟ 0⎟ 0⎟ ⎟ 0 ⎟⎠ 99 Spezielle Algorithmen (SAl) ⎛1 ⎜ ⎜0 0 1 2 3 W = A +A +A +A =⎜ 0 ⎜ ⎜0 ⎝ 1 1 1 0 1 0 1 0 1⎞ ⎟ 1⎟ 1⎟ ⎟ 1⎟⎠ 2.4.2 Warshalls Algorithmus zur Bestimmung der Wegematrix Ist man an der Wegematrix interessiert, so kann man die Zahlen ungleich 0 durch 1 zusammenfassen, indem man die Adjazenzmatrix als logische Matrix auffasst und die Addition und Multiplikation als logische Operatoren ∨ und ∧ 70. void warshall :: transitive() { int i, j, k, n; n = get_n(); for (k = 1; k <= n; k++) for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) adj[i][j] = (adj[i][j] || (adj[i][k] && adj[k][j])); } Durch Umordnung der Schleifenreihenfolge, Initialisierung der Diagonale von der Ergebnismatrix C mit 1 (entspricht der Einheitsmatrix A 0 ) und der Überlagerung von n −1 Ein- und Ausgabematrizen wird direkt ∑A r = A 0 + A1 + ... + A n −1 , die Potenzierung r =0 und die Addition der Potenzen also vermieden. void warshall (Bitmat a) { for (int k = 0; k < n; k++) { // Für alle I und j a[i][j] = 1, falls ein Pfad von I nach j // existiert, der nicht durch // irgendeinen Knoten >= k geht. // beachte, ob es einen Pfad vom Knoten i nach j durch k gibt a[k][k] = 1; // ein Knoten ist von sich selbst erreichbar for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) a[i][j] |=a[i][k] & a[k][j]; // a[i][j] = 1, es gibt einen Pfad von i nach j, der nicht // durch irgendeinen Knoten > k geht } } Der Warshall-Algorithmus 71 sucht nach allen möglichen Tripeln durch Erzeugen von 3 verschachtelten Schleifen mit den Laufvariablen i, j und k. Für jedes Paar (i, j ) wird eine Kante (vi , v j ) hinzugefügt, falls es einen Knoten v k gibt, so dass (vi , v k ) und (v k , v j ) in dem erweiterten Graphen sind. Bsp.: Nimm an die Knoten v und w sind erreichbar über einen direkten Weg eines gerichteten Pfads von 5 Knoten: v = x1 , x 2 , x3 , x 4 , x5 = w . Mit dreifach verschachtelten 70 in C++: | und &, vgl. pr54010.cpp in pr54_1 71 http://de.wikipedia.org/wiki/Algorithmus_von_Floyd_und_Warshall 100 Spezielle Algorithmen (SAl) Schleifen werden alle möglichen Knoten-Tripel betrachtet. Falls die Knoten x1 ...x5 in der angegebenen Reihenfolge erscheinen, dann ist x 2 identifiziert als der Knoten x1 , der x1 und x3 verbindet. Das führt zu der neuen Kante (x1 , x3 ) . x1 und x 4 haben x3 als verbundene Knoten, da der Verbindungsweg x1 und x3 in einem früheren Stadium der Iteration gefunden wurde. So wird ( x1 , x 4 ) hinzugefügt, danach x1 und x5 über x 4 mit ( x1 , x5 ) ergänzt. 2.4.3 Floyds Algorithmus zur Bestimmung der Abstandsmatrix Falls in Warhalls Algorithmus die Diagonale mit 0 (Abstand eines Knoten zu sich selbst) initialisiert wird, bei der Verkettung zweier Pfade & durch + (Summe der Pfadlängen) und beim Finden eines neuen Pfads | durch min (Minimum vom neuen und alten Abstand) ersetzen, erhält man Floyds Algorithmus zur Bestimmung der Abstandsmatrix. 72 void floyd :: floydAl() { int i, j, k, n, dist = 0; n = get_n(); for (k = 0; k < n; k++) { adj[k][k] = 0; for (i = 0; i < n; i++) for (j = 0; j < n; j++) // min(adj[i][j],adj[i][k] + adj[k][j]); if ((adj[i][k] != INFINITY) && (adj[k][j] != INFINITY)) if (adj[i][j] > (adj[i][k] + adj[k][j])) { adj[i][j] = (adj[i][k] + adj[k][j]); } else; } } 72 http://de.wikipedia.org/wiki/Algorithmus_von_Floyd_und_Warshall 101 Spezielle Algorithmen (SAl) 2.5 Kürzeste Wege 2.5.1 Die Datenstrukturen Graph, Vertex, Edge für die Berechnung kürzester Wege Repräsentation von Knoten 73 // Basic info for each vertex. struct Vertex { string name; // Vertex name vector<Edge> adj; // Adjacent vertices (and costs) double dist; // Cost Vertex *prev; // Previous vertex on shortest path int scratch; // Extra variable used in algorithm // Konstruktor Vertex( const string & nm ) : name( nm ) { reset( ); } void reset( ) { dist = INFINITY; prev = NULL; scratch = 0; } }; Über die Instanzvariable adj wird die Liste der benachbarten Knoten geführt, dist enthält die Kosten, path den Vorgängerknoten vom kürzsten Pfad. Identifiziert wird der Knoten durch einen Namen (Typ: string). Repräsentation von Kanten Die Kanten eines Graphen können Distanzen, Entfernungen, Gewichte, Kosten aufnehmen. Jede Kante eines Graphen wird beschrieben über den Zielknoten und das der Kante zugeordnete Gewicht. struct Edge { // First vertex in edge is implicit Vertex *dest; // Second vertex in edge double cost; // Edge cost // Konstruktor Edge( Vertex *d = 0, double c = 0.0 ) : dest( d ), cost( c ) { } }; Die Klasse Graph zur Aufnahme von Algorithmen zur Berechnung kürzester Pfade class Graph { private: Vertex * getVertex( const string & vertexName ); void printPath( const Vertex & dest ) const; void clearAll( ); typedef map<string,Vertex *,less<string> > vmap; Graph( const Graph & rhs ) { } const Graph & operator= ( const Graph & rhs ) { return *this; } vmap vertexMap; 73 vgl. pr55_1 http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr22859 102 Spezielle Algorithmen (SAl) public: Graph( ) { } ~Graph( ); void addEdge( const string & sourceName, const string & destName, double cost ); void printPath( const string & destName ) const; void unweighted( const string & startName ); void dijkstra( const string & startName ); void negative( const string & startName ); void acyclic( const string & startName ); }; 2.5.2 Kürzeste Pfade in gerichteten, ungewichteten Graphen. Lösungsbeschreibung. Die ungewichteten Graphen G: folgende Abbildung einen gerichteten, k2 k1 k4 k3 zeigt k5 k7 k6 Abb.: Ausgangspunkt ist ein Startknoten s (Eingabeparameter). Von diesem Knoten aus soll der kürzeste Pfad zu allen anderen Knoten gefunden werden. Es interessiert nur die Anzahl der Kanten, die in dem Pfad enthalten sind. Falls für s der Knoten k3 gewählt wurde, kann zunächst am Knoten k3 der Wert 0 eingetragen werden. Die „0“ wird am Knoten k3 vermerkt. k2 k1 k4 k3 k5 0 k6 k7 Abb.: Der Graph nach Markierung des Startknoten als erreichbar Danach werden alle Knoten aufgesucht, die „eine Einheit“ von s entfernt sind. Im vorliegenden Fall sind das k1 und k6. Dann werden die Knoten aufgesucht, die von s zwei Einheiten entfernt sind. Das geschieht über alle Nachfolger von k1 und k6. Im vorliegenden Fall sind es die Knoten k2 und k4. Aus den benachbarten Knoten von k2 und k4 erkennt man, dass k5 und k7 die kürzesten Pfadlängen von drei Knoten besitzen. Da alle Knoten nun bewertet sind ergibt sich folgenden Bild: 103 Spezielle Algorithmen (SAl) k2 k1 1 2 k4 k3 k5 0 2 1 k7 k6 Abb.: Graph nach Ermitteln aller Knoten mit der kürzeszen Pfadlänge 2 Die hier verwendete Strategie ist unter dem Namen „breadth-first search“ 74 bekannt. Die „Breitensuche zuerst“ berücksichtigt zunächst alle Knoten vom Startknoten aus, die am weitesten entfernt liegenden Knoten werden zuerst ausgerechnet. Übertragen der Lösungsbeschreibung in Quellcode. Zu Beginn sollte eine Tabelle mit folgenden Einträgen vorliegen: k k1 k2 k3 k4 k5 k6 k7 bekannt false false false false false false false dk ∞ ∞ 0 ∞ ∞ ∞ ∞ pk 0 0 0 0 0 0 0 Die Tabelle überwacht den Fortschritt beim Ablauf des Algorithmus und führt Buch über gewonnene Pfade. Für jeden Knoten werden 3 Angaben in der Tabelle verwaltet: - die Distanz dk des jeweiligen Knoten zu dem Startknoten s. Zu Beginn sind alle Knoten von s aus unerreichbar ( ∞ ). Ausgenommen ist natürlich s, dessen Pfadlänge ist 0 (k3). - Der Eintrag pk ist eine Variable für die Buchführung (und gibt den Vorgänger im Pfad an). - Der Eintrag unter „bekannt“ wird auf „true“ gesetzt, nachdem der zugehörige Knoten erreicht wurde. Zu Beginn wurden noch keine Knoten erreicht. Das führt zu der folgenden Knotenbeschreibung: struct Vertex { string name; // vector<Edge> adj; // int dist; // Vertex *prev; // Vertex( const string & nm { reset( ); } void reset( ) { dist = INFINITY; prev = Vertex name Adjacent vertices (and costs) Cost Previous vertex on shortest path ) : name( nm ) NULL;} }; Die Grundlage des Algorithmus kann folgendermaßen (in Pseudocode) beschrieben werden: void ungewichtet(Vertex s) 74 vgl. 2.2.2 104 Spezielle Algorithmen (SAl) /* /* /* /* 1 2 3 4 */ */ */ */ /* 5 */ /* 6 */ /* 7 */ /* 8 */ /* 9 */ { Vertex v, w; s.dist = 0; for (int aktDist = 0; aktDist < ANZAHL_KNOTEN; aktDist++) for each v if (!v.bekannt && v.dist == aktDist) { v.bekannt = true; for each w benachbart_zu v if (w.dist == INFINITY) { w.dist = aktDist + 1; w.path = v; } } } Der Algorithmus deklariert schrittweise je nach Distanz (d = 0, d = 1, d= 2 ) die Knoten als bekannt und setzt alle benachbarten Knoten von d w = ∞ auf die Distanz d w = d + 1. 2 Die Laufzeit des Algorithmus liegt bei O( V ) . 75 Die Ineffizienz kann beseitigt werden: Es gibt nur zwei unbekannte Knotentypen mit d v ≠ ∞ . Einigen Knoten wurde dv = aktDist zugeordnet, der Rest zeigt dv = aktDist + 1. Man braucht daher nicht die ganze Tabelle, wie es in Zeile 3 und Zeile 4 beschrieben ist, nach geeigneten Knoten zu durchsuchen. Am einfachsten ist es, die Knoten in zwei Schachteln einzuordnen. In die erste Schachtel kommen Knoten, für die gilt: dv = aktDist. In die zweite Schachtel kommen Knoten, für die gilt: dv = aktDist + 1. In Zeile 3 und Zeile 4 kann nun irgendein Knoten aus der ersten Schachtel herausgegriffen werden. In Zeile 9 kann w der zweiten Schachtel hinzugefügt werden. Wenn die äußere for-Schleife terminiert ist die erste Schachtel leer, und die zweite Schachtel kann nach der ersten Schachtel für den nächsten Durchgang übertragen werden. Durch Anwendung einer Schlange (Queue) kann das Verfahren verbessert werden. Am Anfang enthält diese Schlange nur Knoten mit Distanz aktDist. Benachbarte Knoten haben die Distanz aktDist + 1 und werden „hinten“ an die Schlange angefügt. Damit wird garantiert, daß zuerst alle Knoten mit Distanz aktDist bearbeitet werden. Der verbesserte Algorithmus kann in Pseudocode so formuliert werden: /* 1 */ /* 2 */ /* 3 */ /* /* /* /* 4 5 6 7 */ */ */ */ /* 8 */ /* 9 */ /*10 */ 75 void ungewichtet(Vertex s) { Queue q; Vertex v, w; q = new Queue(); q.enqueue(s); s.dist = 0; while (!q.isEmpty()) { v = q.dequeue(); v.bekannt = true; // Wird eigentlich nicht mehr benoetigt for each w benachbart_zu v if (w.dist == INFINITY) { w.dist = v.dist + 1; w.path = v; q.enqueue(w); } } } wegen der beiden verschachtelten for-Schleifen 105 Spezielle Algorithmen (SAl) Die folgende Tabelle zeigt, wie sich die Daten der Tabelle während der Ausführung des Algorithmus ändern: Anfangszustand k bekannt dk k1 false ∞ k2 false ∞ k3 false 0 k4 false ∞ k5 false ∞ k6 false ∞ k7 false ∞ Q: k3 pk 0 0 0 0 0 0 0 k3 aus der Schlange bekannt dk pk false 1 k3 false 0 ∞ true 0 0 false 0 ∞ false 0 ∞ false 1 k3 false 0 ∞ Q: k1, k6 k1 aus der Schlange bekannt dk pk true 1 k3 false 2 k1 true 0 0 false 2 k1 false 0 ∞ false 1 k3 false 0 ∞ Q: k6, k2, k4 k6 aus der Schlange bekannt dk pk true 1 k3 false 2 k1 true 0 0 false 2 k1 false 0 ∞ true 1 k3 false 0 ∞ Q: k2, k4 k2 aus der Schlange k bekannt dk k1 true 1 k2 true 2 k3 true 0 k4 false 2 k5 false 3 k6 true 1 k7 false ∞ Q: k4, k5 pk k3 k1 0 k1 k2 k3 0 k4 aus der Schlange bekannt dk pk true 1 k3 true 2 k1 true 0 0 true 2 k1 false 3 k2 true 1 k3 false 3 k4 Q: k5, k7 k5 aus der Schlange bekannt dk pk true 1 k3 true 2 k1 true 0 0 true 2 k1 true 3 k2 true 1 k3 false 3 k4 Q: k7 K7 aus der Schlange bekannt dk pk true 1 k3 true 2 k1 true 0 0 true 2 k1 true 3 k2 true 1 k3 true 3 k4 Q: leer Abb.:Veränderung der Daten während der Ausführung des Algorithmus zum kürzesten Pfad Implementierung 76. Die Klasse Graph implementiert die Methode unweighted(). Die Schlange in dieser Liste wird über eine LinkedList mit den Methoden removeFirst() und addLast() simuliert. // Single-source unweighted shortest-path algorithm. void Graph::unweighted( const string & startName ) { vmap::iterator itr = vertexMap.find( startName ); if( itr == vertexMap.end( ) ) throw GraphException( startName + " is not a vertex in this graph" ); clearAll( ); Vertex *start = (*itr).second; list<Vertex *> q; // Schlange q.push_back( start ); start->dist = 0; while( !q.empty( ) ) { Vertex *v = q.front( ); q.pop_front( ); for( int i = 0; i < v->adj.size( ); i++ ) { Edge e = v->adj[ i ]; Vertex *w = e.dest; if( w->dist == INFINITY ) { w->dist = v->dist + 1; w->prev = v; q.push_back( w ); } } } } 76 vgl. http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr22859 106 Spezielle Algorithmen (SAl) 2.5.3 Berechnung der kürzesten Pfadlängen in gewichteten Graphen (Algorithmus von Dijkstra) Gegeben ist ein gerichteter Graph G mit Knotenmenge V und Kantenmenge E. Jede Kante e hat eine nichtnegative Länge, Außerdem ist ein Knoten s (Standort) gegeben. Gesucht ist der kürzeste Weg von s nach v für jeden Knoten v ∈ V des Graphen. Vorausgesetzt ist, dass jeder Knoten v ∈ V durch wenigstens einen Weg von s aus erreichbar ist. Für den kürzesten Weg soll die Länge ermittelt werden. Lösungsbeschreibung. Die Lösung stützt sich auf die Berechnung der kürzesten Pfadlängen in ungewichteten Graphen 77 ab. Im Algorithmus von Dijkstra werden auch die Daten über „bekannt“, dv (kürzeste Pfadlänge) und pv (letzter Knoten, der eine Veränderung von dv verursacht hat) verwaltet. Es wird eine Menge S von Knoten betrachtet und schrittweise vergrößert, für die der kürzeste Weg von s aus bereits bekannt ist. Jedem Knoten v ∈ V wird ein Distanz d(v) zugeordnet. Anfangs ist d(s) = 0 und für alle von s verschiedenen Knoten v ∈ V ist d v = ∞ , und S ist leer. Dann wird S nach dem Prinzip "Knoten mit kürzester Distanz von s zuerst" schrittweise folgendermaßen vergrößert, bis S alle Knoten V des Graphen enthält: 1. Wähle Knoten v ∈ V S mit minimaler Distanz 2. Nimm v zu S hinzu 3. Für jede Kante vw von einem Knoten v zu einem Knoten w ∉ S , ersetze d(w) durch min({d ( w), d (v) + c( w, v)}) Der folgende Graph 2 k2 k1 4 1 3 10 2 2 k4 k3 5 8 k5 4 6 k7 k6 1 Abb.: Graph nach Ermitteln aller Knoten mit der kürzeszen Pfadlänge 2 mit der Knotenbeschreibung struct Vertex { string name; vector<Edge> adj; double dist; Vertex *prev; int scratch; // Konstruktor Vertex( const string & { reset( ); } 77 // // // // // Vertex name Adjacent vertices (and costs) Cost Previous vertex on shortest path Extra variable used in algorithm nm ) : name( nm ) vgl. 2.5.2 107 Spezielle Algorithmen (SAl) void reset( ) { dist = INFINITY; prev = NULL;/* pos = NULL;*/ scratch = 0; } }; führt zu der folgende Initialisierung: k k1 k2 k3 k4 k5 k6 k7 bekannt false false false false false false false dk 0 ∞ ∞ ∞ ∞ ∞ ∞ pk null null null null null null null Abb.: Anfangszustand der Tabelle mit den Daten für den Algorithmus von Dijkstra Der erste Knoten (Start) ist der Knoten k1 mit Pfadlänge 0. Nachdem k1 bekannt ist, ergibt sich folgendes Bild: k k1 k2 k3 k4 k5 k6 k7 bekannt true false false false false false false dk 0 2 ∞ 1 ∞ ∞ ∞ pk null k1 null k1 null null null Abb.: Zustand der Tabelle nach „k1 ist bekannt“ „k1“ besitzt die Nachbarknoten: k2 und k4. „k4“ wird gewählt und als bekannt markiert. Die Knoten k3, k5, k6 und k7 sind jetzt die benachbarten Knoten. k k1 k2 k3 k4 k5 k6 k7 bekannt true false false true false false false dk 0 2 3 1 3 9 5 pk null k1 k4 k1 k4 k4 k4 Abb.: Zustand der Tabelle nach „k4 ist bekannt“ „k2“ wird gewählt. „k4“ ist benachbart, aber schon bekannt. „k5“ ist ebenfalls benachbart, wir aber nicht ausgerichtet, da die Kosten von „k2“ aus 2 +10 = 12 sind und ein Pfad der Länge 3 schon bekannt ist k k1 k2 k3 k4 k5 k6 k7 bekannt true true false true false false false dk 0 2 3 1 3 9 5 pk null k1 k4 k1 k4 k4 k4 Abb.: Zustand der Tabelle nach „k2 ist bekannt“ 108 Spezielle Algorithmen (SAl) Der nächste ausgewählte Knoten ist „k5“ (ohne Ausrichtungen), danach wird k3 gewählt. Die Wahl von „k3“ bewirkt die Ausrichtung von „k6“ k k1 k2 k3 k4 k5 k6 k7 bekannt true true true true true false false dk 0 2 3 1 3 8 5 pk null k1 k4 k1 k4 k3 k4 Abb.: Zustand der Tabelle „k5 ist bekannt“ und (anschließend) „k3 ist bekannt“. „k7“ wird gewählt. Daraus resultiert folgende Tabelle: k k1 k2 k3 k4 k5 k6 k7 bekannt true true true true true false true dk 0 2 3 1 3 6 5 pk null k1 k4 k1 k4 k7 k4 Abb.: Zustand der Tabelle „k7 ist bekannt“. Schließlich bleibt nur noch k6 übrig. Das ergibt dann die folgende Abschlußtabelle: k k1 k2 k3 k4 k5 k6 k7 bekannt true true true true true true true dk 0 2 3 1 3 6 5 pk null k1 k4 k1 k4 k7 k4 Abb.: Zustand der Tabelle nach „k6 ist bekannt“. Der Algorithmus, der diese Tabellen folgendermaßen beschrieben werden: berechnet, kann void dijkstra(Vertex s) { Vertex v, w; /* 1 */ s.dist = 0; /* 2 */ for(; ;) { /* 3 */ v = kleinster_unbekannter_Distanzknoten; /* 4 */ if (v == null) /* 5 */ break; /* 6 */ v.bekannt = true; /* 7 */ for each w benachbart_zu v /* 8 */ if (!w.bekannt) /* 9 */ if (v.dist + cvw < w.dist) { /* 10 */ w.dist = v.dist + cvw; /* 11 */ w.pfad = v; 109 (in Pseudocode) Spezielle Algorithmen (SAl) } } } Die Laufzeit des Algorithmus resultiert aus dem Aufsuchen aller Knoten (in den beiden for-Schleifen) und im Aufsuchen der Kanten (c(vw)) (in der inneren forSchleife): O(|E| + |V|2) = O(|V|2). Ein Problem des vorstehenden Agorithmus ist das Durchsuchen der Knotenmenge nach der kleinsten Distanz 78. Man kann das wiederholte Bestimmen der kleinsten Distanz einer prioritätsgesteuerten Warteschlange übertragen. Der Leistungsaufwand beträgt dann O(|E| log(|V)+|V| log(|V|)). Der Algorithmus (in Pseudocode) könnte so aussehen: /* 1 */ for_all v ∈ V do d (v) =∞ /* 2 */ d ( s ) = 0 ; S = 0 /* 3 */ pq = new PriorityQueue(); // Vorrangwarteschlange für Knoten in V /* 4 */ while pq ≠ 0 do /* pq = V S */ /* 5 */ pq.delete _ min() /* 6 */ S = S ∪ {v} /* 7 */ for _ all (v, w) ∈ E do /* 8 */ if d (v) + c(v, w) < d ( w) pq.decrease _ key ( w, d (v) + c(v, w)) /* 9 */ Entferne (v, w) aus E /*10*/ end while Der “update” wird hier durch eine Operation für eine Priority Queue “decrease_key, Herabsetzen eines Schlüssels um einen vorgegebenen Wert” vollzogen. „decrease_key“ schränkt die Zeitbestimmung für das Minimum auf O(log V ) ein. Damit ergibt sich eine Laufzeit von O( V log V + E log V ) = O( E log V ) prioritätsgesteuerte Warteschlange: Binary-Heaps sind eine mögliche Implementation für Priority Queues. Ein Heap mit N Schlüsseln erlaubt das Einfügen eines neuen Elements und das Entfernen des Minimums in O(log N ) Schritten. Da das Minimum stets am Anfang des Heap steht, kann der Zugriff auf das kleinste Element stets in konstanter Zeit ausgeführt werden. In Java (seit der Version 1.5) und C++ gibt es die Klasse PriorityQueue, die sich auf einen Binary Heap abstützt. In der Priority-Queue werden für den Algorithmus von Dijkstra Datensätze von folgendem Typ abgelegt. // Structure stored in priority queue for Dijkstra's algorithm. struct Path { Vertex *dest; // w double cost; // d(w) Path( Vertex *d = 0, double c = 0.0 ) : dest( d ), cost( c ) { } bool operator> ( const Path & rhs ) const { return cost > rhs.cost; } bool operator< ( const Path & rhs ) const { return cost < rhs.cost; } }; 78 v = kleinster_unbekannter_Distanzknoten 110 Spezielle Algorithmen (SAl) Implementierung. 79 void Graph::dijkstra( const string & startName ) { priority_queue<Path, vector<Path>, greater<Path> > pq; Path vrec; // Stores the result of a deleteMin vmap::iterator itr = vertexMap.find( startName ); clearAll( ); if( itr == vertexMap.end( ) ) throw GraphException( startName + " is not a vertex in this graph" ); Vertex *start = (*itr).second; pq.push( Path( start, 0 ) ); start->dist = 0; for( int nodesSeen = 0; nodesSeen < vertexMap.size( ); nodesSeen++ ) { do // Find an unvisited vertex { if( pq.empty( ) ) return; vrec = pq.top( ); pq.pop( ); } while( vrec.dest->scratch != 0 ); Vertex *v = vrec.dest; v->scratch = 1; for( int i = 0; i < v->adj.size( ); i++ ) { Edge e = v->adj[ i ]; Vertex *w = e.dest; double cvw = e.cost; if( cvw < 0 ) throw GraphException( "Graph has negative edges" ); if( w->dist > v->dist + cvw ) { w->dist = v->dist + cvw; w->prev = v; pq.push( Path( w, w->dist ) ); } } } } Der Dijkstra-Algorithmus kann mit unterschiedlichen Datenstrukturen eingesetzt warden, z.B. auch mit Fibonacci-Heaps. Die Laufzeit lässt sich dadurch auf O(V log V ) einschränken. Da Fibonacci-Heaps einen gewissen Betrag an „Overhead“ ergeben, ist der erreichte Vorteil zweifellhaft. Nachteile. Der Algorithmus von Dijkstra hat zwei Nachteile: Es wurden nur die kürzesten Verbindungen von einem ausgezeichneten Startknoten zu einem anderen Knoten bestimmt. Die Gewichte aller Kanten müssen positiv sein 79 vgl. http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr22859, pr55_1 111 Spezielle Algorithmen (SAl) 2.5.4 Berechnung der kürzesten Pfadlängen in gewichteten Graphen mit negativen Kosten Falls ein Graph Kanten mit negativen Kosten enthält, arbeitet der DijkstraAlgorithmus nicht korrekt. Das Problem ist, falls ein Knoten u als bekannt dekariert ist, die Möglichkeit besteht, dass es einen Weg zurück nach u von einem Knoten v mit negativem Resultat gibt. Bsp.: Der folgende Graph besitzt einen negativen Zyklus: 2 k2 k1 4 1 3 -10 1 k4 k3 k5 2 2 6 6 k7 k6 1 Bei der Berechnung der Kosten von k5 nach k4 besitzen die direkt angegebenen Kosten den Wert 1. Es existiert aber noch ein kürzerer Pfad k5, k4, k2, k5, k4 mit Kostenwert -5. Offensichtlich gelangt man hier in einen (negativen Kosten-) Zyklus, der sogar mehrfach durchlaufen werden kann. So entstehen dann immer noch weitere kürzere Pfade. Ein kürzester Pfad zwischen k4 und k5 ist nicht definiert. Abb.: Graph mit negativem Zyklus Eine mögliche, aber umständliche Lösung ist: Addition einer Konstanten Δ zu jedem Kantengewicht. Die Konstante wird so groß gewählt, dass keine negativen Kanten nach der Addition vorliegen. Besser ist der folgende Algorithmus (in Pseudocode): void negativ_gewichtet(Vertex s) { Queue q; Vertex v, w; /* 1*/ q = new Queue(); // for each v v.dist = INFINITY; s.dist = 0; /* 2*/ q.enqueue(s); // Einreihen des Startknoten s /* 3*/ while (!q.isEmpty()) { /* 4 */ v = q.dequeue(); /* 5 */ for each w adjazent to v /* 6 */ if (v.dist + cvw < w.dist) { // Update w /* 7 */ w.dist = v.dist + cvw; /* 8 */ w.path = v; /* 9 */ if (w ist nicht in q) /* 10*/ q.enqueue(w); } } } 112 Spezielle Algorithmen (SAl) Komplexität. Jeder Knoten kann etwa |V| mal aus der Warteschlange entnommen werden, die Laufzeit ist somit O( | E | ⋅ | V | ) (Anstieg gegenüber dem Djikstra Algorithmus), falls Adjazenslisten benutzt werden. Falls negative "Kosten-Zyklen" vorliegen, dann gelangt der Algorithmus in eine Endlosschleife. Implementierung 80 void Graph::negative( const string & startName ) { vmap::iterator itr = vertexMap.find( startName ); if( itr == vertexMap.end( ) ) throw GraphException( startName + " is not a vertex in this graph" ); clearAll( ); Vertex *start = (*itr).second; list<Vertex *> q; q.push_back( start ); start->dist = 0; start->scratch++; while( !q.empty( ) ) { Vertex *v = q.front( ); q.pop_front( ); if( v->scratch++ > 2 * vertexMap.size( ) ) throw GraphException( "Negative cycle detected" ); for( int i = 0; i < v->adj.size( ); i++ ) { Edge e = v->adj[ i ]; Vertex *w = e.dest; double cvw = e.cost; if( w->dist > v->dist + cvw ) { w->dist = v->dist + cvw; w->prev = v; // Enqueue only if not already on the queue if( w->scratch++ % 2 == 0 ) q.push_back( w ); else w->scratch--; // undo the push } } } } 2.5.5 Berechnung der kürzesten Pfadlängen in gewichteten, azyklischen Graphen Falls bekannt ist, dass der Graph azyklisch ist, kann der Dijkstra-Algorithmus verbessert werden: Die Knoten des Graphen werden in topologischer Reihenfolge (partielle Ordnung 81) ausgewählt. Die Auswahl der Knoten in topologischer Folge garantiert: die Distanz dv kann nicht weiter erniedrigt werden. void Graph::acyclic( const string & startName ) { vmap::iterator itr = vertexMap.find( startName ); if( itr == vertexMap.end( ) ) throw GraphException( startName + " is not a vertex in this graph" ); clearAll( ); Vertex *start = (*itr).second; list<Vertex *> q; 80 vgl. http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr22859, pr55_1 81 vgl. 1.3.3 113 Spezielle Algorithmen (SAl) start->dist = 0; // Compute the indegrees for( itr = vertexMap.begin( ); itr != vertexMap.end( ); ++itr ) { Vertex *v = (*itr).second; for( int i = 0; i < v->adj.size( ); i++ ) v->adj[ i ].dest->scratch++; } // Enqueue vertices of indegree zero for( itr = vertexMap.begin( ); itr != vertexMap.end( ); ++itr ) { Vertex *v = (*itr).second; if( v->scratch == 0 ) q.push_back( v ); } int iterations; for( iterations = 0; !q.empty( ); iterations++ ) { Vertex *v = q.front( ); q.pop_front( ); for( int i = 0; i < v->adj.size( ); i++ ) { Edge e = v->adj[ i ]; Vertex *w = e.dest; double cvw = e.cost; if( --w->scratch == 0 ) q.push_back( w ); if( v->dist == INFINITY ) continue; if( w->dist > v->dist + cvw ) { w->dist = v->dist + cvw; w->prev = v; } } } if( iterations != vertexMap.size( ) ) throw GraphException( "Graph has a cycle!" ); } Eie bedeutende Anwendung azyklischer Graphen ist die „critical path analysis“. Benutzt werden solche Graphen für die Zeitplanung bzw. Kapazitätsülanung in Projekten. Man verwendet Aktivitätsgraphen (activity node graph, Kosten sind den Knoten zugeordnet, Kanten zeigen Anordnungsbeziehungen) und ereignisorientierte Graphen. 114 Spezielle Algorithmen (SAl) 2.5.6 All pairs shorted Path Der Algorithmus von Floyd berechnet die kürzesten Verbindungen von allen Knoten zu allen anderen Knoten Zugrundeliegende Idee: Es werden alle direkten Verbindungen zweier Knoten als die "billigste" Veränderung der beiden Knoten verwendet. Die billigste Verbindung ist entweder die direkte Verbindung oder aber zwei Wege über einen Mittelknoten. Die Komplexität des Verfahrens von Floyd beträgt O( | V 3 | ). Implementierung 82: public static final int NO_EDGE = 0; public int [][] floyd(int [][] a, int start) { if (a == null || a.length == 0) return null; // Matrix ist leer int i, j, x, n = a.length; // i = Start, j = Ende, x = Zwischenknoten // Anlegen einer neuen Adjazenzmatrix int [][] c = new int[n][n]; // Kopiere alle Werte aus der Matrix: Am Anfang ist die // direkte Verbindung die einzige und daher auch die // billigste for (i = 0; i < n; i++) for (j = 0; j < n; j++) { c[i][j] = a[i][j]; // direkte Kanten kopieren } // Suche fuer Knoten x nach Wegen ueber x, d.h. i -> x, x -> j for (x = 0; x < n; x++) for (i = 0; i < n; i++) if (c[i][x] != NO_EDGE) // gibt es einen Weg i -> x for (j = 0; j < n; j++) if (c[x][j] != NO_EDGE) if (c[i][j] == NO_EDGE // noch kein Weg i -> j || (c[i][x] + c[x][j] < c[i][j])) // i->x->j billiger { if (i == j) continue; c[i][j] = c[i][x] + c[x][j]; } return c; } Der Test dieses Algorithmus führt zu folgendem Resultat: 82 vgl. http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr52221 pr54_1, pr54020.cpp 115 Spezielle Algorithmen (SAl) 116 Spezielle Algorithmen (SAl) 2.6 Minimale Spannbäume Anwendung. Minimale spannende Bäume sind z.B. für folgende Fragestellung interessant: "Finde die billigste Möglichkeit alle Punkte zu verbinden". Diese Frage stellt sich bspw. für elektrische Schaltungen, Flugrouten und Autostrecken. Problemstellung. Zu einem zusammenhängenden Graphen soll ein Spannbaum (aufspannender Baum) mit minimalem Kantengewicht (minimale Gesamtlänge) bestimmt werden. Der minimale Spannbaum muß nicht eindeutig sein, zu jedem gewichteten Graphen gibt es aber mindestens einen minimalen spannenden Baum. 2.6.1 Der Algorithmus von Prim Das einfachste Verfahren zur Erzeugung eines minimale spannenden Baums stammt von Prim aus dem Jahre 1952. In diesem Verfahren wird zu dem bereits vorhandenen Teilgraph immer die billigste Kante hinzugefügt, die den Teilgraph mit einem bisher noch nicht besuchten Knoten verbindet. Aufgabe. Berechne einen spannenden Baum mit minimalen Kosten (minimum spanning tree). Lösungsbeschreibung. Der folgende Graph 2 k2 k1 4 1 3 10 2 7 k4 k3 5 8 k5 4 6 k7 k6 1 besitzt folgenden minimale Spannbaum: 2 k2 k1 1 2 k4 k3 k5 4 6 k7 k6 1 Abb.: Die Anzahl der Kanten in einem minimal spannenden Baum ist |V| - 1 (Anzahl der Knoten – 1). Der minimal spannende Baum ist 117 Spezielle Algorithmen (SAl) - ein Baum, der keine Zyklen besitzt. - spannend, da er jeden Knoten abdeckt. - ein Minimum. Der Algorithmus von Prim arbeitet stufenweise. Auf jeder Stufe wird ein Knoten ausgewählt. Die Kanten auf seine nachfolgenden Knoten werden dann untersucht. Die Untersuchung folgt nach den Vorschriften des Dijkstra-Algorithmus. Es gibt nur eine Ausnahme hinsichtlich der Ermittlung der Distanz: d w = min(d v , c vw ) Die Ausgangssituation zeigt folgende Tabelle: k k1 k2 k3 k4 k5 k6 k7 bekannt false false false false false false false dv 0 ∞ ∞ ∞ ∞ ∞ ∞ pv null null null null null null null Abb.: Ausgangssituation „k1“ wird ausgewählt, „k2, k3, k4 sind zu k1 benachbart“. Das führt zur folgenden Tabelle: k k1 k2 k3 k4 k5 k6 k7 bekannt true false false false false false false dv 0 2 4 1 ∞ ∞ ∞ pv null k1 k1 k1 null null null Abb.: Die Tabelle im Zustand „k1 ist bekannt“ Der nächste Knoten, der ausgewählt wird ist k4. Jeder Knoten ist zu k4 benachbart. Ausgenommen ist k1, da dieser Knoten „bekannt“ ist. k2 bleibt unverändert, denn die „Kosten“ von k4 nach k2 sind 3, bei k2 ist 2 eingetragen. Der Rest wird, wie die folgende Tabelle zeigt, verändert: k k1 k2 k3 k4 k5 k6 k7 bekannt true false false true false false false dv 0 2 2 1 7 8 4 pv null k1 k4 k1 k4 k4 k4 Abb.: Die Tabelle im Zustand „k4 ist bekannt“ Der nächste Knoten, der ausgewählt wird, ist k2. Das zeigt keine Auswirkungen. Dann wird k3 gewählt. Das bewirkt eine Veränderung der Distanz zu k6. k k1 k2 bekannt true true dv 0 2 pv null k1 118 Spezielle Algorithmen (SAl) k3 k4 k5 k6 k7 true true false false false 2 1 7 5 4 k4 k1 k4 k3 k4 Abb.: Tabelle mit Zustand „k2 ist bekannt“ und (anschließend) mit dem Zustand „k3 ist bekannt“ Es folgt die Wahl des Knoten k7, was die Ausrichtung von k6 und k5 bewirkt: k k1 k2 k3 k4 k5 k6 k7 Bekannt true true true true false false true dv 0 2 2 1 6 1 4 pv null k1 k4 k1 k7 k7 k4 Abb.: Tabelle mit Zustand „k7 ist bekannt“ Jetzt werden noch k6 und dann k5 bestimmt. Die Tabelle nimmt danach folgende Gestalt an: k k1 k2 k3 k4 k5 k6 k7 Bekannt true true true true true true true dv 0 2 2 1 6 1 4 pv null k1 k4 k1 k7 k7 k4 Abb.: Tabelle mit Zustand „k6 ist bekannt“ und (anschließend) „k5 ist bekannt“ Die Tabelle zeigt, daß folgende Kanten den minimal spannenden Baum bilden: (k2,k1),k3,k4)(k4,k1),(k5,k7),(k6,k7),(k7,k4) Der Algorithmus von Prim zeigt weitgehende Übereinstimmung mit dem Algorithmus von Dijkstra 83. Komplexität: Die Laufzeit ist O(|V|2) Implementierung. MinimalSpanningTree.java 84 83 84 vgl. http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr22859 119 Spezielle Algorithmen (SAl) Abb. 2.6.2 Der Algorithmus von Kruskal Beschreibung des Algorithmus. 1. Markiere alle Knoten als nicht besucht. 2. Erstelle eine neue Adjazenzmatrix, in der die tatsächlich verwendeten Kanten eingetragen werden. Zu Beginn sind alle Elemente 0. 3. Bestimme die billigste Kante von einem Knote i zu einem Knoten j, die entweder zwei bisher nicht erreichte Knoten verbindet, einen nicht nicht erreichten mit einem erreichten oder zwei bisher unverknüpfte Teilgraphen verbindet. Falls beide Knoten bereits erreicht wurden, kann diese Kante ignoriert werden, da durch sie ein Zyklus entstehen würde. 4. Markiere i und j als erreicht und setze minimalTree[i][j]= g, wobei g das Gewicht der Kante (i,j) ist. 5. Fahre mit Schritt 3 fort, bis alle Knoten erreicht sind Bsp.: Gegeben ist 2 k2 k1 4 1 3 10 2 7 k4 k3 5 8 k5 4 6 k7 k6 1 Bestimme den minimale spannenden Baum nach dem Algorithmus von Kruskal: 120 Spezielle Algorithmen (SAl) 1. Schritt k2 k1 1 k4 k3 k5 k7 k6 2. Schritt k2 k1 1 k4 k3 k5 k7 k6 1 3. Schritt 2 k2 k1 1 k4 k3 k5 k7 k6 1 4. Schritt 2 k2 k1 1 2 k4 k3 k5 k7 k6 1 121 Spezielle Algorithmen (SAl) 5. Schritt 2 k2 k1 1 2 k4 k3 k5 4 k7 k6 1 6. Schritt 2 k2 k1 1 2 k4 k3 k5 4 6 k7 k6 1 Abb. Lösungsschritte zum Demonstrationsbeispiel Prinzip. Auswahl der Kanten in der Reihenfolge kleinster Gewichte mit Aufnahme einer Kante, falls sie nicht einen Zyklus verursacht. Implementierung. MinimalSpanningTree.java 85 85 vgl. http://fbim.fh-regensburg.de/~saj39122/ad/skript/programme/pr53331 122 Spezielle Algorithmen (SAl) 2.7 Netzwerkflüsse 2.7.1 Maximale Flüsse 27.1.1 Netzwerk und maximaler Fluß Ein Netzwerk N = (V , E , s, t , c ) ist - - ein gerichteter Graph G = (V , E ) ohne Mehrfachkanten mit zwei ausgezeichneten Knoten (Quelle und Senke) o Quelle s aus V . Die Quelle ist ein Knoten mit Eingangsgrad 0. o Senke t aus V . Die Senke ist ein Knoten mit Ausgangsgrad 0. Mit einer Kapazitätsfunktion c , die jeder Kante e aus E eine nicht-negative reelwertige Kapazität c(e) zuweist Ein Restnetzwerk (Residualnetzwerk) von N ist ein Netzwerk N f = (V , E , s, t , c f ) in dem die Kapazitäten um den Fluß durch diese Kanten vermindert werden. Jedes Restnetzwerk ist ein Teilgraph G f des Netzwerks. c=1 c=1 c=2 c=3 s t c=4 c=0 c=3 c=2 Ein s-t-Fluß ist eine Funktion f , die jeder Kante e im Netzwerk einen nichtnegativen, reellen Flusswert f (e) zuweist, der einer Reihe von Bedingungen genügt. c=1 f=0 c=1 f=0 c=2 f=0 c=3 f=1 s t c=4 c=0 f=1 c=3 f=2 c=2 f=1 0 ≤ f (e) ≤ c(e), ∀e ∈ E f (e) − ∑ f (e) - Kapazitätsbeschränkung - Flusserhaltung ∑ e inc v e aus v Wert vom Fluß im Netzwerk: val ( f ) = ∑ f (s, y) − ∑ f ( y, s) . Der Wert eines Flusses y∈s + y∈s − ist die Summe der lokalen Flüsse aus der Quelle (= Summe aller lokalen Flüsse in die Senke) 123 Spezielle Algorithmen (SAl) Der Fluß mit maximalem Wert heißt maximaler Fluß: Gegeben ist ein gerichteter, gewichteter Graph. Ein Fluß ist in diesem Graphen eine Funktion f : E → ℜ mit 0 ≤ f (i, j ) ≤ c(i, j ) 2. Für alle i ∈ V {s, t} gilt 1. ∑ f (a, i) = ∑ f (i, b) a∈V ( i ) b∈N ( i ) V (i ) : alle Vorgänger von i N (i ) : alle Nachfolger von i Gesucht ist der maximale Gesamtfluß: F = ∑ f ( s, b) = ∑ f ( a, t ) Der maximale Fluß im Netzwerk hat genau einen Wert: den minimalen Schnitt. Die folgenden Aussagen sind äquivalent: f ist der maximale Fluß in N bzw. G - das Restnetzwerk N f bzw. G f enthält keinen Verbesserungspfad - - f = c (S , T ) gilt für irgendeinen Schnitt (S , T ) 124 Spezielle Algorithmen (SAl) 2.7.1.2 Optimieren und Finden augmentierender Pfade (Erweiterter Weg) Ein augmentierender Pfad bzgl. eines Flusses f ist ein Pfad von der Quelle zur Senke ohne Berücksichtigung der Kantenrichtung wobei für jede Kante (v,w) gilt: entweder Vorwärtskante: (v, w) ∈ E und f (v, w) < c(v, w) oder Rückwärtskante: ( w, v ) ∈ E und f ( w, v) > 0 Längs eines so erweiternden (augmentierenden) Wegs kann der Fluß vergrößert werden, indem man durch Vorwärtskanten zusätzliche Einheiten fließen lässt oder den Fluß durch Rückwärtskanten verringert. Beides ist nach der Definition des erweiternden Wegs möglich. Bsp. für Flussoptimierung mit einem augmentierenden Pfad 4/3 5/5 3/2 3/3 u 7/3 4/2 6/5 1/1 7/7 4/3 6/3 5/5 w Auf dem augmentierenden Pfad wäre bis zum Knoten u noch Platz für 2 Einheiten (Restkapazität). Wird der „Hahn“ von w nach u etwas zugedreht, fließen insgesamt 2 Einheiten mehr 4/3 5/5 3/2 3/3 u 7/5 4/4 6/5 1/1 7/7 4/1 6/5 5/5 w Optimierung am augmentierenden Pfad ci −1 / f i −1 ci / f i ci +1 / f i +1 nur Vorwärtskanten: Erhöhe Fluß um minimale Restkapazität (slack) min(c k − f k ) über alle Pfadkanten k. 125 Spezielle Algorithmen (SAl) Wenn Rückwärtskanten vorkommen, dann: Vorher cd / f d ca / f a cb / f b Nachher cd / f d ca + x / f a cb − x / f b cc / f c ce / f e cc + x / f c cb / f b Hier gilt: x = min (c v − f v ) über alle Vorwärtskanten v v oder x = min c r über alle Rückwärtskanten r (das kleinere) r Finden eines augmentierenden Pfads Markiere Quelle s wiederhole wenn (v, w) existiert mit v markiert und f (v, w) < c(v, w) markiere w wenn (w, v ) existiert mit v markiert und f ( w, v) > 0 markiere w solange in der Schleife neue Knoten markiert werden. Der Algorithmus markiert genau die Knoten, die von der Quelle aus mit augmentierenden Pfaden erreichbar sind. Ist am Ende die Senke markiert, dann ist ein augmentierender Pfad durch das Netzwerk gefunden. Längs eines so erweiterten Wegs kann der Fluß vergrößert werden, indem man durch Vorwärtskanten zusätzliche Einheiten fließen lässt oder man den Fluß in den Rückwärtskanten verringert. Beides ist nach der Definition des 126 Spezielle Algorithmen (SAl) 2.7.1.2 Algorithmus für optimalen Fluss Ford-Fulkerson-Algorithmus 86 Gegeben: ein Netzwerk mit den Kapazitäten c : E → ℜ ≥0 und 2 Knoten s, t 1. Initialisiere f mit 0 2. solange ein augmentierender Weg P von s nach t im Restnetzwerk G f existiert 2a. Konstruktion bzw. Aktualisierung des Restnetzes G f 2b. Finden eines augmentierenden Wegs 3. für jede Kante e auf P erhöhe den Fluß um c f (P ) Bsp. zum Ford-Fulkerson-Algorithmus: (a) Gegeben 12 16 20 s t 10 4 7 13 9 4 14 (b) augmentierender Weg mit Kapazität 7 12/7 16/7 20/7 s t 10/7 4 7/7 13 9/7 4 14/7 (c) Restnetz nach Schritt 1, augmentierender Weg mit Kapazität 4 12 9 7 7 13 s t 3 13 11 7 0 9 4 7 7 86 http://de.wikipedia.org/wiki/Algorithmus_von_Ford_und_Fulkerson 127 Spezielle Algorithmen (SAl) (d) Restnetz nach Schritt 2, augmentierender Weg mit Kapazität 5 8 4 5 7 11 13 s 4 3 t 11 13 7 5 4 0 3 11 (e) Restnetz nach Schritt 3, augmentierender Weg mit Kapazität 4 3 9 12 16 8 s 4 3 t 11 13 7 5 3 4 11 (f) Restnetz nach Schritt 4, augmentierender Weg mit Kapazität 3 3 9 16 16 4 s t 3 9 11 4 7 9 4 3 11 (g) Restnetz nach Schritt 5, kein augmentierender Weg 12 12 19 16 1 s t 6 6 7 8 7 9 4 3 11 128 Spezielle Algorithmen (SAl) (h) Maximaler Fluß und minimaler Schnitt, beide mit Wert 23 12/12 16/16 20/19 s t 10/4 4/0 13/7 7/7 9/0 4/4 14/11 Analyse der Laufzeit Schritt 1: O( E ) Schritt 2a: O( E ) je Durchlauf Schritt 3: O( E ) je Durchlauf, wenn z.B. Tiefen- oder Breitensuche benutzt wird Wieviel Durchläufe gibt es? Falls die Kapazitäten ganze Zahlen sind, erhöht sich jeder Durchlauf den Fluß um mindestens eins, also gibt es bis zu f ( Laufzeit ist dann insgesamt O f ( * ) * Durchläufe, wobei f * der maximale Fluß ist. Die ⋅E . ) Die Laufzeit O f * ⋅ E ist nicht befriedigend, da f * evtl. exponentiell in der Größe der Eingabe ist. Das folgende Bsp. zeigt die Möglichkeit, dass tatsächlich einmal f * Durchläufe ausgeführt werden: Bsp.: 1000 1000 999 1000 1 1 1 1000 1 1000 1000 (a) Netz 999 (b) 1. Schritt Bei ungeschickter Wahl des 1. erweiternden Wegs, nämlich s, 1, 2, t ist in der 1. Iteration (1. Schritt) lediglich eine Erhöhung um eine Einheit möglich. Saturiert wird nur die Kante (1,2). QAls nächstes kann s, 2, 1, t gewählt werden, wobei wieder nur eine Erhöhung um eine Einheit zu erreichen ist. 129 Spezielle Algorithmen (SAl) 999 999 1 1 1 1 1 999 999 (c) 2. Schritt Abb.: Bestimmen des augmentierenden Weges mit Tiefensuche Hier werden die augmentierenden Pfade mit Tiefensuche gefunden. In jedem Schritt wird der Fluß um 1 erhöht und in den (b) und (c) dargestellten Schritte werden f * = 2000 mal wiederholt bis der maximale Fluß erreicht ist Wenn die augmentierenden Wege mit der Breitensuche bestimmt werden, dann werden 2 Durchläufe benötigt. 1000 0 0 1000 1000 1000 1 1 1000 1000 1000 1000 1000 1000 0 0 1000 1000 Abb.: Bestimmen augmentierender Wege mit Breitensuche Edmonds-Karp-Algorithmus Es ist ersichtlich aus den vorstehenden Beispielen, dass die Breitensuche Vorteile hat gegenüber der Tiefensuche. Genutzt wird die Breitensuche durch den Algorithmus von Edmonds und Karp 87 1. Initialisiere 87 f mit 0 http://de.wikipedia.org/wiki/Algorithmus_von_Edmonds_und_Karp 130 Spezielle Algorithmen (SAl) 2. solange ein augmentierender Weg P von s nach t im Restnetzwerk G f existiert 2a. Konstruktion bzw. Aktualisierung des Restnetzes G f 2b. Finden eines augmentierenden Wegs mit Breitensuche 3. für jede Kante e auf P erhöhe den Fluß um c f (P ) Bei diesem Algorithmus wird der kürzesze augmentierende Weg bzgl. der Kantenzahl ausgewählt. Falls δ f (u, v) ) der Abstand zwischen u und v im Restnetz ist, also die Anzahl der Kanten auf dem kürzesten Weg von u nach v , gilt: Beim Edmonds-Karp-Algorithmus gilt für alle Knoten v (ausgenommen s , t ): Während des Ablaufs des Algorithmus ist δ f (u , v) ) monoton wachsend 131 Spezielle Algorithmen (SAl) 2.7.1.4 Schnitte und das Max-Flow-Min-Cut Problem Eine Unterteilung eines Netzwerks in eine Knotenmenge A und eine Knotenmenge B heißt Schnitt. A B w Die Kapazität c( A, B) eines Schnitts A / B ist die Summe der Kapazitäten aller Kanten von A nach B. Der Wert eines Flusses (der Gesamtfluß) ist nie größer als die Kapazität eines beliebigen Schnitts (irgendwie muß es ja durch). D.h. w( f ) ≤ min c( A, B) Schnitt AB Max-Flow-Min-Cut-Theorem w( f ) , der Wert von f ist maximal ⇔ es gibt keinen augmentierenden Pfad von Quelle zur Senke Dann enden alle von s ausgehenden erweiternden Wege (- genauer gesagt deren Anfangsstücke -)entweder bei einer saturierten Vorwärtskante oder bei einer Rüpckwärtskante mit Fluß 0. Durch diese Kanten wird ein Cut impliziert, dessen Kapazität gleich dem momentanen Fluß ist. ⇔ w( f ) ≤ min c( A, B) Schnitt AB Beweis „ ⇒ “: durch Angeben der Optimierungsregel Beweis „ ⇐ “: Definiere Schnitt A / B , so dass A = alle Knoten, die von der Quelle aus mit augmentierenden Pfad erreichbar sind. Für alle v ∈ A , w ∈ B gilt f (v, w) = c(v, w) , da sonst w auch mit augmentierendem Pad erreichbar. Also ist w( f ) = c( A, B ) 1 2 3 max flow 123 min cut 132 Spezielle Algorithmen (SAl) Bsp.: Flussgraph: 12/12 a b 16/11 20/15 s t 10 4/1 13/7 7/7 9/4 c 4/4 d 14/11 Schnitt: - cut ({s, a, c}, {b, d , t}) - Netto-Fluss. f (a, b ) + f (c, d ) + f (b, c ) = 19 . Netto-Fluss ist in allen Schnitten gleich Eine interessante Eigenschaft des Netzwerks mit gannzahliger Kapazitäz ist, dass auch die maximalen Flüsse in solchen Netzwerken immer ganzzahlig sind, da der vorstehende Algorithmus nur ganzzahlige Erhöhungen durchführt. Das Integral-Flow-Theorem: Wenn in einem Netzwerk alle Kapazitäten ganzzahlige Werte sind, dann ist der maximale Fluß auch ganzzahlig. Beweis: Verwende den vorstehenden Algorithmus. Am Anfang ist der Fluß 0. In jedem Schritt wird er um die Restkapazität eines augmentierenden Pfads erhöht Da alter Fluß ganzzahlig und Kapazität ganzzahlig, ist auch die Restkapazität ganzahlig und neuer Fluß auch 133 Spezielle Algorithmen (SAl) 2.7.2 Konsteminimale Flüsse Durch ein Netzwerk wird häufig nicht ein maximaler Fluß gesendet, sondern ein Fluß mit vorgegebenem Wert, der bzgl. eines Kostenkriteriums minimale Kosten verursacht. Hier bestimmt man zunächst den maximalen Fluß ohne Rücksicht auf die Kosten und steuert anschließend die einfachen Flüsse so um, bis das Kostenminmum erreicht ist. Bsp.: Gegeben ist das Verkehrsnetz 2 7(6) 4(3) 4(4) 1 4 2(2) 3(8) 3 Abb.: Jede Strecke des Netzes (Kante des Graphen) hat eine begrenzte Kapazität (bezeichnet durch die 1. Zahl an den Kanten). Die Zahl in den Klammern an den Kanten gibt die Kosten des Transports (je Einheit) an. Gesucht ist der maximale Fluß durch das Netz vom Knoten 1 zum Knoten 4, wobei die Kosten möglichst niedrig sein sollen. 1. Berechnung des maximalen Flusses ohne Berücksichtigung der Kosten 2 7 7(6) 1 4(4) 4(3) 1 4 1 2(2) 2 3(8) 3 3 Abb.: Der berechnete maximale Fluß besteht aus den Einzelflüssen: 7 (Einheiten) von 1 nach 2 3 (Einheiten) von 2 nach 3 4 (Einheiten) von 2 nach 4 3 (Einheiten) von 3 nach 4 Die Kosten betragen 91 [Kosteneinheiten]. Die Lösung ist nicht kostenminimal. 2. Kostenoptimale Lösung 134 Spezielle Algorithmen (SAl) Zwischen Knoten 1 und 3 bestehen 2 Alternativwege (1 - 2- 3 - 2) und (1 - 3). (1 - 3) wird nicht benutzt. Dort betragen die Kosten nur 2 [Kosteneinheiten]. Eine Umverteilung von 2 [Mengeneinheiten] führt hier zur Verbesserung. Man erhält die Optimallösung mit 77 [Kosteneinheiten]. Übung 27_2: Maximaler Fluß, Transportproblem 135 Spezielle Algorithmen (SAl) 2.8 Matching 2.8.1 Ausgangspunkt, Motivierendes Beispiel, Definitionen, maximales Matching Ausgangspunkt Zuordnungsprobleme (verschiedene Dinge einander zuordnen) - Männer / Frauen im Tanzkurs Arbeiten / Arbeitskräfte Koffer / Schließfächer Gegeben: Ein ungerichteter G = (V , E ) . Die Kanten symbolisieren hier mögliche Zuordnungen. Gesucht: Eine Zuordnung M (Matching), d.h. eine unabhängige Kantenmenge M . Unabhängig bedeutet, es gilt: (i, j ), (i ' , j ' ) ∈ M ⇒ i ≠ i ' , j ≠ j ' , i ≠ j ' , j ≠ i ' Keine der zwei Kanten in M haben die gleiche Zuordnung. M ist die Anzahl der Kanten in M . Motivierendes Beispiel Gegeben Tanzkurs: Jeder Teilnehmer (Knoten) weiß, mit wen ergerne tanzt. (Kante). Gesucht: Mögliche Paarungen (vgl. rot gefärbte Kanten). Eva Heino Martin Klaus Maria Pia Uwe Lilo 3 Paare sind gefunden, aber nicht jeder Knoten hat einen Partner. Es sind keine weiteren Paarungen möglich. Frage: Wie kriegt man eine optimale Paarbildung zustande? Es sind ja noch ein Herr und eine Dame übrig geblieben! Definitionen - - Zwei Kanten (u, v ) und ( x, y ) (x,y) heißen unabhängig, wenn u , v, x, y vier verschiedene Knoten sind. Wenn u = x oder u = y oder v = x oder v = y , dann heißen die Kanten benachbart (oder verbunden oder adjazent) Die Kantenmenge M heißt unabhängig, wenn alle ihre Elemente paarweise unabhägig sind. Solche Untermengen heißen auch Matching (Zuordnung). 136 Spezielle Algorithmen (SAl) - Ein Knoten heißt frei bzgl. eines Matchings, wenn er keine Kante des Matchings hat, sonst heißt er gematcht. - Ein Matching M heißt perfekt, wenn es alle Knoten des Graphen überdeckt. - Ein Matching M heißt maximal (nicht erweiterbar), wenn es um keine Kante erweitert werden kann - Ein Matching M heißt Maximum, wenn es kein Matching mit mehr Kanten gibt, d.h. |M| ist maximale Größe. 137 Spezielle Algorithmen (SAl) - Ein Matching M bei dem nur ein Knoten frei bleibt, heißt fast perfekt. gematcht frei Bsp.: Ein gerader Kreis hat 2 perfekte Matchings Ein gieriger Algorithmus - Gegeben: Graph G - Gesucht Matching M Solange eine unmarkierte Kante (u,v) in G existiert Markiere (u,v) Markiere alle benachbarten Kanten Übertnehme (u,v) nach M 138 Spezielle Algorithmen (SAl) - Algorithmus liefert - ein maximales Matching - aber kein (fast) perfektes Matching Beobachtung Im folgenden Graphen sind Eva Heino Martin Klaus Maria Pia Uwe Lilo Knoten in zwei Gruppen aufteilbar, Kanten jeweils zwischen diesen Gruppen Klaus Heino Uwe Martin Maria Lilo Pia Eva 139 Spezielle Algorithmen (SAl) 2.8.2 Bipartiter Graph Am häufigsten werden Matching-Probleme in bipartiten Graphen betrachtet. Ungerichteter Graph G = ( X ∪ Y , E ) mit X ∩ Y = ∅ und nur Kanten (xi , y j ) ∈ E mit xi ∈ X , y j ∈ Y oder umgekehrt. Gegeben ist der folgende bipartite Graph G = ( X ∪ Y , E ) mit X = {x1 , x 2 ,..., x 6 } und Y = {y1 , y 2 ,..., y 6 } x1 x2 x3 x4 y1 y2 y3 y4 x5 y5 x6 y6 Zuordnung nicht maximal: Mehr Kanten bspw, wenn (x1 , y1 ) und (x3 , y 2 ) (x3,y2) verwendet werden, statt (x1 , y 2 ) x1 x2 x3 x4 y1 y2 y3 y4 x5 y5 x6 y6 Formulierung als Flußproblem Vorgehen - Gegeben Graph G = ( X ∪ Y , E ) - Hinzufügen von zwei weiteren Knoten Quelle s und Senke t - Jede Kante (xi , y j ) von G = ( X ∪ Y , E ) wird in einem Graphen G ' = ( X ∪ Y ∪ {s, t}, E ') zu einem Pfeil von xi nach y j - In E ' existiert ein Pfeil von s zu jedem Knoten xi ∈ X und von jedem y j ∈ Y existiert ein Pfeil zu t - Es ist also E ' = E ∪ {(s, x ) : x ∈ X } ∪ {( y, t ) : y ∈ Y } - Jede Kante erhält die Kapazität 1 Maximale Zuordnung in G entspricht einem maximalen Fluß in G’ 140 Spezielle Algorithmen (SAl) Bsp.: s x1 x2 x3 x4 y1 y2 y3 y4 x5 y5 x6 y6 t Ablösung von (x1,y2) durch (x1,y1) und (x3,y2) in G entspricht Erweiterungspfad e=[s,x3,y2,x1,y1,t] in G ' Jeder Fluß f in G ' entspricht einem Matching M = {( xi , y i ) | f ( xi , y i ) = 1} in G . Der maximale Fluß ordnet genau den Kanten des „Maximum Matchings“ (und denen von Quelle und Senke) 1.0 zu, sonst 0.0. Erweiternder Weg Erweiterungspfad in G’ - Vorwärtspfeil e mit Fluss f = 0 - Rückwärtspfeil e’ mit Fluss f(e’)=1 - Vorwärts- und Rückwärtspfeile wechseln sich ab - Pfad beginnt und endet mit einem Vorwärtspfeil Entsprechnug in G - Pfad, dessen Kanten abwechselnd zur Zuordnung gehören bzw. nicht zur Zuordnung gehören wird als alternierender Pfad bezeichnet, z.B. x2, y4,x5,y6 Vergrößerung der Zuordnung - Vergrößerung alternierender Pfad: Alternierender Pfad mit freien Endknoten, z.B.: y3,x2,y4,x4,y5,x6 - Freie Kante wird zu gebundener und umgekehrt. Für ein gegebenes Matching M nennt man jede für die Zuordnung verwendete Kante e ∈ M gebunden, jede Kante e'∈ E − M ist frei. Jeder Knoten, der eine gebundene Kante inzidiert, ist ein gebundener Knoten, jeder andere Knoten ist frei. Ein Weg in G dessen Kanten abwechselnd gebunden und frei sind, heißt alternierender Weg. Die Länge eines alternierenden wegs, ist die Anzahl der Kanten auf diesem Weg. Natürlich kann nicht jeder alternierende Weg zur Verrgrößerung der Zuordnung benutzt werden. Das geht nur dann, wenn die beiden Knoten an den 141 Spezielle Algorithmen (SAl) Enden eines Wegs frei sind. Ein alternierender Weg mit zwei freien Knoten an beiden Enden heißt deshalb vergrößernd. Bsp.: Erweiternder Weg M ' ist das aus der Vergrößerung entstehende Matching. Falls es ein aus der Vergrößerung entstehendes Matching gibt, ist der Pfad M - M ' alternierend: M M M’ ⇒ M M’ M M’ Beachte: Ein Zyklus kann kein vergrößernd alternierender Pfad sein Es ist einleuchtend, dass sich ein Matching entlang eines solchen erweiternden Wegs um eine Kante erweitern lässt, indem man jede Matchingkante zu einer Nichtmatchingkante macht und umgekehrt. Maximum Matching Dabei gilt der folgende Satz: M ist Maximum Matching ⇔ Es gibt keinen erweiternden Weg bzgl. M . Beweis: ⇒ : trivial! ⇐: Es gibt keinen erweiternden Weg bzgl. M Annahme: Es gibt M ' mit M ' > M Betrachte nun M und M ' in G und entferne alle Kanten aus dem Rest von G, die abwechselnd in M ' und M liegen, wobei diese Folge mit einer Kante aus M ' beginnt und aufhört. Die Endpunkte dieser Folge von Kanten sind frei bzgl. M . Somit ist diese Folge von Kanten ein erweiternder Weg bzgl. M . Dies ist ein Widerspruch zur Voraussetzung. Falls M < M ' , M ist nicht Maximum. 142 Spezielle Algorithmen (SAl) Bsp.: v v w v Kein Maximum Matching da vergrößernd alternierend Pfad zwischen nicht gematchten Knoten v, w v v w w Kein maximum Matching da vergrößert alternierender Pfad zwischen nicht gematchten Knoten Daraus folgt ein Ansatz zur Lösung des Problems, ein Maximum Matching zu bestimmen Benutze irgend einfachen Algorithmus um ein maximales Matching M zu finden, solange ein vergrößernd M-alternierender Pfad vorhanden ist Vertausche die M-Zugehörigkeit der Kanten auf diesem Pfad - Der Algorithmus 88 fügt in jedem Schritt eine Kante zu M hinzu. Da es nur endlich viele Kanten gibt, terminiert er. Wenn er terminiert, hat er ein Maximum Matching gefunden Noch offen: Wie findet man M-alternierende Pfade? (Erweiternder) alternierender Baum Bei bipartiten Graphen kann man für ein gegebenes Matching einen vergrößernden Weg finden, indem man mit der Suche bei einem freien Knoten beginnt und entlang eines bzg. M alternierenden Wegs fortschreitet. Sobald man bei einem freien Knoten angekommen ist, ist ein vergrößernder Weg gefunden. Zu einem freien Startknoten kann man einen entsprechenden Baum mit Hilfe der Breitensuche ermitteln. 88 abgeleitet aus dem Satz von Berge 143 Spezielle Algorithmen (SAl) x1 x2 x3 x4 y1 y2 y3 y4 x5 x6 y5 y3 y6 freier Knoten freie Kante gebundene Kante y4 freie Kante x5 x4 gebundene Kante y5 y6 freie Kante x6 Abb.: Breitensuchbaum für die in der vorstehenden Abbildung gezeigte Zuordnung und den Startknoten y3 Maximal gewichtete Zuordnung (maximum weigtht matching) Für einen ungerichteten, bewerteten Graph G = (V , E ) mit Kantenbewertung w : E → ℜ ist das Gewicht einer Zuordnung M die Summe der Gewichte der Kanten von M. Von Interesse ist eine maximale gewichtete Zuordnung (maximum weight matching). Falls bspw. in einer Firma mit k Mitarbeitern m1,…,mk die k Tätigkeiten t1,…,tk auszuführen sind und eine Maßzahl w(mi,tj) für die Eignung eines Mitarbeiters mi für die Tätigkeit tj bekannt ist, sofern Mitarbeiter mi die Tätigkeit tj überhaupt ausführen kann, so kann eine maximale gewichtete Zuordnung von Mitarbeitern und Tätigkeit erwünscht sein. 144 Spezielle Algorithmen (SAl) m1 m2 m3 m4 m5 m6 6 1 2 2 t1 2 t2 1 5 t3 6 6 t4 5 6 5 t5 7 t6 2.8.3 Maximale Zuordnung im allgemeinen Fall In allgemeinen Graphen kann man mit einer einfachen Breitensuche vergrößernde Wege nicht unbedingt finden finden. Bsp.: Gegeben ist 4 9 12 3 5 1 8 2 10 11 6 7 mit der Zuordnung M = {(6,7 ), (8,10 )} Finde den vergrößernden Weg vom freien Knoten 2 aus mit Hilfe eines alternierenden Baums. 2 freier Knoten freie Kante 6 gebundene Kante 7 freie Kante 8 10 gebundene Kante ? Abb.: Alternierender Baum (auf einen Teilgraph beschränkt) 145 Spezielle Algorithmen (SAl) Die Breitensuche sorgt dafür, dass Knoten 10 besucht wird, bevor die Nachfolger von Knoten 8 im alternierenden Baum in Betracht gezogen werden. Wenn jeder Knoten, wie bei der Breitensuche üblich, nur einmal besucht werden darf, so verhindert das Finden des alternierenden Wegs 2, 6, 7, 10 der nicht mit einem freien Knoten endet, dass der alternierende Weg 2, 6, 7, 8, 10, 11 gefunden wird (, obwohl der mit einem freien Knoten enden würde. Die reine Breitensuche ist also hier nicht in der Lage, vergrößernde Wege auch wirklich zu finden. Ursache: Ein und derselbe Knoten kann auf mehreren, alternierenden Wegen in gerader und ungerader Entfernung vom Startknoten auftreten. Knoten 10 tritt auf dem alternierenden Weg 2, 6, 7, 10 in ungerader Entfernung vom Startknoten 2 auf, währen er bei dem alternierenden Weg 2, 6, 7, 8, 10 in gerader Entfernung vom Startknoten auftritt. Man kann aber nicht in eine Abänderung der reinen Breitensuche das 2malige Besuchen eines jeden Knoten erlauben, nämlich je einmal für die gerade und ungerade Entfernung vom Startknoten, dann können auch Knotenfolgen gefunden werden, die keinen vergrößernden Weg beschreiben, z.B. das Matching M = {(6,7 ), (8,10 )} kann für Startknoten 2 die Knotenfolge 2, 6, 7, 8, 10, 7, 6, 5 liefern. Überlegung: Das Finden eines vergrößernden Wegs von einem freien Knoten v aus ist nur dann schwierig, wenn es einen alternierednen Weg p von v zu einem Knoten v’ in jeder Entfernung von v gibt und wenn eine Kante v’ mit einer anderen v’’ verbindet, der auf dem Weg ebenfalls in gerader Entfernung von v liegt. v’ v v’’ j i Der Teil des Wegs p von v’’ nach v’ heißt zusammen mit der Kante (v’,v’’) Blüte. Eine Blüte ist also ein Zyklus ungerader Länge. Der Teil des Wegs p von v nach v’’ heißt Stiel der Blüte. In der vorstehenden Abb. gibt es sowohl einen alternierenden Weg von v nach i als auch einen von v nach j. Den Weg von v nach i erhält man, wenn man im Zaklus ungerader Länge im Uhrzeigersinn fortschreitet. Den Weg von v nach j erhält man durch Besuchen einiger Knoten des Zyklus entgegen dem Uhrzeigersinn. Diese beiden Wege kann man finden, wenn man die Blüte auf einen Knoten schrumpfen lässt, also den Zyklus ungerader Länge in einen Knoten kollabiert. Jede Kante, die vor dem Schrumpfen mit einem Knoten des Zyklus inzident war, ist nach dem Schrumpfen mit dem die Blüte repräsentierten Knoten inzident. v i j Abb.: Effekt des Schrumpfens der Blüte zur vorstehenden Abb. 146 Spezielle Algorithmen (SAl) Wenn ein Graph G’ aus einem Graph G durch Schrumpfen einer Blüte entsteht, so gibt es in G’ genau dann einen vergrößernden Weg, wenn es einen solchen in G gibt. Blüte ⇒ frei Blüte in G = Knoten in G’ außen außen frei G Blüte (blossom): Kreis ungerader Länge Kante von „außen“ nach „außen ergibt Blüte. G’ Es gilt folgender Satz: G’ hat erweiternden Weg ⇔ G hat erweiternden Weg 147 Spezielle Algorithmen (SAl) 3. Problemlösung durch Suchen 3.1 Lösen von Problemen Wenn der denkende Mensch ein Problem löst, so operiert er nicht in der realen Welt, sondern in seiner Vorstellung. Er macht sich ein Bild von der Aufgabe, durchdenkt sie und setzt die Lösung schließlich in die Praxis um. Dem Rechner ist es meistens nicht möglich, die Resultate in die Tat umzusetzen. Er ist voll und ganz auf eine Scheinwelt festgelegt. Daher ist es von großer Bedeutung auf welche Weise im Rechner ein Abbild der Welt dargestellt ist. Die Frage nach Repräsentation des Problems steht am Anfang eines jeden Rechnereinsatzes. Problemlösung wird in der „Künstlichen Intelligenz“ (KI) oft als Alternative zwischen einem Suchprozeß und logischen Schließen aufgestellt. Welche Sichtweise angemessen ist, hängt von der individuellen Problemstellung ab. Problemlösung als Suche Es wird ein Raum konstruiert mit einer Anzahl von Zuständen, in welchem sich die Aufgabenstellung befinden kann (z.B. Konfiguration auf einem Schachbrett). Die Zustände sind untereinander verbunden, eine jede Verbindung ist als eine Überführung eines Zustands in einen anderen zu betrachten (z.B. ein Spielzug beim Schach). Das Problem wird gelöst, indem die Zustände durchsucht werden nach solchen, die einen Zielzustand darstellen (z.B. ein Matt beim Schach). Problemlösung als logisches Schließen Es wird ein logisches System aufgebaut aus diversen Axiomen, durch die die Problemstellung charakterisiert wird. Regeln stehen zur Verfügung, nach denen sich aus den Axiomen weitere Ziele ableiten lassen. Das Ziel besteht darin, durch fortgesetzte Anwendung dieser Regeln soviel „Wissen“ anzuhäufen, bis die Lösung des Problems gefunden ist. Die Basis für soche Systeme bilden (z.B.) die Prädikatenlogik und die Produktionssysteme. 3.1.1 Repräsentation von Zustandsräumen Ein Zustandsraum wird durch 3 Faktoren bestimmt: = = = die Menge der Ausgangspositionen die Menge der Startknoten die Menge der Operatoren zur Überführung eines Zustands in einen anderen die Menge der Operatoren zur Erweiterung eines Knotens die Menge der Zielpositionen die Menge der Endknoten Eine Repräsentation des Zustandsraums besteht aus 2 Komponenten: - aus einzelnen Zuständen aus den Operatoren, die den Übergtang von einem Zustand in einen anderen vollziehen 148 Spezielle Algorithmen (SAl) Bsp.: 8-Puzzle Gegeben ist ein quadratisches Feld und 8 Spielsteine mit den Ziffern 1 bis 8. Ein Feld bleibt leer. Jeder Stein, der mit einer Kante an das leere Feld angrenzt, kann dann durch Anwendung eines der Operatoren UP, DOWN, LEFT oder RIGHT auf den Platz des leeren Felds geschoben werden. Ziel ist es, durch geeignete Verschiebungen die Optimallösung als Zustand zu erzeugen 2 1 4 7 6 2 8 5 3 7 1 6 1 4 8 8 5 3 7 2 3 4 6 5 Mögliche AusgangsSituation Folgezustand nach AnwenOptimallösung dung des Operators RIGHT auf Stein 4 Es gibt 9! mögliche Anordnungen der Steine im Spielfeld. Zu gegebener Ausgangssituation ist (angeblich) aber nur die Hälfte aller Zustände erreichbar. Dies bedeutet: Der Zustandsraum beinhaltet 9! Möglichkeiten ( = 181440 Zustände) 2 Ein Zustandsraum-Repräsentation ist gegeben durch das Tripel (S , O, G ) . S : Menge der Anfangszustände, O : Menge der Operatoren, G : Menge der Zielzustände Bei einem Zustandsraum-Graph bestehen die Knotenb aus den möglichen Zuständen und die Kanten aus den Operatoren, die einen Zustand zum Nachfolgezustand führen. Bei manchen Problemen müssen in einem Graphen minimale Wege bestimmt werden, in diesen Fällen haben die Kanten die Weglängen als Bewertung. Bsp.: Travelling-Salesman Problem (TSP) Gegeben ist die Entfernungstabelle A B C D A - B 4 - C 6 7 - D 10 10 5 - Gesucht ist eine Rundreise minimaler Länge 149 Spezielle Algorithmen (SAl) A 4 AB 7 ABC 6 AC 10 ABD 10 AD 7 ACB 5 5 10 ABCD ABDC ACBD 10 6 10 ABCDA ABDCA ACBDA 5 ACD 10 ADB 10 ACDB 7 ADBC 4 ACDBA 5 ADC 7 ADCB 6 ADBCA 4 ADCBA Abb.: Zustangsraum-Graph des TSP Die beste Lösung ist die Strecke A-B-D-C-A. Typischerweise ist der Graph eines Zustandsraums zu groß, um explizit repräsentiert zu werden. Das Suchproblem besteht damit im Wesentlichen darfin, nur so viel des Graphen zu erzeugen, dass der gewünschte Lösungspfad ermittelt werden kann. 3.1.2 Repräsentation von Problemreduktionen Eine Problemreduktionsrepräsentation wird durch 3 Faktoren bestimmt: = = die Menge der Ausgangspositionen die Menge der Startknoten die Menge der Regeln, nach denen ein Problem in Teilprobleme zerlegt werden kann die Menge der trivialen Probleme, die ohne weitere Bearbeitung gelöst werden können die Menge der Endknoten Die Schlussfolgerung erfolgt rückwärts, ausgehend von der ursprünglichen Zielsetzung. Bsp.: Tower of Hanoi-Puzzle Scheibe A Scheibe B Scheibe C Platz 1 Platz 2 Platz 3 Problemreduktion findet durch folgenden Operator statt, der das Problem von Plat I mit n > 1 Scheiben zu Platz k zu bewegen in 3 Teilprobleme zerlegt: (1) Bewegung des Stapels mit n – 1 Scheiben von Platz I nach Platz j (2) Bewegung des Stapels mit einer Scheibe von Platz I zu Platz k (3) Bewegung des Stapels mit n – 1 Scheiben von Platz j zu Platz k 150 Spezielle Algorithmen (SAl) Primitiv: Bewegung nur einer einzigen Scheibe von Platz I zu Platz k, vorausgesetzt, dass auf Platz k keine kleinere Scheibe liegt. Die Wahl der Repräsentationsform Zustandsraum oder Problemreduktion ist nicht eindeutig. Das Problem „Tower of Hanoi“ lässt sich auch mit Hilfe einer Zustandsraum-Repräsentation lösen. Allerdings erweist sich bei diesem Problem die Problemreduktion als wesentlich erffizienter, da sie unmittelbar einen Lösungsalgorithmus ergibt. 3.1.3 And / Or-Graphen Zustandraum bzw. Problemreduktion sind Spezialfälle des sog. „And / Or-Graphen“. And / Or-Graphen haben 2 Typen von Knoten: „And-Knoten“ bewirken: Alle Nachfolgerknoten müssen gelöst werden, um das durch den Knoten repräsentierte Problem zu lösen. Beim „Or-Knoten“ genügt es einen der Nachfolger zu lösen. Wie lassen sich „And“ unf „Or“ – Graphen zu einer Problemreduktion verknüpfen? Relativ einfach: Eine Aufteilung in Teilproblemen impliziert natürlich, dass alle Knoten dieser untergeordneten Aufgaben einer Lösung bedürfen – womit „And“ erklärt ist. Auf einen Zustand lassen sich oft mehr als ein Operator anwenden, d.h. es gibt verschiedene Möglichkeiten zur Reduktion. Die bestehenden Alternativen werden durch den „Or“-Graph dokumentiert. 151 Spezielle Algorithmen (SAl) 3.2 Uninformierte Suchstrategien in Zustandsraumrepräsentationen Blinde Suche (blind search) nennt man die Algorithmen, die keinerlei Informationen über wahrscheinliche Lösungspfade verwenden. In der Praxis ist blind search nur bei „kleinen Problemen“ anwendbar. „blind search“ Algorithmen müssen gegebenenfalls sicherstellen, dass alle möglichen Lösungspfade überprüft werden. Wenn alle Nachfolger eines Knoten bestimmt werden, dann spricht man vom Expandieren des Knotens. Breitensuche (breadth first search) 89 Bei Breadth-First-Search werden die Knoten expandiert, die die geringste Entfernung zu den Startknoten (des Graphen) haben. Benötigt werden zwei Listen OPEN (bestehend aus nicht expandierten Knoten) und CLOSED (bestehend aus expandierten Knoten). 1. Schreibe den Startknoten nach OPEN. Falls er einen Zielknoten darstellt, dann ist das Problem gelöst. 2. Wenn OPEN leer ist, gibt es keine Lösung. 3. Übertrage den ersten Knoten n von OPEN nach CLOSED. 4. Erweitere Knoten n. Falls er keine Nachfolger hat, gehe nach (2). 5. Schreibe alle Nachfolger von n an das Ende von OPEN. 6. Wenn einer Nachfolger ein Zielknoten ist, ist eine Lösung gefunden, anderenfalls gehe nach (2). Suche mit einheitlichen Kosten (uniform cost search) Ein Spezialfall der Breitensuche tritt bei Problemen auf, wo Pfade durch Kosten gekennzeichnet sind, z.B. beim TSP-Problem. Der Breadth-First-SearchAlgorithmus kann für das Bestimmen eines minimalen Weges von einem gegebenen Startknoten zu einem Zielknoten in bewerteten Graphen erweitert werden. c(i, j ) : Bewertung der Kante, z.B. Kosten des Wegs vom Knoten i nach Knoten j. g (i) : minimaler Weg vom Startknoten zum Knoten i. Wenn die Kosten zwischen den benachbarten Knoten alle gleich wären, handelte es sich um reine Breitensuche, ansonsten spricht man von Uniform-Cost-Search. 1. Schreibe den Startknoten s nach OPEN. Falls er einen Zielknoten darstellt, ist das Problem gelöst, anderenfalls setze g ( s ) = 0 . 2. Wenn OPEN leer ist, gibt es keine Lösung. 3. Suche in OPEN den Knoten i mit dem niedrigsten Wert für g (i ) . Falls keine eindeutige Entscheidung möglich ist, wähle einen Zielknoten. Existiert kein solcher, wähle zufällig, übertrage i nach CLOSED. 4. Wenn i ein Zielknoten ist, so ist eine Lösung gefunden. 5. Erweitere Knoten i. Wenn er keine Nachfolger hat, gehe nach (2). 6. Für jeden Nachfolger j von i berechne g ( j ) = g (i ) + c(i, j ) und schreibe jedes j nach OPEN. 7. Gehe nach (2) 89 vgl. 2.2.2 152 Spezielle Algorithmen (SAl) Bsp.: Durchrechnen dieses Algorithmus im Rahmen der folgenden Landkarte: A 2 5 F B 3 3 4 D 2 3 4 C 4 E A B F C D D E D C F B E C F F E E D F C 14 15 16 16 E C C B D E D B 17 18 19 B E Tabelle mit der Reihenfolge der Rechenschritte zum Algorithmus 1 2 3 4 5 6 OPEN A(0) AB(2), AF(5) ABC(4), ABD(5), AF(5) ABCD(7), ABCE(8), ABD(5), AF(5) ABCD(7), ABCE(8), ABD(5), AFD(8), AFE(9) 153 CLOSED A A, AB A, AB, ABC A, AB, ABC, AF Spezielle Algorithmen (SAl) Tiefensuche (depth first search) 90 Bei Depth-First-Search werden zuerst die Knoten expandiert, die die größte Entfernung zu den Startknoten des Graphen haben. 1. Schreibe den Startknoten nach OPEN. Falls er einen Zielknoten darstellt, so ist das Problem gelöst. 2. Wenn OPEN leer ist, gibt es keine Lösung 3. Übertrage den ersten Knoten n von OPEN nach CLOSED. 4. Wenn die Tiefe von n gleich der vereinbarten Maximaltiefe ist, gehe nach (2). 5. Erweitere Knoten n. Falls er keinen Nachfolger hat, gehe nach (2). 6. Schreibe alle Nachfolger von n an den Anfang von OPEN. 7. Wenn einer der Nachfolger ein Zielknoten ist, so ist eine Lösung gefunden, anderenfalls gehe nach (2). Bidirektionale Suche (bidirectional search) Bisher wurde forward reasoning zum Suchen verwendet. In den Fällen, bei denen ein Knoten mehrere Vorgänger hat, kann ebenso backwared reasoning verwendet werden. Biderctional-Search bezeichnet die Kombination beider Vorgehensweisen. Parallel werden zwei Bäume aufgebaut. Der eine beginnt mit dem Startknoten, der andere mit dem Zielknoten. Im folgenden Algorithmus wird angenommen, dass jeweils nur ein Start- und Zielknoten vorhanden ist. Sobald der Schnitt der beiden Graphen n icht leer ist, ist eine Lösung gefunden. Bezeichnungen: Startknoten s; Zielknoten (Terminalknoten) t; s-OPEN und s-CLOSED sind Listen, die ausgehend vom Startknoten generiert werden; t-OPEN und t-CLOSED, die vom Terminalknoten; c(i, j ) = Kosten (Bewertung) des Pfeils vom Knote i zum Knoten j; g s (x) = minimaler Weg von s zum Knoten x; g t (x) = analog vom Knoten x zu t. Algorithmus (Bidirectional Search) 1. Setze s in die Liste s-CLOSED, 2. Setze t in die Liste t-CLOSED, 3. Führe eine Entscheidung herbei, ob der nächste Schritt per forward chaining erfolgt (goto 4) oder per backward chaining (goto 5). 4. Forward Chaining Entferne von s-OPEN einen Knoten n mit g s (n) = Minimum. Platziere n in s-CLOSED. Falls n ebenfalls in t-CLOSED ist, goto (6). Ansonsten, für jedenh Nachfolger x von n: Falls x weder in s-OPEN noch in s-CLOSED, platziere x in s-OPEN, verkette x mit n, setze g s ( x) = g s (n) + c(n, x) . (nicht Fall 4.2), aber x in s-OPEN: ein neuer Weg s zu x ist gefunden. (nicht Fall 4.2) goto (2) 5. Backward Chaining Entferne von t-OPEN einen Knoten n Falls x weder in t-OPEN noch in t-CLOSED (nicht Fall 5.2) (nicht Fall 5.2) 90 vgl. 2.2.1 154 Spezielle Algorithmen (SAl) goto (2) 6. Wähle aus der Schnittmenge der Listen s-CLOSED mit t-CLOSED oder s-CLOSED mit tOPEN einen Knoten n mit g s ( n) + g t ( n) minimal. Bestimme den kompletten Pfad von s nach t mit Hilfe der Verkettungen. 3.3 Informierte (heuristische) Suche in Zustandsraumrepräsentationen Das bislang betrachtete „blinde“ Suchen ist in der Praxis nicht anwendbar, da die Anzahl der zu püfenden Möglichkeiten an Zeit- bzw. Speicherplatzrestriktionen stößt (kombinatorische Explosion). Benötigt wird zusätzliche Information, die eine Entscheidung in „vermutlich bessser“ oder „vermutlich weniger gut“ gestattet. Man spricht von Heuristik oder heuristischer Information. Heuristiken sind beim Suchen für folgende Teilaufgaben verwendbar: 1. Auswahl des Knotens, der als nächster expandiert werden soll 2. Beim Expandieren eines Knoten müssen u.U. nicht alle Nachfolger generiert werden. Eine Heuristik kann hier helfen, die passende Auswahl zu treffen. 3. Pruning (Abschneiden): Eine Problemreduktion kann dadurch herbeigeführt werden, dass im Lösungsbaum bzw. im Suchgraphen Teilbäume bzw. eine Menge von Knoten abgeschnitten wird. Allgemeiner Ansatz für die informierte (heuristische) Suche ist die sog. Bestensuche (Best First). Bei dieser Suche wird ein Knoten unabhängig von einer Evaluierungsfunktion f (n) für die Expandierung ausgewählt. Die Bewertung ermittelt die Distanz zum Ziel. Die Bestensuche kann innerhalb der Suchumgebung mit Hilfe einer Prioritätswarteschlange implementiert werden. Es gibt eine ganze Familie von Best-First-Search-Algorithmen mit unterschiedlichen Evaluierungsfunktionen. 3.3.1 Geordnete Zustandsraum-Suche Es wird die Existenz einer Evaluations-Funktion f * () unterstellt, die im Sinne einer Heuristik die Güte eines Knoten abzuschätzen gestattet. Dabei wird angenommen, dass Knoten i dem Knoten j vorgezogen wird, wenn f * (i ) < f * ( j ) . 1. Schreibe den Startknoten s nach OPEN, bestimme f * ( s ) . 2. Falls OPEN leer, exit mit Fehlermeldung „Es existiert keine Lösung“. 3. Enferne aus der Liste OPEN einen Knoten i mit f * (i ) minimal. Ist er nicht eindeutig zu bestimmen, so wähle einen Zielknoten. Existiert kein solcher, so wähle zufällig einen Knoten aus. 4. Übertrage i von OPEN nach CLOSED. 5. Wenn i ein Zielknoten ist, so ist eine Lösung gefunden. 6. Expandiere Knoten i und ermittle alle Nachfolger. Für jeden Nachfolger j von i bestimme 6.1 Ermittle f * ( j ) . 6.2 Falls j weder in OPEN noch in CLOSED, platziere j in OPEN (zusammen mit f * ( j ) ), verkette j mit seinem Vorgänger i. (nicht Fall 5.2), j ist nicht in OPEN oder in CLOSED: ein neuer Weg s zu j ist gefunden. Bestimme f *neu ( j ) und vergleiche mit f *alt ( j ) . - Wähle den kleineren der beiden Wege dadurch aus, dass j hinsichtlich 155 Spezielle Algorithmen (SAl) dieses Wegs mit seinem Vorgänger verkettet wird bzw. bleibt. - Speichere den aktualisierten Wert von f * ( j ) . - Falls j in der CLOSED-Liste war, versetze es zurück in die OPEN-Liste. 7. Gehe zu 2. 3.3.2 Gierige Bestensuche Die gierige Bestensuche (Greedy Best-First) versucht den Knoten zu expandieren, der dem Ziel am nächsten liegt. Begründung: Dies führt wahrscheinlich schnell zu einer Lösung. Kosten werden nur nach der Heuristikfunktion f (n) = h(n) ausgewertet. h(n) = geschätzte Kosten für den billigsten Pfad von Knoten n zu einem Zielknoten Eigenschaften der Greedy-Suche - Vollständigkeit: Nein, kann in Zyklen hängen bleiben Zeitkomplexität: O b m , eine gute Heuristik kann das dramatisch verbessern ( ) ( ) Speicherkomplexität: O b m , alle Knoten werden im Speicher gehalten Optimalität: Nein 3.3.3 A*-Suche Es ist ein Suchverfahren der Klasse „Geordnete Zustandraumsuche“. Vorläufer ist der Algorithmus von Dijkstra (1959) zur Bestimmung kürzester Entfernungen. Die in Operations Research verwendeten Branch-and-Bound-Verfahren sind ähnlich. Der A*-Algorithmus 91 bestimmt die minimalen Kosten eines Weges vom Startknoten zu einem Zielknoten in dem Zustandsraum-Graph. Beim A*-Algorithmus 92 beseht die Evaluierungsfunktion f aus zwei Teilen: f ( n) = g ( n) + h( n) g (n) : bisherige Pfadkosten (von Wurzel bis zum Knoten n) h(n) : geschätzte Pfadkosten von n bis zum Ziel f (n) : geschätze Gesamt-Pfadkosten von Wurzel bis zum Ziel g ist ein Maß für die bereits zurückgelegte Entfernung, während h den verbleibenden Suchaufwand abschätzt. h wird in Abhängigkeit des Anwendungsgebiets gewählt. Endlichkeit von A*: Wenn die Anforderungen an h erfüllt sind, dann findet A* in endlich vielen Schritten eine optimale Lösung, falls es zulässige Lösungen gibt. Eigenschaften der A*-Suche 91 92 Klassiker der KI, geht auf Hart, Nilsson und Raphael (1968) zurück. http://de.wikipedia.org/wiki/A* 156 Spezielle Algorithmen (SAl) 3.4 Lokale Suchalgorithmen und Optimierungsprobleme Allgemeine Problemstellung: Gegeben sei eine Funktion f : S → ℜ ( S : Suchraum). Finde ein Element s ∈ S , dass f optimiert (maximiert oder minimiert) bzw. Finde ein Element s ∈ S , dass f maximiert. (Ist f zu minimieren, kann man stattdessen f = − f betrachten). Voraussetzung: Für ähnliche Elemente s1 ; s 2 ∈ S unterscheiden sich die Funktionswerte f ( s1 ) und f ( s 2 ) nicht zu sehr (keine großen Sprünge in den Funktionswerten). Verfahren: Gradientenverfahren 93 Zufallsaufstieg Simulated Annealing Akzeptieren mit Schwellenwert Sintflut Algorithmus Algorithmen für die lokale Suche arbeiten mit einem einzelnen aktuellen Zustand (an Stelle von mehreren Pfaden) und bewegen sich im Allgemeinen nur zu Nachbarn dieses Zustands. 3.4.1 Hillclimbing-Suche Der Hillclimbing-Algorithmus 94 besteht im Wesentlichen aus einer Schleife, die ständig in Richtung des steigenden Werts verläuft, also aufwärts. Sie terminiert, wenn sie einen Gipfel erreicht, wobei kein Nachbar einen höheren Wert hat. Hill Climbing erstellt eine lokale Ordnung durch Heuristik: 1. Sei L die Liste der Startknoten für das Problem sortiert nach der Heuristik 2. Ist L leer, so melde einen Fehlschlag. Anderenfalls wähle den ersten Knoten n aus L. 3. Stellt n einen Zielknoten dar, so melde Erfolg und liefere den Pfad vom Startknoten zu n. 4. Anderenfalls entferne n aus L und füge seine Nachfolger sortiert nach der Heuristik am Anfang von L ein. Markiere die neuen Knoten mit dem jeweils zugehörigen Pfad vom Startknoten. 5. Weiter mit Schritt 2 93 94 vgl. 3.5 http://de.wikipedia.org/wiki/Bergsteigeralgorithmus 157 Spezielle Algorithmen (SAl) 3.4.2 Simulated Annealing und seine Geschwister 3.4.2.1 Simulated Annealing Prinzip des Simulated Annealing 95: - Zufällige Varianten der aktuellen Lösung wurden erzeugt. - Bessere Lösungen werden übernommen - Schlechtere Lösungen werden mit einer bestimmten Wahrscheinlichkeit übernommen, die abhängig von -- der Qualitätsdifferenz der aktuellen und der neuen Lösung und -- einem (Temperatur-) Parameter, der im Laufe der Zeit verringert wird. Ausgangspunkt dieser Idee (Minimierung statt Maximierung): - Physikalische Minimierung der Energie (genauer: der Atomgitterenergie), wenn ein erhitztes Stück Metall langsam abgekühlt wird. Dieser Prozeß wird Ausglühen (annealing) genannt. Er dient dazu, ein Metall weicher zu machen, indem innere Spannungen und Instabilitäten aufgehoben werden, um es dann leichter bearbeiten zu können. - Alternativ kann man das Rollen einer Kugel auf einer gewellten Oberfläche heranziehen. Die zu minimierende Funktion ist die potentielle Energie der Kugel. Am Anfang hat die Kugel eine gewisse kinetische Energie, die ihr es erlaubt, Anstiege zu überwinden. Durch Reibung sinkt die Energie der Kugel, so dass sie schließlich in einem Tal zur Ruhe kommt. Achtung: Es gibt keine Garantie, dass das globale Optimum gefunden wird. Algorithmus zur Minimierung 96 Wähle eine Anfangskonfiguration Wähle eine Anfangstemperatur T > 0 WIEDERHOLE WIEDERHOLE Wähle eine neue Konfiguration, die eine kleine Änderung der alten Knofiguration ist Berechne die Qualitätsfunktion Q der neuen Konfiguration DE := Q(neu ) − Q(alt ); WENN DE < 0 DANN alte Konfiguration := neue Konfiguration; SONST WENN Zufallszahl < exp(− DE /( k ⋅ T ) 97 DANN alte Konfiguration := neue Konfiguration; BIS lange keine Erniedrigung der Qualität; Verringere T; BIS überhaupt keine Verringerung der Qualitätsfunktion mehr; Mit einer gewissen Wahrscheinlichkeit p( DE ) = exp(− DE /(k ⋅ T )) werden auch energetisch schlechtere Konfigurationen angenommen. In der Praxis wird dies dadurch bewerkstelligt, dass man eine Zufallszahl zwischen 0 und 1 bestimmt. Ist sie kleiner als als p (DE ) wird die energetisch schlechtere Konfiguration als der neue Systemzustand angenommen. 95 http://de.wikipedia.org/wiki/Simulated_Annealing vgl. Thomas Otto: Reiselust in c’t 1994, Heft 1 97 k : Boltzmann-Konstante, universelle Naturkonstante wie bspw. die Lichtgeschwindigkeit, um diese Konstante kümmert man sich in der Regel beim Simulated Annealing nicht 96 158 Spezielle Algorithmen (SAl) Simulated Annealing in Neuronalen Netzen (Hopfield-Netze, Boltzmann-Maschine) Dynamik: Die Dynamik des Simulated-Annealing ist bestimmt durch den folgenden Simulationsprozeß (1) Wähle ein Neuron zufällig aus (2) Errechne die Wahrscheinlichkeit: p ( oi = 1) = 1+ e − ∑ 1 ( wij ⋅o j ( t ) + Θ i )/ T (3) Erzeuge eine gleichverteilte Zufallszahl z: 0 <= z <= 1 (4) Falls z <= p, dann setze oi = 1, sonst ist oi = 0 (5) Weiter bei (1) Die Dynamik führt zu einem stabilen Zustand, falls T klein gewählt wird. Bei großem T besteht immer die Wahrscheinlichkeit, daß die Gesamtenergie zunimmt und danach ein lokales Minimum verlassen werden kann. Energiefunktion: In Neuronalen Netzen wird häufig (z.B. in Hopfield-Netzen) folgende Energiefunktion verwendet: E=− 1 ∑ ∑ wij ⋅ o j ⋅ oi + ∑i Θi ⋅ oi 2 i j heißt Energie zum Zustand ai (= Ausgangsgröße oi). Bezogen auf Neuron i beträgt die Energie: N E = − ∑ wij ⋅ o j ⋅ ai − Θi ⋅ ai j N N ∂E = − ∑ wij ⋅ o j − Θi = − ( ∑ wij ⋅ o j + Θ i ) ∂ai j j N ΔE = − ( ∑ wij ⋅ o j + Θi ) ⋅ Δai j Setzt man Neuron i von 0 auf 1, dann ist Δai = 1 − 0 = 1 Daraus folgt: p ( oi = 1) = 1 1 + e ΔE / T 159 Spezielle Algorithmen (SAl) p(oi=1) ΔE / T Abb. 2.6-10: Verlauf von p(oi)=1 Für ΔE = E1 − E0 < 0 folgt p(oi=1)>1/2. E1 ist die Energie, wenn Neuron i den Zustand 1 hat und E0 ist die Energie, wenn Neuron i den Zustand 0 hat. Allgemeingülig ist folgender Satz: Sind Z0 und Z1 zwei Zustände des Netzes mit den zugehörigen Energien E0 und E1, dann gilt für die Wahrscheinlichkeit der Zustände: p ( Z0 ) = e ( E1 − E0 )/ T p ( Z1 ) Daraus läßt sich folgern, daß das Verfahren des simulierten Kühlens stochastisch gegen das absolute Minimum konvergiert. Hat bspw. die Energiefunktion folgenden Verlauf E Z Z1 Z0 Abb. 2.6-11: Energiekurve 160 Spezielle Algorithmen (SAl) p ( Z0 ) = e ( E1 − E0 )/ T > 1 und p ( Z0 ) > p ( Z1 ) , d.h.: Die Wahrscheinlichkeit für den p ( Z1 ) Zustand des absoluten Minimums ist größer als die Wahrscheinlichkeit für die Zustände der relativen Minima. Generell gilt: Das Verfahren des simulierten Kühlens konvergiert gegen den Zustand, für den die Energie das absolute Minimum annimmt. Hinweis: Da das simulierte Kühlen ein stochastisches Verfahren ist, erhält man das Minimum zwar mit Wahrscheinlichkeit, aber nicht mit Sicherheit. , dann ist Boltzmann-Verteilung: Mit einer gewissen Wahrscheinlichkeit werden die energetisch schlechteren Konfigurationen angenommen. Der dazu abgeleitete mathematische Formalismus besitzt große Ähnlichkeit mit den Formeln einer physikalischen Theorie, die man als Boltzmann-Statistik bezeichnet. Die Boltzmann-Statistik liefert für die statistische Verteilung von Molekülen der Energie die Formel p ( E ) = α ⋅ e − E /( k ⋅T ) p( E ) : Wahrscheinlichkeit für ein Molekül, daß es die Energie E besitzt. k: Dimensionskonstante (Boltzmann-Konstante) T: absolute Temperatur (eine mittlere Temperatur für alle Teilchen) Offenbar folgt aus der vorstehenden Formel für 2 Energiezustände E1 und E0: (E −E ) − 0 1 p( E0 ) = e k ⋅T p ( E1 ) E0-E1: Energieunterschied k: Boltzmann-Konstante T: (Temperatur-) Parameter 3.4.2.2 Threshold Accepting und “Sintflut”-Algorithmus Idee: Ähnlich wie beim Simulated Annealing werden auch schlechtere Lösungen akzeptiert, allerdings mit einer oberen Schranke für die Verschlechterung beim Threshold Accepting bzw. mit einer unteren Schranke beim Sintflut-Algorithmus. Threshold-Algorithmus 98 Wähle eine Anfangskonfiguration Wähle eine Anfangstemperatur T > 0 WIEDERHOLE WIEDERHOLE Wähle eine neue Konfiguration, die eine kleine Änderung der alten Konfiguration ist Berechne die Qualitätsfunktion Q der neuen Konfiguration DE := Q(neu) − Q(alt ); WENN DE < T DANN alte Konfiguration := neue Konfiguration; BIS lange keine Erniedrigung der Qualität; Verringere T; BIS überhaupt keine Verringerung der Qualitätsfunktion mehr; 98 1990 veröffentlicht von Dueck, Scheuer 161 Spezielle Algorithmen (SAl) Leider muß man einen optimalen Fahrplan für die Ermittlung der Temperatur ermitteln “Sintflut”-Algorithmus Wähle eine Anfangskonfiguration Wähle eine Regenmenge Re gen > 0 Wähle einen Wassers tan d > 0 WIEDERHOLE Wähle eine neue Konfiguration, die eine kleine Änderung der alten Konfiguration ist Berechne die Qualitätsfunktion Q der neuen Konfiguration WENN Q( neu ) > Wassers tan d DANN alte Konfiguration := neue Konfiguration; Wassers tan d := Wassers tan d + Re gen; BIS lange keine Erniedrigung der Qualität; Vorteil: Kein ausgefeiltes Abkühlschema Übung 34_2: TSP mit Simulated Annealing (SA) and Threshold Accepting 3.4.3 Genetische Algorithmen Genetische Algorithmen 99 können als stochastische Hill-Climbung Suche mit einer großen Anzahl von Individuen in einer Population interpretiert werden Übung 34_3: Hillclimbing, Simulated Annealing, Genetische Algorithmen 99 vgl. 5. 162 Spezielle Algorithmen (SAl) 3.5 Lokale Suche in stetigen Räumen Die meisten Umgebungen der realen Welt sind stetig. Keine der bisher beschriebenen Algorithmen können jedoch stetige Zustandsräume verarbeiten. Einige lokale Suchtechniken zum Auffinden von optimalen Lösungen in stetigen Räumen beruhen auf dem Gradientenabstiegsverfahren bzw. dem NewtonVerfahren. 3.5.1 Gradientenverfahren - Voraussetzung: S ⊆ ℜ n , f : S → ℜ ist differenzierbar. - Gradient: Differentialoperation, die einen Vektor in Richtung der stärksten Steigung einer Funktion liefert r ∇z |( xo yo ) yo ∂z ∂z |( xo yo ) |( x y ) ∂y o o ∂x z xo y x Abb.: Darstellung des Gradienten einer Funktion z = f ( x, y ) am Punkt ( xo , y o ) . Es ist r ⎛ ∂z ⎞ ∂z ∇z |( xo yo ) = ⎜⎜ |( xo , yo ) , |( xo , yo ) ⎟⎟ . ∂y ⎝ ∂x ⎠ Idee: Mache von einem zufälligen Startpunkt kleine Schritte im Suchraum, jeweils in Richtung des stärksten Anstiegs der Funktion, bis ein (lokales) Maximum erreicht ist. r ( 0) ( ) = x1( 0 ) ..., x n( 0 ) r (i ) 2. Bestimme den Gradienten am aktuellen Punkt x : ⎛ ∂ r r r ⎞ ∂ f x ( i ) ,..., f ( x ( i ) ) ⎟⎟ ∇ xv f ( x ( i ) ) = ⎜⎜ ∂x n ⎠ ⎝ ∂x1 r (i +1) r (i ) r 3. Gehe ein kleines Stück in Richtung des Gradienten: x = x + ε ⋅ ∇ xr f x ( i ) . „ ε “ ist ein 1. Wähle einen (zufälligen) Startpunkt x ( ) ( ) Schrittweitenparameter 100. 4. Wiederhole Schritte 2 und 3. bis ein Abbruchkriterium erfüllt ist 101. 100Lernrate 101 in neuronalen Netzen, vgl. 4. Vorgegebene Anzahl Schritte ausgeführt, aktueller Gradient sehr klein. 163 Spezielle Algorithmen (SAl) - Probleme: -- Wahl des Schrittweitenparameters --- Bei einem zu kleinen Wert kann es sehr lange dauern, bis das Maximum erreicht ist (Schritte zu klein) --- Bei einem zu großen Wert kann es zu Oszillationen kommen (Hin- und Herspringen im Suchraum) kommen (Schritte zu groß). --- Lösungsmöglichkeiten: Momentum, Schrittweitenparameter. -- Hängenbleiben in einem lokalen Maximum --- Da nur solche Steigungsinformationen genutzt wird, wird eventuell nur lokales Maximum erreicht --- Chancenausführung für Finden des globalen Optimums: Mehrfaches Ausführen des Gradientenaufstiegs von verschiedenen Startwerten aus. 3.5.2 Zufallsanstieg (Monte-Carlo Zufalls-Suche) Idee: Falls die Funktion f nicht differenzierbar ist, kann man versuchen eine Richtung, in der die Funktion ansteigt, durch Auswerten zufälliger Punkte in der Umgebung des aktuellen Punkts zu bestimmen 1. Wähle einen (zufälligen) Startpunkt s 0 ∈ S 2. Wähle einen Punkt s '∈ S „in der Nähe“ des aktuellen Punkts si (z.B. durch zufällige, aber nicht so große Veränderung von si ) ⎧s ' , falls f ( s ' ) ≥ f (s ) sonst ⎩ si 3. Setze si +1 = ⎨ 4. Wiederhole Schritte 2 und 3 bis Abbruchkriterium erfüllt ist. Problem: Hängenbleiben in lokalen Minima. Monte-Carlo Suche Man könnte auf die Idee kommen, eine gewisse Anzahl von Punkten rein zufällig im Suchraum auszuwählen ind die „Höhe“ an diesen Punkten zu messen. Der beste Wert wird dann ausgewählt und als Optimum verwendet. Es ist klar, dass die Wahrscheinlichkeit mit diesem Algorithmus (zumindest wenn relativ wenige Werte gemessen werden) ein globales Optimum zu finden, äußerst gering ist bzw. Der Aufwand extrem hoch wird. So schlecht die Leistung als Optimierungsmethode auch sein mag, jedenfalls weist diese Strategie einen Vorteil gegegenüber dem Hillclimbing-Algorithmen auf: Es besteht nicht die Gefahr sich an einem lokalen Optimum „festzufressen“, vielmehr wird ohne Einschränkung der gesamte Suchraum betrachtet. 164 Spezielle Algorithmen (SAl) 3.5.3 Newton’sches Verfahren Das Newton’sche Verfahren ist eine allgemeine Technik zur Ermittlung der Nullstellen von Funktionen. Ausgangspunkt ist die folgende Formel zur Abschätzung g ( x) der Nullstelle: x ← x − . g ' ( x) Zum Finden von Maximum bzw. Minimum ist ein x zu bestimmen, so dass der Gradient gleich 0 ist (d.h. ∇f ( x) = 0 ). „g(x)“ in der Formel von Newton wird also zu ∇f ( x) und die aktualisierte Gleichung kann folgendermaßen geschrieben werden: x ← x − H −f 1 ( x )∇f ( x) . H f (x ) ist die Hesse-Matrix der zweiten Ableitung, deren Elemente H ij durch ∂ 2 f / ∂xi ∂x j gegeben sind. 165 Spezielle Algorithmen (SAl) 3.6 Spiele 3.6.1 Algorithmen für das Suchen in Spielbäumen Spiele spielen bedeutet: Untersuchen, was beim Versuch Zukunftplanung passiert, obwohl ein Gegner existiert. Spiele sind demnach: Suchprobleme mit einem Gegenspieler. Durch Aktionen des Gegenspielers entstehen Unsicherheiten: Mögliche Ereignisse müssen behandelt werden. perfekte Information unvollständige Information deterministisch Dame, Schach, GO ? zufällig Backgammon, Monopoly Bridge, Scrabble, Skat, Poker Abb. Suchstrategien für Spiele Untersucht werden deterministische Spiele mit vollständiger Information und zwei Spielern: Spieler MAX bzw. Spieler MIN, die Spieler ziehen abwechselnd. Ein solches Spiel kann als Problemlösungsprozeß aufgefasst werden, bei dem die Spieler abwechselnd Operationen wählen. Jeder Spieler versucht, einen für ihn möglichst günstigen Endzustand (Zielzustand) zu erreichen. Ein Endzustand ist durch einen Gewinn gekennzeichnet, welchen der eine Spieler minimieren, der andere Spieler maximieren möchte. Ein Spiel ist demnach definiert durch - Anfangszustand Menge von Operatoren, die legale Züge definieren. Test auf Spielende (Endzustände) Utility-Funktion (Gewinnfunktion) 3.6.2 Spielbäume in Minimax-Notation Gegeben ist folgende Situation: A (Maximierer) und B (Minimierer) sind Gegenspieler in einem Nullsummenspiel. In einer konkreten Spielsituation hat A die Möglichkeit Zustand 2 oder Zustand 3 herbeizuführen. Er überlegt die möglichen Züge von B und seine Anwort darauf. Insgesamt stellt sich die Spielsituation folgendermaßen dar: 166 Spezielle Algorithmen (SAl) 1 A (MAX) 2 B 4 A 10 w 3 B (MIN) 5 A 11 12 w l 6 A 13 14 w l 7 A 15 l 16 u 8 A 17 u 18 w 9 A 19 u 20 l 21 u w = win; l = loose, u = unentschieden Spieler A (MAX) kann sich entscheiden, ob er Spielsitiation 2 oder 3 herbeiführen will, d. h. Knoten 1 ist vom Typ "or". Wenn er sich für Situation 2 entscheidet, muß er mit dem Eintreten einer der drei Möglichkeiten rechnen, die B zur Verfügung stehen. Unterstellt wird vollständige Information, d.h. A muß mit jedem der möglichen Züge von B rechnen. Deswegen werden die Züge von B (aus Sicht von A) als "and"Knoten modelliert. Die Entscheidungssituation für A stellt sich damit folgendermaßen dar: Ausgehend von den Endknoten rechnet A zurück und bestimmt den höchsten Spielwert (mögliche Werte für w, l und u z.B. w = 1, l = -1, u = 0): - Der Wert eines "OR"-Knotens ist das Maximun der Werte seiner Nachfolger Der Wert eines "MIN"-Knotens ist das Minimum der Werte seiner Nachfolger Minimax - Erzeugen des Suchbaums des Spiels (bis herunter zu den Spielendesituationen) Wurzel zeigt den Initialzustand, MAX ist am Zug Jede Ebene repräsentiert Züge eines Spielers - Bewerten der Blätter des Suchbaums für jedes Spielende wird der Nutzen bewertet - Bewertungen bis zur Wurzel propagieren MAX wählt den maximalen Nutzen MIN wählt den minimale Nutzen MAX wählt den besten Zug unter der Annahme, dass MIN den für MAX schlechtesten Zug wählt MIN verhält sich entsprechend - Tiefensuche für den ganzen Baum - Zeitkomplexität: Wenn maximale Tiefe m und b legale Züge an jedem Punkt, dann O(b m ) 167 Spezielle Algorithmen (SAl) MAX 3 a3 a1 a2 3 102 MIN 2 b1 b2 b3 3 103 MAX 2 c1 c2 c3 12 8 2 4 d1 6 14 d2 d 3 5 2 -Knoten: MIN ist am Zug - a1 - a3 sind Züge, die MAX durchführen kann. - MIN kann mit b1-3 , c1-3 , d1-3 antworten. - Ein Zug = 2 Halbzüge = 2-Schichten-Spiel - Die Endzustände haben Utility-Werte für MAX, die anderen Knoten werden durch die Utilityfunktion ihrer Nachfolger berechnet Abb.: Illustration von Minimax (für ein fiktives, einfaches Spiel) Beispiele 1. Nim-Spiel - Zu Beginn des Spiels gibt es n Objekte - 2 Spieler MAX und MIN machen abwechselnd einen Zug - Zug: Nimm 1, 2, 3 Objekte - Terminalzustand: kein Objekt mehr da - der Spieler, der den letzten Zug ausführt, verliert MAX MIN 1 -1 -1 104 1 -1 1 MAX MIN 1 105 MAX 102 Der beste für MAX Der schlechteste für MAX ist der beste für MIN 104 MIN gewinnt, da MAX das letzte Objekt nimmt 105 MAX gewinnt, da MIN das letzte Objekt nimmt 103 168 Spezielle Algorithmen (SAl) 2. Hexapawn-Spiel Grundstellung: o o o x x x Von dieser Grundstellung aus, ziehen beide Spieler abwechselnd je einen Stein nach den Regeln 106 - ein Feld geradeaus, wenn es frei ist ein Feld diagonal nach vorn, wenn dort ein Stein des Gegners geschlagen werden kann. Das Ziel besteht darin, entweder die Grundlinie des Gegners zu erreichjen, oder ihn zugunfähig zu machen bzw. ihn festzusetzen 107. ooo xxx o o o xxx oo o xxx o o ox xx o o x ox +∞ o oox xx o o x xx o ox xx o o xx o o xx oo ox x x o xo xx 2 − 0 = 2 1− 0 = 1 2 − 2 = 0 1 − 2 = −1 1 − 1 = 0 o oo x x 107 o oxo x x 2−0 = 2 +∞ Abb.: 106 oo o x xx ähnlich den Zugregeln für das Bauernspiel beim Schach analog zum Mühlespiel 169 o oox xx o o o xx oo x x x oo x xo o o x x o x o x x o x o x x 2 − 0 = 2 1 − 1 = 0 1 − 2 = −1 2−0 = 2 3 −1 = 2 +∞ Spezielle Algorithmen (SAl) 3. Tic-Tac-Toe MAX (X) … X X X MIN (O) … X X X MAX (X) X O X O X …. …. O …. … … MIN (O) X O X O O X -1 X O 0 X X O X 1 - Initialzustand und legale Züge definieren Spielbaum - MAX hat zu Beginn 9 mögliche Züge - Spiel dauert an, solange keiner der Spieler drei X oder drei O in einer Zeile, Spalte oder Diagonale hat oder alle Felder ausgefüllt sind - Die Zahl an den Blättern beschreibt den Utility-Wert aus Sicht von MAX (hohe Werte sind gut für MAX und schlecht für MIN) Abb.: Partieller Suchbaum für TicTacToe, MAX hat den ersten Zug (x) 170 Spezielle Algorithmen (SAl) Implementierungsbeispiel: Tic-Tac-Toe Auswahl des bestmöglichen Zugs 108 public class MoveInfo { public int move; public int value; public MoveInfo(int m, int v) { move = m; value = v; } /* Rekursive Methode zur Bestimmung des besten Zugs durch den Rechner. MoveInfo gibt eine Zahl 1 bis 9 zurück (Indikator für das jeweils erreichte Kästchen) Mögliche Bewertungen genügen der Bedingung: COMP_VERLUST < UNENTSCHIEDEN < COMP_GEWINN. Komplementär dazu ist das Verhalten des menschl. Spielers in findHumanMove() */ // Computer am Zug public MoveInfo findCompMove() { int i, responseValue; int value, bestMove = 1; MoveInfo gewinnMitnahme; // Evaulierung: Gewinn, Unentschieden /* 1*/ if (besetztesBrett()) /* 2*/ value = UNENTSCHIEDEN; else /* 3*/ if ((gewinnMitnahme = computerGewinn()) != null) /* 4*/ return gewinnMitnahme; else { // Mögliche Nachfolgepositionen /* 5*/ value = COMP_VERLUST; // kleinster möglicher Wert /* 6*/ for (i = 1; i <= 9; i++) // Jedes Quadrat ausprobieren { // Suche nach Verbesserungen /* 7*/ if (isEmpty(i)) { // Jede Nachfolgeposition wird rekursiv // hinsichtlich Bewertung angesprochen /* 8*/ platziere(i,COMP); /* 9*/ responseValue = findHumanMove().value; // findHumanMove() ruft wiederun findCompMove() auf /*10*/ unplatziere(i); // Ruecksetzen /*11*/ if (responseValue > value) { // Aktualisieren bester Zug /*12*/ value = responseValue; /*13*/ bestMove = i; } } } } /*14*/ return new MoveInfo(bestMove, value); } // Mensch am Zug 108 Minimax tic-tac-toe algorithm: computer selction bzw. human selection in Mark Allen Weiss: Data Structures & Algorithm Analysis in Java 171 Spezielle Algorithmen (SAl) public MoveInfo findHumanMove() { int i, responseValue; int value, bestMove = 1; MoveInfo gewinnMitnahme; // Evaulierung: Gewinn, Unentschieden /* 1*/ if (besetztesBrett()) /* 2*/ value = UNENTSCHIEDEN; else /* 3*/ if ((gewinnMitnahme = humanGewinn()) != null) /* 4*/ return gewinnMitnahme; else { // Mögliche Nachfolgepositionen /* 5*/ value = COMP_GEWINN; // kleinster möglicher Wert /* 6*/ for (i = 1; i <= 9; i++) // Jedes Quadrat ausprobieren { // Suche nach Verbesserungen /* 7*/ if (isEmpty(i)) { // Jede Nachfolgeposition wird rekursiv // hinsichtlich Bewertung angesprochen /* 8*/ platziere(i,HUMAN); /* 9*/ responseValue = findCompMove().value; // findCompMove() ruft wiederun findHumanMove() auf /*10*/ unplatziere(i); // Ruecksetzen /*11*/ if (responseValue > value) { // Aktualisieren bester Zug /*12*/ value = responseValue; /*13*/ bestMove = i; } } } } /*14*/ return new MoveInfo(bestMove, value); } } Abb.: Implementierung des MiniMax-Algorithmus 3.6.3 Alpha-Beta-Pruning Entscheidender Nachteil von Minimax: Auswertung und Generierung des Suchbaums sind getrennte Prozezze, (ersteres setzt letzteres voraus). Viel effizienter wäre: Auswertung des Baums während seiner Entstehung mit Rückgriff auf Depth First Search. Im Spielbaum müssen Werte rückwärts von unten nach oben aktualisiert werden, also muß möglichst schnell das Ende des Baums zur Vornahme einer statischen Evaluierung erreicht werden. Mit Pruning wurde eine Möglichkeit zur Verringerung des Suchbaums und somit zur Erlangung von geringeren Suchzeiten beschrieben. Alpha-Beta-Pruning ist eine Modifikation des Minimax-Verfahrens, das wegen der Gesetzmäßigkeiten beim rückwärtigen Aktualisieren der Knotenwerte bestimmte Teile des Suchbaums von der Erweiterung ausschließt. 172 Spezielle Algorithmen (SAl) Gegeben ist der folgende Spielbaum (von irgendeinem hypothetischen Spiel): 44 MAX 44 44 27 42 27 40 78 44 86 44 68 73 78 D 36 40 C 78 68 MIN 40 79 73 27 40 27 36 B 87 68 MAX 36 36 55 MIN 36 MAX A 42 25 27 7 72 86 9 44 50 68 73 31 78 17 79 37 73 23 30 40 19 27 64 87 57 68 29 36 5 55 2 36 Abb.: Hypothetischer Spielbaum Betrachtet wird der Knoten D. MIN wird mindestens 40 wählen, MAX hat anschließend bereits 40 erreicht. Alles, was in D auch immer erreicht wird, kann diesen Wert steigern. D braucht deshalb auch nicht bewertet zu werden. Diese Form des Abschneidens (pruning) bestimmter Äste im Suchbaum wird α -Pruning genannt. Eine identische Situation ereignet sich in Knoten B. Ähnliches ereignet sich an den Knoten A und C. Im Zweig zum Knoten C wurde von MIN bereits 44 erreicht, MAX steht bereits auf 68. Alles was MIN in C erreichen könnte, kann das beits erhaltene Resultat von MIN mit 44 nicht erschüttern. Dieser Teil des Abschneidens heißt β -Pruning (eine symmetrische Version des α -Pruning). Beide Techniken miteinander kombiniert, ergeben das " α − β "- Pruning. 44 44 44 27 42 27 68 44 86 40 68 44 68 40 C 40 73 42 25 27 7 72 86 9 44 50 68 73 A 73 D 27 40 27 73 23 30 40 19 27 Abb.: Ein "abgeschnittener (pruned)" Spielbaum. 173 B Spezielle Algorithmen (SAl) Implementierung des Minimax TicTacToe-Algorithmus mit " α − β "- Pruning Der Rechner ist am Zug /* Ein Hauptprogramm-Routine ruft auf mit alpha = COMP_VERLUST und beta = COMP_GEWINN */ public MoveInfo findCompMove(int alpha, int beta) { int i, responseValue; int value, bestMove = 1; MoveInfo gewinnMitnahme; // Evaulierung: Gewinn, Unentschieden /* 1*/ if (besetztesBrett()) /* 2*/ value = UNENTSCHIEDEN; else /* 3*/ if ((gewinnMitnahme = computerGewinn()) != null) /* 4*/ return gewinnMitnahme; else { // Mögliche Nachfolgepositionen /* 5*/ value = alpha; // kleinster möglicher Wert /* 6*/ for (i = 1; i <= 9 && value < beta; i++) // Jedes Quadrat ausprobieren { // Suche nach Verbesserungen /* 7*/ if (isEmpty(i)) { // Jede Nachfolgeposition wird rekursiv // hinsichtlich Bewertung angesprochen /* 8*/ platziere(i,COMP); /* 9*/ responseValue = findHumanMove(value,beta).value; // findHumanMove() ruft wiederun findCompMove() auf /*10*/ unplatziere(i); // Ruecksetzen /*11*/ if (responseValue > value) { // Aktualisieren bester Zug /*12*/ value = responseValue; /*13*/ bestMove = i; } } } } /*14*/ return new MoveInfo(bestMove, value); } Abb.: Minimax TicTacToe mit " α − β "- Pruning Alpha-Beta-Pruning 109 mit Minimax - Jeder Knoten besitzt einen Alpha- und einen Beta-Wert Alpha-Wert: untere Schranke für einen Gewinn. Werden im MAX-Knoten geändert. Beta-Wert: obere Schranke für den Gewinn. Werden im MIN-Knoten berechnet. - Der Spielbaum wird mit Tiefensuche-Strategie abgearbeitet - Endknoten werden bzgl. des Gewinns oder bei fester Tiefe mit Hilfe einer heuristischen Bewertungsfunktion markiert. und 109 geht auf Knuth und Moore (1975) zurück 174 Spezielle Algorithmen (SAl) - In einem MAX-Knoten wird nach folgendem Prinzip der Rückgabewert berechnet: α -- β -- und β kommen vom Vorgänger. Für den Wurzelknoten ist α = −∞ und β = +∞ ist immer kleinster β -Wert auf dem Pfad zum aktuellen Knoten. -- Bertrachte nacheinander jeden Nachfolger n --- Setze α = Maximum von α und dem Rückgabewert von n. n bekommt das aktuelle --- Fall α ≤ β : gib β α und β . zurück. In diesem Fall findet Pruning statt, da der MIN-Knoten, von dem der schlechtere Alternative als die aktuelle besitzt. -- Gebe α zurück β -Wert stammt, eine - Pruning findet dann statt, wenn der Algorithmus feststellt, dass er sich in einer Alternative befindet, die nie gewählt werden würde. - In den MIN-Knoten wird entsprechend verfahren. Die Rollen von MAX durch MIN ersetzt. Bsp.: [ − ∞,+∞ ] MAX MIN [ − ∞,+∞ ] MAX MAX MIN [ − ∞,3 ] [ 3,+∞ ] ≥3 [ 3,+∞ ] ≥3 ≤3 MAX 3 MAX MIN [ − ∞,3 ] ≤3 MAX 3 12 175 α und β werden vertauscht und Spezielle Algorithmen (SAl) MAX ≥3 [ 3,+∞ ] ≥3 3 [ 3,3 ] MIN [ 3,+∞ ] MAX 3 12 8 MAX [ 3,3 ] MIN [ − ∞, 2 ] 3 MAX ≤2 X 3 12 8 2 [ 3,+∞ ] MAX MIN [3,3] >=3, <= 14 [ − ∞, 2 ] 3 X MAX <=2 X 3 12 8 14 [3,3] MIN >= 3, <= 5 [ − ∞, 2 ] [3,3] MAX 12 8 2 [ − ∞,5 ] <=2 X 3 <=14 X 2 MAX [ − ∞,14 ] X 14 176 <=5 5 Spezielle Algorithmen (SAl) MAX MIN [3,3] [ − ∞, 2 ] [3,3] MAX <=2 X 3 12 8 [2,2] 2 X 2 14 5 2 3.7 Probleme unter Rand- oder Nebenbedingungen Ein Problem unter Rand- und Nebenbedingungen (Constraint Satisfaction Problem (CSP) ist durch eine Menge von Variablen X 1 , X 2 ,..., X n sowie eine Menge von Randbedingungen (Constraints) C1 , C 2 ,..., C m definiert. Jede Variable X i hat eine nicht leere Domäne Di möglicher Werte. Jede Randbedingung Ci betieht sich auf eine Untermenge der Variable und gibt die erlaubte Wertekombination für diese Untermengen an. 177 Spezielle Algorithmen (SAl) 4. Neuronale Netze 4.1 Einführung 4.1.1 Biologische bzw. psychologische Grundlagen Grundidee Angelehnt an Anatomie und Physiologie des menschlichen Gehirns entspricht ein "Neuronales Netz (NN)" einer großen Menge sehr einfacher Prozessoren (Neuronen), die vielfach (ein Neuron bspw. mit bis zu 10.000 weiteren Neuronen) verbunden sind. Aufbau eines Neurons Zellkörper, Axon, Dendriten, Synapsen 110 Arbeitsweise Über die verbindenden Synapsen (Kontaktstellen, Speicher) können Signale verstärkt oder vermindert (d.h. gewichtet) weitergegeben werden. Falls das Summensignal einen bestimmten Wert überschreitet, reagiert das einzelne Neuron mit einem Signal (Prozessorbefehl). Zwei unterschiedliche Funktionsebenen sind zu beachten: - Die schnelle Ebene (Veränderungen im Sekundenbereich) ist durch den augenblicklichen Aktivitätsbestand geprägt (Kurzzeitgedächtnis) - Die 2. Ebene ist durch allmähliche Änderungen des Verbindungsmusters der schnellen Aktivitätsmuster geprägt (Langzeitgedächtnis) Neuronenaktivitäten und Synapsenstärken bestimmen die Arbeitsweise des Gehirns. 110 vgl. Abb. 4.1-1 178 Spezielle Algorithmen (SAl) 4.1.2 Überführung in ein datentechnisches Verarbeitungsmodell (NN) Strukturelemente eines Neurons werden in ein datentechnisches Verarbeitungsmodell überführt. Dabei dient (bzw. dienen): - der Zellkörper als Informationsträger. Im einfachsten Fall werden 2 Zustände (erregt, nicht erregt) unterschieden - die Dendriten zur Aufsummierung der Netzeingaben - das Axon zur Weitervermittlung des Erregungszustands und Kontaktaufnahme mit den Dendriten nachfolgender Neuronen über Synapsen. - die Synapse zur Bestimmung, wie sich die über ein Axon vermittelte Erregung auf andere Zellen auswirken soll. Die Stärke der Synapsen wird meistens durch einen numerischen Wert (dem Verbindungsgewicht) dargestellt. Dendrite Dendrite Zellkörper Axon Synaptic Gap Dendrite Abb. 4.1-1: Aufbau einer Nervenzelle Das menschliche Gehirn ist jedoch kein autonomes Gebilde. Es benötigt - Rezeptoren als Informatiomationsträger - Effektoren zur Ausgabe Die NN vorgeschaltete Ansteuerung wird als Eingabecodierung, die nachgeschaltete Ausgabe als Ausgabecodierung bezeichnet. Eingabecodierung heißt: Die Fragen, den Sachverhalt, das Problem der Anwendung (entsprechend den Netztypkonventionen) zu verschlüsseln Die Reaktion des Netzes ist dann wieder zu einer Antwort, einer Beurteilung oder einer Lösung zu dekodieren. NN versuchen in Struktur und Funktionsweise Gehirnkomplexe nachzubilden und dadurch eine Simulation menschlicher Denkvorgänge zu erreichen. 179 Spezielle Algorithmen (SAl) 4.2 Wie arbeiten Neuronale Netzwerke? 4.2.1 Allgemeiner Aufbau neuronaler Netze 1. Grundlegende Bestandteile Das neuronale Netz an sich gibt es nicht. Es gibt lediglich einige unterschiedliche Modelle und Ansätze. Ein "künstliches neuronales Netz" besteht generell aus verschiedenen Elementen, Strukturen, Regeln. Die grundlegenden Bestandteile sind: - eine Menge von Verarbeitungselementen (Neuronen) - eine Menge von Aktivitätszuständen - eine Ausgabefunktion für jedes Element - eine Verbindungshierarchie - eine Regel zur Fortschaltung (Propagierung) der Aktivitätsmuster von einem Element zum nächsten - eine Regel zur Aktivierung eines Elements durch anliegende Signale und Erzeugung eines neuen Aktivitätszustands - einer Lernregel zur Modifizierung der Verbindungsgewichte - eine Umgebung, in der das Netz arbeitet Die Verarbeitung in solchen Netzwerken erfolgt in der Regel von der Eingabeschicht über verborgene Schichten zur Ausgabeschicht, gelegentlich aber auch von der Eingabeschicht direkt zur Ausgabeschicht oder in einem Feedback zwischen den einzelnen Schichten (hin und her). Input-Units Hidden Units Output-Units Eingabeschicht verborgene Schicht Ausgabeschicht Abb. 4.2-1: Schichtenweise Verarbeitung Die einzelnen Schichten bestehen aus einer Anzahl von Prozessorelementen. Jede Schicht enthält mindestens ein Prozessorelement. Elemente einer Schicht sind meistens von der gleichen Art. Ein Prozessorelement kann mit beliebig vielen anderen Prozessorelementen einer anderen Schicht ("inter-neuronlayer-connection) verbunden sein oder auch mit Prozessorelementen der gleichen Schicht (intra-neuronlayer-connection). Eine Verbindung von Prozessorelementj nach Prozessorelementi heißt Gewicht, neben variablen Gewichten gibt es auch feste Gewichte. Gewichte werden gewöhnlich durch relle Zahlen (häufig im Intervall von -1 bis +1) dargestellt. In vielen Fällen gibt es eine zusätzliche Schicht mit genau einem Element, das Bias oder Schwellenwert genannt wird. Mathematisch gibt der Schwellenwert die Stelle 180 Spezielle Algorithmen (SAl) der größten Steigung einer monoton wachsenden Aktivierungsfunktion an. Biologisch entspricht er der Reizschwelle, die erreicht werden muß, damit das Neuron „feuern“ kann. In einfachen Anwendungen hat dieses Element den konstanten Wert 1 und hat nur Ausgänge keine Eingänge. Mit Hilfe des Bias-Elements kann sichergestellt werden, dass bestimmte Prozessorelmente immer eine Eingabe ungleich Null erhalten. In Simulationen kann dieser Schwellwert unterschiedlich realisiert werden, entweder als Parameter in der Aktivierungsfunktion oder über einen zusätzlichen gewichteten Eingang. Verbindungen zwischen Neuronen der gleichen Schicht sind häufig sowohl erregender als auch hemmender Art, während zwischen Schichten meistens nur erregende Verbindungen bestehen. Falls sich die Aktivierung von Schicht zu Schicht vorwärts ausbreitet, spricht man von Feedforward-Netzen. Es handelt sich um Feedback-Netze, falls die Aktivierung von nachfolgenden Schichten auch an vorgelagerte Schichten zurückgegeben wird. 2. Arbeitsweise der Elemente NN Die Elemente (Neuronen) in Neuronalen Netze arbeiten nach einem konstanten Schema. Es werden Eingabemuster i verarbeitet, die Vektoren aus Zahlen (i1,i2, ... in) sind. Über die Verbindungshierarchie, die Verbindungsgewichte bzw. die Aktivierungsfunktion wird jedes Eingabemuster durch das Netz geschleußt, bis neu errechnete (Aktivitäts-)Werte an den Ausgangsneuronen erscheinen. Auch die Ausgangswerte können wieder als Zahlenvektor o = (o1,o2,...,om) geschrieben oder zu einem Muster zusammengefaßt werden. von Neuron i o1 o2 o3 ........... on i1 i2 i3 ........... in Neuron „j“ o1 o2 o3 ............ om von Neuron j Abb. 4.2-2: Aufbau eines Neurons Zunächst werden alle Eingänge mit einer Eingabefunktion (Input) bearbeitet. Über die an einem Neuron einlaufenden Verbindungen werden Aktivierungen von anderen Neuronen herbeigeführt (z.B. o1, o2, o3, ... ). Die Aktivierungen müssen den 181 Spezielle Algorithmen (SAl) Übertragungswiderstand (das Gewicht der Verbindung zwischen zwei Neuronen) überwinden. Gewöhnlich ist das ein einfaches Aufsummieren der Eingänge oi multipliziert mit den entsprechenden Gewichten wij : net = ∑ wij ⋅ oi i Diese Formel ist auch unter dem Namen Ausbreitungsregel (Propagierungsfunktion) bekannt, denn sie beschreibt die Signalweiterleitung durch das Netz. Das Ergebnis dieser Funktion wird an die sogenannte Aktivierungsfunktion (Transferfunktion, Schwellwertfunktion) weitergeleitet. Die Transferfunktion legt die Aktivität fest, die ein Prozessorelement in Abhängigkeit von der aktuellen Eingabe annehmen soll. Der Aktivierungswert einer Verarbeitungseinheit wird über die Funktion ai(t) bestimmt. Häufig ist sie die identische Abbildung vom Netto-Input (ai = neti). Allgemein ist sie von vorhergehenden Aktivierungen, einem Satz von Parametern und einem Aktivierungszufluß von außen (ext_inpi) abhängig: a j (t + 1) = f p (net (t + 1), ext _ inp (t + 1), a j (t )) Mit den Aktivierungsgrößen Nettoinput, externer Input und alter Aktivierungszustand kann der neue Aktivierungszustand a j des Neurons ‚j’ unter Anwendung der Aktivierungsfunktion f p bestimmt werden. Als Aktivierungsfunktion kommen häufig die lineare Funktion, Schwellwertfunktionen oder Sigmoidfunktionen zum Einsatz. o1 o2 o3..... w1 j w2 j w3 j Propagierungsfunktion Aktivierungsfunktion Ausgabefunktion oj Abb. 4.2-3: Verarbeitungsfunktionen in einem Neuron Die Ausgabefunktion (Output) kann der Aktivierungsfunktion nachgeschaltet sein. Die Outputfunktion definiert, welches Ausgabesignal an die benachbarten Einheiten weitergegeben werden soll: 182 Spezielle Algorithmen (SAl) o j (t ) = f p (a j (t )) In der Regel wird der Ausgangszustand dem inneren Aktivierungszustand gleichgesetzt, aber in einigen Fällen hängt z. B. die Wahrscheinlichkeit, daß der Ausgabewert 0 oder 1 ist, vom inneren Aktivierungszustand ab. Die Outputfunktion ermöglicht einen Wettbewerb unter den einzelnen Prozessorelementen einer Schicht. So kann z.B. die Outputfunktion "winner-takesall" gewählt werden. In diesem Fall gibt nur das Prozessorelement einer Schicht seine Information weiter, dessen Transferfunktion den höchsten Wert liefert. Alle anderen Prozessorelemente dieser Schicht geben eine Null weiter. Häufig beschränkt man sich bei der Beschreibung des Prozessor- Elements auf folgende Aktivierungsfunktion: a j (t ) = f p (net j (t )) Der Wert des Aktivitätssignals ist hier nicht mehr vom vorangegangenen Wert abhängig, und das Ausgangssignal ist gleich dem Aktivitätssignal. 3. Zusammenstellung der wesentlichen Aktivierungsfunktionen Besondere Bedeutung kommt der Aktivierungsfunktion f p (net j (t )) zu. Durch geeignete Wahl läßt sich jede gewünschte nichtlineare Kennlinie realisieren. 3.1 Lineare Funktion f p (net j (t )) net j (t ) Abb.: 4.2-4: Lineare Aktivierungsfunktion 183 Spezielle Algorithmen (SAl) 3.2 halblineare Funktion f p (net j (t )) net j (t ) Die Größe θ θ ist der Schwellenwert ⎧0 x ≤ θ ⎪ f p (net j (t )) = ⎨ ⎪ x −θ ⎩ Abb. 4.2-5: Halblineare Aktivierungsfunktion 3.3 Linear bis zur Sättigung (linear threshold) f p (net j (t )) 1.0 net j (t ) -1.0 -0.5 0.5 1.0 ⎧ 1 net j (t ) ≥ 1 ⎪ Abb.: 4.2-6: Lineare Aktivierungsfunktion: f p ( net j (t )) = ⎨ net j (t ) ⎪− 1 net (t ) ≤ 1 j ⎩ 184 Spezielle Algorithmen (SAl) 3.4 Schwellenwertfunktion (threshold) f p (net j (t )) 1.0 net j (t ) -1.0 -0.5 0.5 1.0 Abb. 4.2-7: Schwellenwertfunktion: bzw. f p (net j (t )) 1.0 net j (t ) -1.0 -0.5 θ 1.0 ⎧0 net j (t ) ≤ θ ⎩1 net j (t ) > θ Abb. 4.2-8: Schwellenwertfunktion: f p ( net j (t )) = ⎨ 185 Spezielle Algorithmen (SAl) 3.5 Signum-Funktion (bipolare Schwellenwertfunktion) f p (net j (t )) 1.0 net j (t ) -1.0 θ -0.5 1.0 -1.0 ⎧− 1 net j (t ) ≤ θ ⎩ 1 net j (t ) > θ Abb. 4.2-9: Schwellenwertfunktion: f p ( net j (t )) = ⎨ 3.6 Sigmoide-Funktionen (logistische Funktion, Fermi-Funktion, TangensHyperbolicus) Logistische Funktion: f p (net j (t )) f p (net j (t )) net j (t ) Abb. 4.2-10: Logistische-Funktion Fermi-Funktion: f p (net j (t )) = 1 1+ e − λnet j ( t ) Tangenshyperbolicus: f p (net j (t )) = e net j −e − net j e net j +e − net j 186 Spezielle Algorithmen (SAl) 4. Beispiel Durch geeignete Wahl der Gewichtsfaktoren w1j(t), w2j(t) und der Schwelle θ (in Verbindung mit der Schwellenwertfunktion) lassen sich die logischen Grundoperationen ∧ , ∨ , ¬ abbilden. log. Funktion ∧ ∨ ¬ W1j(t) 1 1 -1 W2j(t) 1 1 0 θ 1.5 0.5 -0.5 4.2.2 Informationsverarbeitung in neuronalen Netzen Sie bedeutet in vielen Fällen: Abbildung von Mustern. Über die Eingabeschicht werden die im Eingabemuster enthaltenen Informationen in das Netz eingespeist. Aus der Ausgabeschicht kann das nach Ausbreitung von Aktivierungen im Netzwerk entstehende Ausgabemuster abgelesen werden. Ein neuronales Netzwerk arbeitet gewöhnlich in zwei verschiedenen Modi - einem Lernmodus - einem Ausführungsmodus (Recall Modus) 1. Lernmodus (Trainingsphase) Lernen bedeutet Verändern der Gewichte. In der Regel werden Gewichte solange verändert, bis die Zuordnung der Eingabemuster zu einer gewünschten Ausgabe erreicht wurde. Das Verändern der Gewichte geschieht über Lernregeln. Einfache Lernregeln sind die Lernregel von Hebb und die erweiterte DeltaLernregel von Widrow/Hoff. Die einfachste Form der Hebb-Regel ist: Δwij = ε ⋅ ai ⋅ o j " ε " ist hier Parameter, der die Größe eines Lernschritts bemißt. " ε " wird günstigerweise zwischen 0 und 1 gewählt. Ist ε = 0, dann wird nichts gelernt. Ist ε = 1, dann werden die Gewichte, die schon vorher erlernte Muster assoziieren konnten, wahrscheinlich zerstört, d.h.: Das Netzwerk merkt sich neue Muster so stark, daß die alten Muster teilweise zerstört werden. Eine andere sehr häufig verwendete Form des Lernens ist die sog. Delta-Regel: Δwij = ε ⋅ (t j − o j )⋅ oi bzw. Δwij = ε ⋅ δ j ⋅ oi Δwij : Änderung des Gewichts von Verarbeitungseinheit i zu Verarbeitungseinheit j in einem Lernschritt. 187 Spezielle Algorithmen (SAl) tj (target): gewünschter Output der Unit j oi : tatsächlicher Output der Verarbeitungseinheit i o j : Output der Verarbeitungseinheit j δ j = t j − o j : Fehlersignal der Verarbeitungseinheit j Diese Lernregel, auch als Widrow/Hoff-Regel (1960) bekannt, ermöglicht es, Gewichtsänderungen so durchzuführen, daß ein bestimmter Eingabevektor mit einem gewünschten Ausgabevektor assoziiert wird. Allerdings ist sie nur für zweischichtige Netze definiert, da der gewünschte Output nur im Output-Layer, nicht aber für Hidden-Units beschrieben wird. Eine für mehrere Schichten geeignete Variante ist das sogenannte Back-Propagation-Lernverfahren, eine Verallgemeinerung der Delta-Regel, die eine Berechnung der Fehlersignale für Hidden-Units definiert. Bei der Delta-Lernregel wird der Fehler nach der tatsächlichen Ausgabe eines Prozessorelements der Ausgabeschicht (bzw. seiner Aktivität) und dem vorgegebenen Ausgabewert berechnet. Dieser Fehler wird gleichmäßig auf die verschiedenen Gewichte der Prozessorelemente verteilt, die Gewichte werden angepaßt. Das Lernen muß solange andauern, bis die Veränderung der Gewichte gegen Null geht. Man gibt sich offenbar mit dem errechneten Ausgangsmuster zufrieden und berechnet den Ausgangsfehler als Differenz zwischen Ist- und Sollausgang t (Target: Ziel) des Netzes und speist ihn rückwärts wieder in das Netz ein. Mit Hilfe der Lernregeln werden die Verbindungsgewichte so verbessert, daß der Ausgangsfehler des Netzes in der nächsten Ausführungsphase (Recall-) geringer wird. Recall- und Lernphase wechseln sich gegenseitig so lange ab, bis der Ausgangsfehler für alle zu erlernenden Muster unter eine vorgegebene Schwelle sinkt. 2. Ausführungsmodus In diesem Modus werden trainierten Netzwerken Eingabedaten präsentiert, die sie noch nicht gelernt haben. Ein neuronales Netz, das nur wenige Lernschritte durchgeführt hat, produziert einen anderen Ausgabewert als ein Netz, das genügend lange gelernt hat. Die Qualität des Ausgabevektors hängt entscheidend von der Länge und der Qualität der vorangegangenen Lernphase ab. Man unterscheidet bzgl. der beiden Lernregeln - das überwachte Lernen - das unüberwachte Lernen Beim überwachten Lernen ist zusätzlich eine weitere Unterteilung in mit Lehrer bzw. ohne Lehrer möglich. Das überwachte Lernen mit Lehrer bietet dem Netzwerk zum Vergleich die richtige Lösung an. Tritt anstelle des Lehrers der Bewerter, so wird dem Netzwerk lediglich nur noch Information über die Qualität des Ergebnisses angeboten. 188 Spezielle Algorithmen (SAl) Das unüberwachte Lernen benötigt weder Lehrer noch Bewerter. Der Abgleich der Gewichtsfaktoren kannn nach der Hebbschen Regel erfolgen. In diesem Fall spricht man auch von selbstorganisierenden Netzwerken. 4.2.3 Mathematische Grundlagen zum Lernverhalten NN Beim Lernen muß ein Netzwerk über die jeweilige Lernregel Gewichte finden, die bei vorgegebener Eingabe die erwünschte Ausgabe erzeugen. Das Lernverhalten NN kann mit Matrizen und Vektoren beschrieben werden. Ist ein Eingabevektor i (in normierter Darstellung 111) und ein Ausgabevektor o gegeben, so muß das Netzwerk eine Gewichtsmatrix finden, die die Gleichung o = W⋅i erfüllt. Im einfachsten Fall besteht o nur aus einer Komponente, die Gewichtsmatrix besteht dann nur aus einer Zeile, und es gilt o = w ⋅ i Falls i = 1 ist, dann ist auch das Skalarprodukt i T ⋅ i vom Wert 1. o ⋅ i T = w ⋅ i ⋅ i T = w ⋅ i T ⋅ i 112 o ⋅ iT = w Dieser Ansatz läßt sich verallgemeinern: W = o ⋅ i T Wird die Gewichtsmatrix mit dem Eingabevektor multipliziert, dann ergibt sich der gewünschte Ausgabevektor: ( ) ( ) W ⋅ i = oi ⋅ i T ⋅ i = o ⋅ i T ⋅ i = o Falls man für die Eingabevektoren noch eine Reihe zusätzlicher Eigenschaften fordert, dann kann man eine einzige Matrix konstruieren, die gleichzeitig mehrere verschiedene Eingabe- und Ausgabevektoren aufeinander abbildet. Solche Eigenschaften für alle Eingabevektoren sind: - ii ⋅ i j = 0 für alle i <> j (d.h. die Eingabevektoren sind orthogonal zueinander) - iT ⋅ i = 1 - i =1 Dann gilt für die Matrix M = ∑ W = ∑ o ⋅ iT i i folgendes → 111 → → Der Vektor x = ( x1 , x2 ,..., xn ) mit dem Betrag x = → → → → x12 + x22 +... xn2 ist in normierter Darstellung: x N = ( x1 / x , x2 / x ,...., xn / x ) . x N hat dann den Wert 1. 112 Das Skalarprodukt ist kommutativ 189 Spezielle Algorithmen (SAl) → → → → → → → → → → T → → → T → → M ⋅ i j = ∑ Wi ⋅ i j = ∑ ( o i ⋅ i i ) ⋅ i j = ∑ oi ⋅ i i ⋅ i j = ∑ oi ⋅ ( ii ⋅ i j ) = o j ⋅ ( i j ⋅ i j ) = o j i T i T i i Die Matrix kann also beliebige Ein- und Ausgabevektoren einander zuordnen. Allerdings müssen die Eingabevektoren dann orthogonal zueinander sein. In diesem Fall ist die vorliegende Konstruktion eine Rechtfertigung für die Hebbsche Regel. Sind die Eingabevektoren nicht orthogonal zueinander, sondern bspw. linear 113 unabhängig, dann muß zur Konstruktion der Gewichtsmatrix ein anderer Lernalgorithmus herangezogen werden (z.B. Delta-Regel). Eine lineare Abhängigkeit von Vektoren erschwert die Konstruktion von Gewichtsmatrizen und spielt eine entscheidende Rolle beim Trainieren (Lernen) Neuronaler Netze. Mathematisch gesehen bilden NN Vektoren auf andere Vektoren ab. Ein NN ordnet verschiedenen Eingabevektoren entsprechende Ausgabevektoren zu. Was das NN mit den Vektoren anstellt (addiert, multipliziert, transformiert), das ist der eigentliche Kern eines neuronalen Netzes. Hier setzen die zahlreichen Modelle an. → 113 → → → → v 0 ist eine Linearkombination der Vektoren v 1 , v 2 ,..., v n , falls es gewisse Koeffizienten gibt, so daß → → → v 0 = a1 ⋅ v 1 + a2 ⋅ v 2 +... + an ⋅ vn gilt. → v 0 kann also aus der Addition bestimmter Vielfache oder Teile der anderen Vektoren bestimmt werden. → → Eine Menge von Vektoren { vi |i < n} heißt linear unabhängig, falls keiner der Vektoren vi als Linearkombination anderer Vektoren dargestellet werden kann 190 Spezielle Algorithmen (SAl) 4.3 Topologien NN 4.3.1 NN-Modelle Im Laufe eines Zeitraums von 40 Jahren (1950-1990) wurde eine große Anzahl NN entwickelt. Diese Netze unterscheiden sich in mehrfacher Hinsicht, z. B. durch - die Anzahl der Schichten - die Art des Lernens - feed forward bzw. feed back - Annahme binärer oder stetiger Ereignisse Eine einfache Klassifizierung könnte bspw. folgendermaßen aussehen: feed forward einlagig, binär Perzeptron feed back mehrlagig BPG deterministisch CPN selbstorganis. Boltzmann-Maschine BSB, Hopfield BPG: Backproppagation CPN: Counterpropagation BAM: Bidirectional Associative Memory ART: Adaptive Resonance Theory Abb. 4.3-1: Klassifizierungsmerkmale NN Die verschiedenen Netzwerktypen wurden z.T. mit sehr unterschiedlichen Motivationen und Zielsetzungen entwickelt. Ein Teil der Netze wurde bspw. durch physikalische Modelle inspiriert. Andere Netzwerke haben eher biologische Grundlagen. Häufig war bei der Entwicklung des Netzwerks eine befriedigende Antwort nach einer geeigneten Lernregel entscheidend. Ein wichtiger Gesichtspunkt ist jeweils der lokale Charakter einer Lernregel. Ein Neuron sollte nur aufgrund der ihm tatsächlich zur Verfügung stehenden Information (Eingaben, eigene Aktivität und Ausgabe, evtl. lokal berechneter Fehler) seine Gewichte anpassen. Nur ein solches Modell dient dem Verständnis der verteilten, parallelen Verarbeitung von Daten im Gehirn und damit den biologischen Vorbild. 191 Spezielle Algorithmen (SAl) 4.3.2 Netzwerktypen Verbindet man mehrere Neuronen miteinander, dann erhält man ein Neuronales Netz. Ein Neuronales Netz ist ein Paar (N, V) mit einer Menge N von Neuronen und einer Menge V von Verbindungen. Es umfasst den Aufbau eines gewichteten Graphen, für die folgende Einschränkungen und Zusätze gelten: 1. Die Knoten des Graphen sind die Neuronen. 2. Die Kanten sind die Verbindungen. 3. Jedes Neuron kann eine beliebige Menge von Verbindungen empfangen, über die das Neuron seine Eingaben erhält. 4. Jedes Neuron kann genau eine Ausgabe über eine beliebige Menge von Verbindungen aussenden. 5. Das Neuronale Netz (NN) erhält aus Verbindungen, die der „Außenwelt“ entspringen, Eingaben und gibt seine Ausgaben über in der „Außenwelt“ endende Verbindungen ab. x1 x2 x3 xn Eingabevektoren …………… …………… ....................... ......... Eingabeschicht (Schicht 0) 1. verborgene Schicht (h-2). verborgene Schicht Ausgabeschicht (Schicht h) Abb. 4.3-2: Darstellung eines „h“ Schichten umfassenden NN mit einer Eingabeschicht, „h-2“ verborgenen Schichten und einer Ausgabeschicht (ohne Rückkopplungen) Die Verbindungsstruktur (Topologie) kann man in Form einer Matrix beschreiben. Zeilen und Spalten identifiziert man mit den Neuronen (Units, Zellen), in den Kreuzungspunkt schreibt man das Gewicht der Verbindung. Für die Matrix gilt dann bspw. wij = 0 : Keine Verbindung von Neuron „i“ zu Neuron „j“ wij < 0 : hemmende Verbindung der Stärke wij wij > 0 : anregende Verbindung der Stärke wij Netze ohne Rückkopplung: „feedforward“-Netze 1. Ebenenweise verbundene Feedforward-Netze 192 Spezielle Algorithmen (SAl) Die Netze sind in mehreren Ebenen (Schichten) eingeteilt. Es gibt nur Verbindungen von einer Schicht zur nächsten 114. 2. Feedforward-Netze mit „shortcut connections“ Bei diesen Netzen gibt es neben Verbindungen zwischen aufeinanderfolgenden Ebenen auch solche, die Ebenen überspringen, d.h. die direkt von einem Neuron der Ebene „k“ zu einem Neuron in Ebene „k+i“ mit „i > 1“ verlaufen. „shortcut“ Abb. 4.3-3: Feedforward-Netz mit „shortcut“ Netze mit Rückkopplungen: „rekurrente Netze“ Netze mit Rückkopplungen unterteilt man in die Klasse der 1. Netze mit direkten Rückkopplungen („direct feedback“) Die Netze ermöglichen es, dass ein Neuron seine eigene Aktivierung über eine Verbindung von seinem Ausgang zu seinem Eingang verstärkt oder abschwächt. Diese Verbindungen bewirken oft, dass Neuronen die Grenzzustände ihrer Aktivierungen annehmen, weil sie sich selbst verstärken oder hemmen. 2. Netze mit indirekten Rückkopplungen („indirect feedback“) Bei diesen Netzen gibt es ein Rückkopplung von Neuronen höherer Ebenen zu Neuronen niederer Ebenen. Diese Art der Rückkopplung macht auf bestimmte Bereiche der Eingabeneuronen bzw. auf bestimmte Eingabemerkmale aufmerksam. 3. Netze mit Rückkopplungen innerhalb einer Schicht („lateral feedback“) Netze mit Rückkopplungen innerhalb derselben Schicht werden für Aufgaben eingesetzt, bei denen nur ein Neuron in einer Gruppe von Neuronen aktiv werden soll. Jedes Neuron erhält dann hemmende Verbindungen zu anderen Neuronen und auch noch eine aktivierende direkte Rückkopplung zu sich selbst. Das Neuron mit der stärksten Verbindung hemmt dann die anderen Neuronen 4. vollständig verbundene Netze. Vollständig verbundene Netze haben Verbindungen zwischen alle Neuronen. Sie sind insbesondere durch die Hopfield-Netze weit verbreitet worden. Netze mit Rückkopplungen werden auch für die Modellierung von Zeitabhängigkeiten bei Daten (z.B. die Struktur einer Schwingung) eingesetzt. 114 Vgl. 4.3-2 193 Spezielle Algorithmen (SAl) 4.4 Das Perzeptron 4.4.1 Singlelayer-Perzeptron (SLP) Das Perzeptron-Modell 115 wurde 1958 von Frank Rosenblatt entwickelt. Das Modell kann die Klassifikation von Mustern erlernen. Biologische Grundlagen Das Perzeptron ist die Nachbildung eines biologischen Neurons 116. Funktionsweise Aus den Kentnissen über das biologische Neuron kann man ein mathematisches Modell für das Perzeptron ableiten. So ist die Ausgabe "o" bestimmt durch N ⎧ 1 , falls net wi ⋅ ii > θ = ∑ ⎪ i =1 ⎪ o=⎨ ⎪ 0, anderenfalls ⎪ ⎩ Zur vereinfachten Darstellung der Perzeptrone kann die Schwelle θ auf Null normiert werden. Hierfür wird ein zusätzlicher Eingang, an dem immer eine Eins anliegt erstellt und dessen Gewicht auf das Negative des ursprünglichen Schwellenwerts gesetzt. Man spricht dann von einem (sogenannten) Bias. i1 i2 iN ..... o Abb.: Singlelayer-Perzeptron (SLP) 115 http://de.wikipedia.org/wiki/Perzeptron 116 vgl. 4.1.1, Aufbau einer Nervenzelle 194 Spezielle Algorithmen (SAl) Jede Eingangsinformation wird mit dem zugehörigen Gewicht multipliziert, das den unterschiedlichen Übertragungswiderstand der Synapsen simuliert. Nach der Summenbildung aller Eingangswerte entscheidet die Aktivierungsfunktion F über die Höhe des Ausgangssignals, das zu den angeschlossenen Neuronen übertragen werden soll. Lernen mit der Delta-Regel Das Perzeptron setzt sich aus Gewichten der Verbindungen, dem Addierer und aus einen einstellbaren Schwellwert (Bias) zusammen. Lernen bedeutet: Ändern der Gewichte und Einstellen des Schwellwerts (Bias). Häufig wird der Schwellwert (Bias) 117 über ein peripheres Gewicht (w0) berücksichtigt. Falls bei einem Muster p und einer Einheit i die Differenz zwischen Zielwert tip und korrespondierendem Ausgabewert oip vom Wert 0 ist, ist das Lernziel erreicht. Andernfalls werden Schwellwert und Verbindungsgewichte wij angepaßt. Das Training des Schwellwerts läßt sich durch folgende Formel beschreiben: Biasi = Biasi + ε ⋅ ( tip − oip ) Das Training der Gewichte erfolgt über die Formel: wij = wij + ε ⋅ ( tip − oip ) Ein Perzeptron berechnet 0 oder 1 aus den vorliegenden Eingaben. Mehrere berechnen komplexe Funktionen 118. i1 i2 ....... iN 1 Abb.4.4-2: Perzeptron mit N Eingangssignalen und drei Ausgangssignalen Da jedes Perzeptron unabhängig ist, können sie getrennt trainiert werden. Die Schwelle θ kann so auf Null normiert werden. Es wird, wie bereits angegeben, ein zusätzlicher Eingang, an dem immer eine Eins anliegt, erstellt und dessen Genauigkeit auf das Negative des ursprünglichen Schwellenwerts gesetzt. 118 Vgl. 117 195 Spezielle Algorithmen (SAl) Lineare Trennbarkeit i2 i1 Abb. : Lineare Trennnbarkeit zur Mustererkennung Gegeben sind die Ausprägungen von 2 Eingabemustern. Das Perzeptron soll 1 ausgeben, falls es die Eingabe zu den weißen Punkten zuordnet bzw. 0, falls es die Eingabe zu den schwarzen Punkten zuordnet. net = w0 + w1 ⋅ i1 + w2 ⋅ i2 ⎧1, falls net > 0 o=⎨ ⎩0, falls net < 0 i2 = − w1 w ⋅ i1 − 0 w2 w2 Die Gleichung trennt die beiden Muster (Trennfläche). Im Fall umfangreicher Eingaben, erweitert sich die Trennfläche zur Hyperfläche: N ∑ w ⋅i i i =0 i =1 Funktionen, für die eine solche Hyperfläche existiert, werden "linear trennbar" genannt. Das Problem des Lernens reduziert sich damit auf das Problem, eine solche Trennfläche zu finden. Lineare Trennbarkeit bedeutet also: Zwei Mengen P und N von Punkten in einem ndimensionalen Eingaberaum sind linear trennbar, falls n+1 reelle Zahlen θ , w1, w2, ... n r ,wn existieren, so dass für jeden Punkt x ∈ P gilt: ∑ wi xi ≥ θ und für jeden Punkt i =1 r x ∈ N gilt: n ∑w x i =1 i i <θ . 196 Spezielle Algorithmen (SAl) Trennfunktion w1 x1 + w2 x 2 = θ entspricht einer Geradengleichung: w θ x2 = − 1 x1 . Die Trenngerade steht senkrecht zum Gewichsvektor. w2 w2 Die x2 r w r r h (Projektion von x auf w r x d x1 Abb.: Der Vektor der Trenngeraden ist mit x 2 = 0 ⇒ x1 = θ w1 und x1 = 0 ⇒ x 2 = θ w2 : ⎛ θ ⎞ ⎟ ⎛θ ⎞ ⎛ 0 ⎞ ⎜ ⎜ ⎟ ⎜ θ ⎟ ⎜ w1 ⎟ ⎜ w1 ⎟ − ⎜ ⎟ = ⎜ θ ⎟ ⎜ 0 ⎟ ⎝ ⎠ ⎝ w2 ⎠ ⎜ − w ⎟ 2 ⎠ ⎝ ⎛θ ⎞ ⎛θ ⎞ r r Das Skalarprodukt des Vektors w mit der Trenngeraden ist ⎜⎜ ⎟⎟ w1 − ⎜⎜ ⎟⎟ w2 = 0 . w ⎝ w1 ⎠ ⎝ w2 ⎠ ist immer die Normale zur Hyperebene. Für einen beliebigen Punkt auf der r r r r θ Trennfläche gilt (h=d): θ = wT ⋅ x = w x cos ϕ und d = . w Die lineare Trennfunktion ist die (n-1)-dimensionale Hyperebene des ndimensionalen Eingaberaums. Eine Perzeptron-Definition mit spezieller Eingabekomponente x0 und Gewicht w0 = −θ bedeutet, dass die Trennfunktion r r durch wT ⋅ x = 0 gegeben ist. Diese geht wegen d = 0 durch den Ursprung des um eine Dimension erhöhten Eingaberaums. Einschichtige Perzeptrone (SLP) und Bool’sche Funktionen. Da jede Boole’sche Funktion als disjunktive und konjunktive Normalform geschrieben werden kann, ist es möglich, mit einem einschichtigen Perzeptron (Singlelayer-Perceptron, SLP) logische Funktionen zu realisieren. So ist es offensichtlich, daß sich die Bool’sche Funktion AND und OR durch einen linearen Klassifikator und damit einschichtigen Perzeptron lösen lassen x1 0 0 1 1 x2 0 1 0 1 AND 0 0 0 1 OR 0 1 1 1 197 Spezielle Algorithmen (SAl) 1 (1,1) x1 w1 + y w2 OR AND x2 b (Schwellwert) 0 0.5 1 1.5 Hyperebenen: AND x1 + x 2 − 3 / 2 = 0 , d = 3 / 2 ⋅= 3 ⋅ 2 4 OR x1 + x 2 − 1 / 2 = 0 , d = 1 / 2 =1 ⋅ 2 4 2 Neuronale Netze können die Trenngeraden durch Trainieren ermitteln 119. Für das XOR-Problem (vgl. Wertetafel) existiert bspw. keine derartige Trennfäche (Gerade). Eingabe 0 0 1 1 Eingabe 0 1 0 1 2 Ausgabe 0 1 1 0 Die folgenden 4 Ungleichungen 0 ⋅ w1 + 0 ⋅ w2 < Θ 0 ⋅ w1 + 1⋅ w2 > Θ 1⋅ w1 + 0 ⋅ w2 > Θ 1⋅ w1 + 1⋅ w2 < Θ zeigen: w1 und w2 können allein für sich allein betrachtet nicht größer sein als ihre Summe, d.h. Das XOR-Problem ist mit einem Perzeptron nicht lösbar. Das zeigt die folgende grafische Darstellung: i2 (1,1) 1 1 0 0 1 1 i1 XOR ist durch eine zusätzliche (verborgene) Schicht lösbar: 119 vgl. nnperz1.m im Verzeichnis lehrbrief2 198 Spezielle Algorithmen (SAl) Eingabe 0 0 1 1 Eingabe 0 1 0 1 Eingabe 0 0 0 1 Ausgabe 0 1 1 0 Die ersten beiden Spalten entsprechen der Eingabe beim XOR, die 3. Spalte umfaßt eine logisches UND der Eingabe zum XOR. 0 (1,1,1) 1 (0,1,0) (0,0,0) 0 (1,0,0) 1 Abb. 2.3-5: Schwellenwertebene für das XOR-Problem: Die Überführung des zweidimensionalen in ein dreidimensionales Problem zeigt einen Lösungsweg für das XOR-Problem. Allerdings ist ein 3. Eingabeelement (in einer zusätzlichen verborgenen Schicht) erforderlich. I1 i2 1 1 w1=1 w2= 1 I3 θ = 1.5 w3 =-2 o θ = 0.5 Abb. : Einfaches XOR-Netzwerk mit einem verdeckten Element Die zusätzliche (verborgene) Schicht empfängt keine externe Eingaben. Sie muß aber trainiert werden, d.h.: Es muß extra dafür eine Lernprozedur geben. Lange hat man geglaubt: Eine solche Prozedur gibt es nicht. Eine Verallgemeinerung der DeltaRegel 120 zeigt jedoch eine Lösung des Problems. 120 vgl. 4.4 199 Spezielle Algorithmen (SAl) Satz der linearen Separierbarkeit Allgemein lassen sich die Eingabemöglichkeiten n vieler Eingabeneuronen in einem n-dimensionalen Würfel darstellen, die von einem Singlelayer-Perzeptron (SLP) durch eine (n-1)- dimensionale Hyperebene separiert wird. Nur Mengen, die durch eine solche Hyperebene trennbar (linear separierbar) sind, kann ein SLP klassifizieren. Applet: Perzeptron-Netz zur Rekodierung von Zahlen 1. Aufgabenstellung Ein binär angegebener Wert einer Zahl ist in die entsprechende dezimale Form umzuwandeln. Der dezimale Wert erhält soviele Einsen in aufsteigender Folge zugeordnet, wie es dem binären Wert entspricht. Die Eingabe einer Zahl von 0..9, z.B. 8 führt zur Ausgabe von: 1 1 1 1 1 1 1 1 0. Ein binär angegebener Wert einer Zahl ist in die entsprechende dezimale Form umzuwandeln. Der dezimale Wert erhält soviele Einsen in aufsteigender Folge zugeordnet, wie es dem binären Wert entspricht. 2. Lösungsvorschlag Es werden 4 Neuronen in der Eingabeschicht zur Aufnahme der Binärzahlen und 9 Neuronen der Ausgabeschicht zur Ausgabe der Dezimalzahlen benötigt. Aus der Aufgabenstellung ergeben sich folgende Trainingsmusterpaare: Zahl 0 1 2 3 4 5 6 7 8 9 Eingabe 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 Ziel 000000000 100000000 110000000 111000000 111100000 111110000 111111000 111111100 111111110 111111111 3. Das Applet http://fbim.fh-regensburg.de/~saj39122/vhb/NN-Script/script/gen/Applets/fi2/Perz.html 200 Spezielle Algorithmen (SAl) 4.4.2 Multilayer-Perzeptron (MLP) Aufbau Perzeptrone zählen lediglich zu den linearen Klassifikatoren. Das reicht 121 oft nicht aus. Man ist daher übergegangen, mehrere Perzeptrone miteinander zu verschachten. Man erhält Multilayer Perzeptrone (MLP), die einen wesentlich mächtigeren Klassifikator darstellen. X2 X2 X1 Abb. 4.4-9: Eine mögliche Aufteilung einer Punktmenge mit einen einzelnen Perzeptron X1 Abb. 4.4-10: Eine mögliche Aufteilung einer Punktmenge mit einem MLP Ein MLP erstreckt sich über mindestens 3 Schichten 122: Eingabe-, Ausgabe und mindestens eine verborgene Zwischenschicht (hidden layer). Die Eingabeeinheiten leiten lediglich die Daten an die nächste Schicht weiter. In den Zwischenschichten findet die eigentliche Verarbeitung / Klassifizierung statt. Die Eingabe- und jede Zwischenschicht enthält zusätzlich zu den normalen Perzeptronen noch ein Bias, das immer eine "1" an die nächste Schicht weitergibt und damit den Schwellenwert auf Null normiert. Beim MLP handelt es sich um vollverschaltete Netzwerke, d.h. die Ausgänge der Perzeptrone einer Schicht werden alle mit jedem Perzeptron der nächsten Schicht verknüpft und damit ein vorwärtsgerichtetes Netz konstruiert. Die Spezifizierung eines Netzwerks erfolgt über die Parameter: Anzahl der Schichten Anzahl der Perzeptrone der einzelnen Schichten in der Schicht verwendete Aktivierungsfunktion Hier stehen bspw. lineare Funktionen, Tangenshyperbolicus, Fermifunktion usw. zur Auswahl. 121 122 vgl. XOR-Problem oft auch Layer genannt. 201 Spezielle Algorithmen (SAl) Eingabevektor 1 ∑ ∑ ∑ 1 ∑ ∑ 1 ∑ Ausgabevektor Abb. Beispiel für den Aufbau eines MLP. Das vorstehende Netz stellt ein vierschichtiges oder dreistufiges Netz dar. Bei "Stufennomenklatur" wird die Eingabeschicht nicht als Stufe mitgezählt, weil diese nur die Eingaben weiterleitet. Die Anzahl der Neuronen wird von der Eingabeschicht an hintereinander geschrieben. Das vorstehende Netz ist bspw. ein 4 – 3 – 2 – 1 –MLP. Man kann desweiteren hinter jede Nummer eine Zahl schreiben, die angibt, welche Kennlinie die Neuronen der jeweiligen Schicht haben. Für das vorstehende Netz bspw. 4L 1233T 124-2T-1T –MLP 125. Lösung des XOR-Problems durch ein Multilayer-Perzeptron Das XOR-Problem ist mit einem Perzeptron nicht lösbar. Das zeigt die folgende grafische Darstellung: 123 Linear Tangenshyperboloicus 125 Der erste Buchstabe der Aktivierungsfunktion ist Kennbuchstabe 124 202 Spezielle Algorithmen (SAl) i2 (1,1) 1 1 0 0 1 1 i1 Eine Lösung ergibt sich durch Kombination 2er Trennlinien. Dies führt auf ein ein mehrlagiges Perzeptron. -1/2 0 (1,1) 1 x1 + y1 + + -3/2 y2 OR NAND x2 y 3/2 0 0.5 1 1.5 XOR x1 x2 0 0 0 1 Erste Schicht y2 y1 0 1 1 1 1 1 1 0 Zweite Schicht XOR 0 1 1 0 In der ersten Schicht eines Perzeptrons reagiert ein Neuron mit 0 oder 1 in Abhängigkeit davon, in welcher Hälfte der von der Hyperfläche erzeugten beiden Halbräume sich der Eingangsvektor befindet. Jedes weitere Neuron erzeugt eine zusätzliche Trennebene. Die erste Schicht eines Perzeptrons bildet somit den reelwertigen Eingangsraum auf die Eckpunkte eines Hyperwürfels ab. Die sich schneidenden Trennebenen bilden linear begrenzte (konvexe Gebiete), sog. Polyeder. Jedes Gebiet korrespondiert zu einem Exkpunkt des Hyperwürfels. Nach der Abbildung der ersten Schicht ergibt sich ein linear trennbares Problem. 203 Spezielle Algorithmen (SAl) 0 (1,1) y2 1 1 1 y1 (0,0) (1,0) Abb. Abtrennen vom Eckpunkt des Hyperwürfels − y = ( x1 ∨ x 2 ) ∧ ( x1 ∧ x 2 ) = y1 ∧ y 2 Das 3-Lagen-Perzeptron mit Schwellwertfunktion. Mit einem 3-lagigen MLP lassen sich beliebig begrenzte Cluster in Merkmalsräumen klassifizien: 1. Schicht: sich schneidende Hyperebenen bilden konvexe Polyeder. Diese werden auf die Eckpunkte eines Hyperwürfels abgebildet. 2. Schicht Durch Abtrenneb von Eckpunkten des Hyperwürfels 126 werden konvexe Polyeder durch Schnittmengenbildung von Halbräumen selektiert (AND). 3. Schicht: Beliebig linear begrenzte Gebiete entstehen durch Vereinigung von konvexen Gebieten. (OR) Abb.: Beliebig konvexe Gebiete universaler Funktionsapproximator MLPs sind universale Funktionsapproximatoren, d.h. Sie können beliebige Muster und Funktionen darstellen. Wieviele Neuronen nun für ein bestimmtes Problem benötigt werden ist nicht klar. Gibt man einem neuronalen Netz zu viele Neuronen, lernt es sehr schnell auswendig und generalisiert nicht. Hat es zu wenige, wird das 126 vgl. Abb.204 Spezielle Algorithmen (SAl) Problem nicht ordentlich gelöst. Die Anzahl der Neuronen in der verborgenen Schicht ist maßgeblich für die Mächtigkeit eines MLPs. Neuronale Netze mit mehr als einer Hiddenschicht sind leistungsfähiger als die mit einer Schicht, lernen aber langsamer. Das ein neuronales Netz eine Funktion darstellen kann, heißt noch lange nicht, dass es sie auch lernen kann. Es kann tiefe Täler in der Fehleroberfläche geben, in die man aber nicht hineinspringen kann. 4.5 Support Vector Machines 127 4.5.1 Linear separierbare Probleme Lineare Separierbarkeit Ein einzelnes Perzeptron teilt den Eingangsraum mit einer Ebene in zwei Teile. Im zweidimesionalen Raum sind diese Ebenen Geraden, im dreidimensionalen Raum Flächen usw. Abb.: Lineare Trennbarkeit in zweidimensionalen Raum Falls die Ausgabe des Perzeptrons nicht der gewünschten Ausgabe entspricht, kann man die Gewichte durch Aufaddieren von Δwi lernen lassen Im zweidimensionale Fall hat die separierende Gerade folgende Formel: w1 x1 + w2 x 2 + w0 = 0 . Daraus ergibt sich die folgende normale Geradengleichung (umgestellt nach x 2 ) x2 = − w w1 x1 − 0 w2 w2 Linear separierbare Probleme lassen sich offensichtlich durch eine Gerade (Hyperebene) in zwei Klassen teilen. Eine solche Hyperebene lässt sich definieren ⎧→ ⎛ → → ⎞ ⎫ durch ⎨ z | ⎜ w⋅ z ⎟ + b = 0⎬ . Die eine Halbebene ist dann definiert durch ⎠ ⎩ ⎝ ⎭ → → → ⎧ ⎛ ⎫ ⎧→ ⎛ → → ⎞ ⎫ ⎞ ⎨ z | ⎜ w⋅ z ⎟ + b > 0⎬ , die andere durch ⎨ z | ⎜ w⋅ z ⎟ + b < 0⎬ . Klassifikationsmechanismen ⎠ ⎠ ⎩ ⎝ ⎭ ⎩ ⎝ ⎭ dieser Art werden auch Perceptrons genannt. Für viele separierbare Probleme kann eine Hyperebene auf verschiedene Art und Weise erstellt werden, z.B.: 127 http://de.wikipedia.org/wiki/Support_Vector_Machine 205 Spezielle Algorithmen (SAl) Abb.: Viele Ebenen separieren nicht gerade klug. Legt man die Ebene an einen Rand, dann können leicht Muster auftauchen, die die Ebenen überschreiten. Deshalb versucht man eine Ebene mit einem möglichst breiten Korridor drumherum auszustatten. Linear separierbare Probleme mit Korridor Im vorliegenden Fall wurden 2 Klassen Ω1 und Ω 2 betrachtet, die sich linear trennen lassen: ⎧− 1 ⇔ x i ∈ Ω 1 yi = ⎨ ⎩ 1 ⇔ xi ∈ Ω2 Gesucht ist aber jetzt die optimale Trennfunktion, die die Summe der Distanzen zu rr den beiden Klassen maximiert. Die trennende Hyperebene wx + b = 0 ist (zu diesem Zweck) definiert durch → - w (Vektor der senkrecht auf der Hyperebene steht - b (Skalar, der mit dem Abstand der Hyperebene vom Ursprung des Koordinatensystems korrespondiert. Der Abstand ist gleich b → → , wobei w = 2 ∑w i =1 w i 2 die Länge des Vektors ist. Der Abstand eines Punkts x von der Hyperebene ist gegeben durch r r w⋅ x + b r . d ( w ,b ) = → w r rr Die Parameter w und b der Trennfunktion wx + b = 0 sind nicht eindeutig bestimmt. r r r r r r r Für c ≠ 0 gilt: {x | w, x + b = 0} = {x | cx , x + cb = 0}. (cw, cb ) beschreibt die gleiche r Hyperebene wie (w, b ) , d.h. die Hyperebene läßt sich nicht eindeutig beschreiben. 206 Spezielle Algorithmen (SAl) r r n Deshalb Skalierung (w, b ) relativ zu den Trainingsdaten {xi , y i }i =1 , so daß r r r v min wT xi + b = min wT xi + b = 1 128 xi ∈Ω1 xi ∈Ω 2 rr Das ist gleichbedeutend mit y i ⋅ (wxi + b ) ≥ 1 Falls die letzten beiden Bedingungen gelten, liegt die Hyperebene in kanonischer Form vor. 1 → w ⎧→ ⎛ → → ⎞ ⎫ ⎨ z | ⎜ w⋅ z ⎟ + b = −1⎬ ⎠ ⎩ ⎝ ⎭ ⎧→ ⎛ → → ⎞ ⎫ ⎨ z | ⎜ w⋅ z ⎟ + b = 1⎬ ⎠ ⎩ ⎝ ⎭ ⎧→ ⎛ → → ⎞ ⎫ ⎨ z | ⎜ w⋅ z ⎟ + b = 0⎬ ⎠ ⎩ ⎝ ⎭ Der Korridor soll möglichst breit werden Abb.: Hyperebene in kanonischer Form mit möglichst breitem Korridor (margin) → w und b können so eingestellt werden, dass alle Punkte außerhalb des Korridors → ⎛→ → ⎞ liegen und die Support Vectors genau auf der Grenze: min ⎜ w⋅ xi ⎟ + b = 1 . Falls z 1 i =1...l ⎝ ⎠ → und z 2 die Support Vectors sind, dann gilt: - 128 ⎛→ →⎞ ⎜ w⋅ z1 ⎟ + b = +1 ⎠ ⎝ ⎛→ → ⎞ ⎜ w⋅ z 2 ⎟ + b = −1 ⎝ ⎠ → → → ⎛ ⎛ ⎞⎞ ⎜ w⋅ ⎜ z 1 − z 2 ⎟ ⎟ = +2 ⎠⎠ ⎝ ⎝ kanonische Form der Hyperebene 207 Spezielle Algorithmen (SAl) Normieren führt zu → w ⎛→ →⎞ 2 ⎜ z1 − z 2 ⎟ = → → ⎠ w w ⎝ → w ⎛→ →⎞ 2 Daraus erhält man ein passendes w : → ⎜ z1 − z 2 ⎟ − → = 0 ⎠ w w ⎝ → Die zugehörige Entscheidungsfunktion ist: f → w ,b ⎛⎛ → → ⎞ ⎞ = sgn ⎜ ⎜ w⋅ xi ⎟ − b ⎟ = y i ⎠ ⎠ ⎝⎝ → Der Abstand von z 2 zur Hyperebene beträgt: d ( wr ,b ) = r r w ⋅ z2 + b − 1 → . Durch Einsetzen w rr 2 von wz 2 = −b − 1 erhält man → w → Der Abstand von z1 zur Hyperbene beträgt: d ( wr ,b ) = r r w ⋅ z1 + b + 1 → . Durch Einsetzen von w rr 2 wz1 = −b + 1 erhält man → w → → Somit gilt: m arg in = d ( wr ,b +1) ( z 1 ) = d ( wr ,b −1) ( z 2 ) = 2 → w Damit ist das Auffinden der optimalen Hyperebene, welche die Größe margin maximiert, äquivalent zur Lösung des folgenden Problems: → Finde diejenigen Parameter w und b , die - m arg in = 2 → maximieren unter der Bedingung w →→ - y i ( w x i + b) ≥ 1 (i = 1, 2, ... ,N) Das Problem lässt sich mit Hilfe verschiedener Optimierungsmethoden lösen, z.B. durch quadratische Optimierung. Den Rand (margin) zu maximieren ist identisch mit der Forderung: "die → 1 r 1 r 2 Ebenenvektorlänge w zu minimieren bzw. w oder w . 2 2 208 Spezielle Algorithmen (SAl) r w 2 = w1 + w2 + ... + wn 2 2 2 2 = w1 + w2 + ... + wn 2 2 2 r r 1 r 2 1 r r w ⇔ w ⋅ w , weil w ⋅ w = w1 ⋅ w1 + w2 ⋅ w2 + ... + wn⋅ wn ist. 2 2 Somit ist das folgende Optimierungsproblen zu lösen: r r Gegeben ist ein linear trennbare Trainingsmenge (( x1 , y1 ),..., ( xl , y l )) 1 r 2 w unter den Nebenbedingungen 2 rr rr wxi + b ≥ +1 für y i = +1 und wxi + b ≤ −1 für y i = −1 r r bzw. mit der Entscheidungsfunktion y i ⋅ ( w, xi + b ) ≥ 1 Minimiere Das Problem ist ein quadratisches Optimierungsproblem 129 mit linearen Einschränkugen (constraints). Das Problem kann gelöst werden unter Einsatz vom Lagrange-Multiplikatoren und dem Dualitätstheorem zu Optimierungsproblemen. Lagrange 2 l ⎛ ⎛⎛ →T → ⎞ ⎞ ⎞ ⎛→ →⎞ 1 → Die Lagrange–Funktion hat die Form: L⎜ w, b, α ⎟ = w − ∑ α i ⎜ y i ⎜ ⎜⎜ w ⋅ xi ⎟⎟ + b ⎟ − 1⎟ . ⎟ ⎟ ⎜ ⎜⎝ ⎝ ⎠ 2 i =1 ⎠ ⎠ ⎠ ⎝ ⎝ α i ≥ 0 sind die Langrange-Multiplikatoren. r r Die Lagrange-Methode besagt, daß ( w, b, α ) „geometrisch ausgedrückt“ ein r r Satellpunkt der Funktion L( w, b, α ) darstellt. r ⎛ → →⎞ L⎜ w,b,α ⎟ wird minimiert für w und b und maximiert für die α i . 130 ⎠ ⎝ r r r l ∂ L ( w , b , α ) = w − ∑ y iα i x i = 0 r ∂w i =1 l r r ∂ L( w, b,α ) = ∑ yiα i = 0 ∂b i =1 Daraus erhält man: l r r w = ∑ y iα i x i i =1 l 0 = ∑ y iα i i =1 ⎛ → →⎞ L⎜ w,b,α ⎟ bekommt man das sog. duale Problem: ⎝ ⎠ 2 l → → → rr ⎛ ⎞ 1 LD ⎜ w, b,α ⎟ = w − ∑ α i ( y i ( wxi + b ) − 1) ⎝ ⎠ 2 i =1 l l l l r r r rv 1 1 l = ∑ α iα j y i y j x i x j − ∑ α iα j y i y j + ∑ α i = ∑ α i − ∑ α iα j y i y j xi x j = W (α ) 2 i , j =1 2 i , j =1 i , j =1 i =1 i =1 Durch 129 130 Einsetzen in Quadratische Optimierungsprobleme sind lösbar mit einem Aufwand von O(n3) die alle nichtnegative reelle Zahlen sein müssen 209 Spezielle Algorithmen (SAl) l l ⎛ →T → ⎞ 1 l ⎛→⎞ bzw. W ⎜ α ⎟ = ∑ α i − ⋅ ∑ α iα j y i y j ⎜ xi ⋅ x j ⎟ mit α i ≥ 0 und 0 = ∑ y iα i ⎟ ⎜ 2 i , j =1 ⎝ ⎠ i =1 i =1 ⎠ ⎝ Das Ziel besteht im Auffinden Optimierungsproblem lösen. ⎛→⎞ ⎝ ⎠ Maximiere W ⎜ α ⎟ = l 1 2 l ⎛ → Parameter → ⎞ T ∑ α i − ⋅ ∑ α iα j yi y j ⎜⎜ xi ⋅ x j ⎟⎟ i =1 i , j =1 αi ≥ 0 mit der ⎠ ⎝ r α*, die das folgende l unter den Randbedingungen ∑yα i =1 i i =0 ⎛ l ⎞ ⎛ →T → ⎞ ⎛→⎞ ⎜ Die Decision Function wäre dann: f ⎜ x ⎟ = sgn ∑ α i y i ⎜⎜ xi ⋅ x j ⎟⎟ + b ⎟ . ⎜ i =1 ⎟ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠ r Hat man die optimalen Werte für W (α ) ermittelt, so ergeben sich die Gewichte durch r r w = ∑ α i ⋅ yi x . Das Bias b wird folgendermaßen ermittelt: Man wählt irgendeinen Support-Vektor r v und setzt ein in wT ⋅ x + b = 1 . Daraus ergibt sich b . r Die Trainingsvektoren xi gehen in die Formel nur in der Form parametrisierter r r Skalarprodukte xiT ⋅ x j ein. Support Vectors r r In einem Sattelpunkt ( w, b, α ) muß notwendigerweise (für alle i) die Kuhn-KarushTucker Bedingung gelten: r* r* r l ∂ * L ( w , b , α ) = w − ∑ y iα i x i = 0 r ∂w i =1 l r r ∂ * L ( w * , b * , α * ) = ∑ y iα i = 0 ∂b i =1 r* r * y i w ⋅ xi + b − 1 ≥ 0 ( ) αi ≥ 0 ( (r r ) ) α i y i w* ⋅ x + b * − 1 = 0 Die Zusatzterme der Lagrange-Funktion, die die Randbedingungen repräsentieren, sind in einem Sattelpunkt gleich 0. Die Kuhn-Tucker Bedingungen sind notwendig und ausreichend zur Bestimmung für r eine Lösung von w* , b,α . Die Lösung des primalen bzw. dualen Optimierungsproblems ist äquivalent zur Bestimmung einer Lösung für die KuhnTucker Bedingungen. Für die SVM folgt daraus, dass für einen gegebenen Punkt der r r Trainingsmenge xi der Lagrange-Multiplikator entweder α i Null ist oder, falls r ungleich Null, xi in dem m arg in -Hyperebenen-Bereich (H1 oder H2)liegt. r r H1: w ⋅ xi + b = +1 210 Spezielle Algorithmen (SAl) r r H2: w ⋅ xi + b = −1 Die Vekoren, die auf H1 und H2 liegen sind die Support Vectors. Sie sind die l r *r einzigen Punkte, die den optimalen Gewichtsvektor bestimmen: w* = ∑ y iα i xi . i =1 rr Diese Formel kann eingesetzt werden in y i ( wxi + b ) − 1 ≥ 0 und ersetzt das dort das Ungleichheitszeichen durch das Gleichheitszeichen (liegt innerhalb des margin). b kann nun berechnet werden. Numerisch sicherer ist allerdings die Bestimmung eines mittleren Werts: b = * ( ) ( r r r r max yi = −1 w* ⋅ xi + min yi =1 w* ⋅ xi ) 2 r Nach dem Erlernen der Hyperebenen-Parameter w* und b * mit der Trainingsmenge, r können bisher noch nicht betrachtete Datenpunkte z klassifiziert werden. Die gefundene Hyperebene teilt den Raum, dem die zu klassifizierenden Daten r r r r angehören in 2 Bereiche: w* ⋅ x + b * > 0 und w* ⋅ x + b * < 0 . Die r* * r Entscheidungsfunktion, wohin die Datenpunkte z gehören, kann mit w , b (d.h.mit r * den support vectors xi unter der Bedingung α i > 0 ) so formuliert werden: r r r r ⎛ l ⎞ ⎛ ⎞ r r r * r f ( z ,α * , b * ) = sgn( w* ⋅ z + b * ) = sgn ⎜ ∑ yiα i xi ⋅ z + b * ⎟ = sgn ⎜ ∑ yiα i* xi ⋅ z + b * ⎟ ⎝ i =1 ⎠ ⎝ i∈SV ⎠ 4.5.2 Nicht linear trennbare Klassifizierer Die Idee, die deratigen Klassifizierern zugrunde liegt, ist: Abbilden der Eingabedaten aus dem Eingaberaum in einen Raum mit höherer Dimesion, in dem die Daten linear getrennt werden können. 211 Spezielle Algorithmen (SAl) Φ X F Φ ( x) x Φ ( x) x o Φ (o) Φ (o ) Φ (o) x o o Φ ( x) Φ ( x) x Φ (o ) o Abb.: Klassifikation mit einer Feature Map Φ ( x) bildet den Merkmalraum X (input space) in einen neuen Merkmalraum r r F = {Φ ( x ) | x ∈ X } (feature space) ab, der von der Dimension P ist: x → Φ ( x) = (Φ 1 ( x),..., Φ P ( x)) . Φ 1 ( x),..., Φ P ( x) werden „Features“ genannt. Die Abbildung Hinweis: Eine andere (alternative ) Lösungsmöglichkeit für nicht linear trennbare Klassifizierer besteht in der Erweiterung der Perzeptron-Netze zu einem mehrschichtigen (MultiLayer-) Netzwerk 131. Feature Space In hohen Dimensionen kann man einfacher trennen. seperating hyperplane Abb.: Falls das Problem nicht linear trennbar ist, wird das Problem in einen hochdimensionalen Feature-Space eingebettet: Φ : x ∈ X → Φ( x) ∈ F Φ (feature map) ist eine Abbildung von einem N-dimensionalen Raum in einen Mv dimensionalen Raum, z.B. Φ( x ) = x12 , 2 x1 x 2 , x 22 . Die Dimension M des Feature ( 131 ) vgl. 4.4.2 212 Spezielle Algorithmen (SAl) Space ist sehr hoch: M = (N + d − 1)! . d!(N − 1)! (d ist die Ordnung und N die Dimension des Eingangsraums). Bei d=5 und N=256 ist bspw. M ≈ 1010 . Das Problem ist aber, dass die großen Skalarprodukte, die nun entstehen, gar nicht berechnet werden können. Das duale Optimierungsproblem nimmt bei Abbildung in einen höher dimensionierten feature space folgende Gestalt an: l Maximiere ∑α i =1 mit i − ( ) r r 1 l l ⋅ ∑∑ α iα j yi y j Φ ( x1 )T ⋅ Φ ( x j ) unter den Randbedingungen 2 i =1 j =1 l ∑yα i =1 i i =0 αi ≥ 0 Nicht die Merkmalvektoren müssen selbst berechnet werden, sondern nur die paarweisen Skalarprodukte. Die Decision Function nimmt folgende Gestalt an: r ⎛ l ⎞ ⎛→⎞ f ⎜ x ⎟ = sgn⎜ ∑ α i yi Φ ( xi )Φ( x j ) + b ⎟ ⎝ ⎠ ⎝ i =1 ⎠ Ausweg: Statt des Skalarprodukts benutzt man eine sog. Kernelfunktion. r r r r K (xi , x j ) = Φ ( xi ) ⋅ Φ (x j ) , die sich wie ein Skalarprodukt verhält. Kernel Functions r r Definition: Ein Kernel ist eine Funktion, so daß für alle xi , x j ∈ X s r r r K ( x1 , x j ) = Φ ( x1 ) ⋅ Φ ( x j ) gilt, wobei Φ eine Abbildung von K in einen feature space r r r r (inneres Produkt) ist. Die auf K (x1 , x j ) = Φ ( xi ) T ⋅ Φ ( x j ) definierte Funktion heißt Kernel-Funktion. Kernel-Funktionen müssen die Mercer-Bedingung erfüllen: r r X ist ein endlicher Eingaberaum mit einer symmetrischen Funktion K (xi , x j ) auf X . Dann ist r r K (xi , x j ) genau dann eine Kernfunktion, wenn die Matrix K positiv semidefinit ist (, d.h. Keine negativen Eigenwerte besitzt). Mit der Kernel Funtion kann man die duale Form und die Decision Function berechnen: Maximiere Die Decision Function ist l 1 l ⎛→ →⎞ ⎛→⎞ W ⎜ α ⎟ = ∑ α i − ⋅ ∑ α i α j y i y j K ⎜ xi , x j ⎟ . 2 i , j =1 ⎠ ⎝ ⎝ ⎠ i =1 l ⎞ ⎛ r ⎛r r ⎞ f ( xi ) = sgn ⎜⎜ ∑ α i yi K ⎜ xi x j ⎟ + b ⎟⎟ ⎝ ⎠ ⎠ ⎝ i =1 Bsp.: Betrachtet wird die Einbettung Φ 2 : ℜ2 → ℜ4 mit r 2 2 Φ 2 ( x ) = Φ 2 ( x1 , x 2 ) = x1 , x1 x 2 , x 2 x1 , x 2 . r r r 2 Dann gilt: Φ 2 ( x ) T ⋅ Φ 2 ( z ) = x12 z12 + 2 x1 x 2 z1 z 2 + x 22 z 22 = ( x1 z1 + x 2 z 2 ) = k 2 ( x T ⋅ z ) mit der Funktion k 2 ( x) = x 2 . Allgemein heißt eine Funktion K : ℜ n × ℜ n → ℜ eine Kernel-Funktion, falls es einen mit einem Skalarprodukt ... versehenen Vektorraum H (Hilbert-Raum) und eine r r r r r r Einbettung Φ : ℜ n → H gibt, so daß für alle xi , x j ∈ ℜ n gilt: K (xi , x j ) = Φ ( xi , x j . ( ) 213 Spezielle Algorithmen (SAl) Das Skalarprodukt muß nicht das euklidische Skalarprodukt mit einem reellen endlichdimensionalen Merkmalsraum sein, der Merkmalsraum H muß nicht endlichdimensional sein. r r Ziel ist eine möglichst einfache Berechnung von K ( xi , x j ) . Ideal wäre bspw., wenn r r rT r K ( xi , x j ) aus xi ⋅ x j durch Anwendung einer einfachen Funktion k : ℜ × ℜ berechnet r r vT r werden könnte: K ( xi , x j ) = k ( xi ⋅ x j ) . Bsp.: Betrachtet wird die Einbettung Φ 2 : ℜ2 → ℜ4 mit r 2 2 Φ 2 ( x ) = Φ 2 ( x1 , x 2 ) = x1 , x1 x 2 , x 2 x1 , x 2 . r r r 2 Dann gilt: Φ 2 ( x ) T ⋅ Φ 2 ( z ) = x12 z12 + 2 x1 x 2 z1 z 2 + x 22 z 22 = ( x1 z1 + x 2 z 2 ) = k 2 ( x T ⋅ z ) mit der Funktion k 2 ( x) = x 2 . Das Beispiel läßt sich verallgemeinern: Ist Φ d : ℜ n → ℜ m die Abbildung, die zu r r einem Vektor x den Vektor aller Produkte von jeweils genau d Komponenten von x bildet, wobei es auf die Ordnung der d Komponenten ankommt und Komponenten auch mehrfach auftreten dürfen, so ist die zugehörige Kernel-Funktion: r r r r rT r d K d ( xi , x j ) = Φ d ( xi ), Φ d ( x j ) = xi ⋅ x j ( ) ( ) Sollen in der Einbettung verschiedene Produkte von d Komponenten, die sich nur in der Reihenfolge der Indizes ihrer Faktoren unterscheiden (also x1 x 2 und x 2 x1 ) nicht mehrfach auftreten, so muß man mit der Wurzel die Vielfachheit der Produkte r skalieren: Φ 2 ' ( x ) = Φ 2 ' ( x1 , x 2 ) = x12 , 2 x1 x 2 , x 22 . Diese Einbettung gibt dieselbe Kernel-Funktion wie oben. Will man in der Einbettung Φ ≤ d alle Produkte von maximal d Faktoren (also auch weniger als d Faktoren) berücksichtigen, wobei verschiedene Ordnungen der Indizes der Faktoren auch verschiedene solcher Produkte ergeben soll, so definiert dies die r r r r rT d rTr folgende Kernel-Funktion: K ≤ d ( xi , x j ) = Φ ≤ d ( xi ), Φ ≤ d ( x j ) = 1 + xi x j = k ≤ d xi x j . ( ) ( ) ( ) Kernel-Funktionen dieser Art heißen Polynomkernels. Das auf Kernel-Funktionen abgestimmte Optimierungsproblem kann so formuliert werden: Maximiere ∑ α i − ∑ α iα j yi y j k (xi , x j ) unter den Nebenbedingungen l l i =1 i , j =1 r r l ∑yα i =1 i i = 0 mit α i ≥ 0 Ein konvex quadratisches Problem mit einer globalen optimalen Lösung, das in polynomialer Zeit lösbar ist, wird erzeugt. Bekannte Kernel-Funktionen - - - ( r r ⎛→ → ⎞ K ⎜ xi , x j ⎟ = xi ⋅ x j + c ⎝ ⎠ 2 ⎛ → → ⎞ ⎛→ →⎞ 2 RBF K ⎜ xi , x j ⎟ = exp⎜ − x − z / (2σ )⎟ ⎜ ⎟ ⎝ ⎠ ⎝ ⎠ → → r r ⎛ ⎞ Sigmoid K ⎜ xi , x j ⎟ = tanh κ xi , x j + θ ⎝ ⎠ Polynom-Klassifikation ( ) 214 ) p Spezielle Algorithmen (SAl) Polynomialer Kernel p ist der Grad des Polynoms und c eine nicht negative Konstante (gewöhnlich vom Wert 1). ⎛⎛ x ⎞ ⎞ ⎛⎛ x ⎞ ⎞ Bsp.: Betrachte 2 Trainingsdaten ⎜⎜ ⎜⎜ 11 ⎟⎟, y1 ⎟⎟ und ⎜⎜ ⎜⎜ 21 ⎟⎟, y 2 ⎟⎟ , wobei y1 , y 2 ∈ {± 1} und ⎝ ⎝ x12 ⎠ ⎠ ⎝ ⎝ x 22 ⎠ ⎠ r r r r x1 , x 2 ∈ ℜ 2 , d.h. x1 = ( x11 , x12 ) und x 2 = ( x 21 , x 22 ) und einen polynomialen Kernel 2. Grades mit c = 1. Dann gilt: r r r r 2 2 k ( x1 , x 2 ) = (1 + x1 , x 2 ) = (1 + x11 x 21 + x12 x 22 ) = 1 + 2 x11 x 21 + 2 x12 x 22 + ( x11 x 21 ) 2 + ( x12 x 22 ) 2 + 2 x11 x 21 x12 x 22 r Mit Φ ( x1 ) = Φ (( x11 , x12 )) → 1, 2 x11 + 2 x12 , x112 + x122 , 2 x11 , x12 r r r r Φ ( x1 ), Φ ( x 2 ) = k ( x1 , x 2 ) ( ) kann man folgern: Verallgemeinert läßt sich daraus nochmals folgern: - Der Raum muß nicht bekannt sein. Die Kern-Funktion als Maß für die Ähnlichkeit ist für alle Berechnungen ausreichend. Die Lösung des optimalen Programms ergibt sich durch Einsetzen des ursprünglichen Skalarprodukts durch die Kern-Funktion Sigmoid Ein Kernel, der aus der Sigmoid-Funktion gebidet wird, genügt der Mercer-Bedingung für bestimmte Werte von κ und δ 132. Die Verwendung eines sigmoiden Kernels im Rahmen der Support Vector Machine kann mit einem zwei Schichten umfassenden r Neuronalen Netz verglichen werden. In solchen Netzwerken wird z durch die erste r r r Schicht in den Vektor F = ( F1 ,..., Fn ) mit Fi = tanh (κ ( xi ⋅ z ) − δ ) ( i = 1,..., N )abgebildet. r Die Dimension von F ist die Anzahl der sog. Hidden Units. In der zweiten Schicht r wird die gewichtete Summe der Elemente von F berechnet mit den Gewichten α i 132 experimentell von Vapnik ermittelt. 215 Spezielle Algorithmen (SAl) F1 α1 r z F2 α2 y Feature Space . +1/-1 αN . FN Abb.: Ein 2 Schichten umfassendes Neurales Netz mit N "hidden units". Die Ausgabe der ersten Schicht ist r r ⎛ N ⎞ Fi = tanh (κ ( xi ⋅ z ) − δ ) . Die Ausgabe des Netzes ist sgn ⎜ ∑ y iα i Fi − b ⎟ ⎝ i =1 ⎠ ⎛ → →2 ⎞ ⎛ → →⎞ Radial Basis Function (Gauss'scher Kernel): K ⎜ z , x ⎟ = exp⎜⎜ − x − z / 2σ 2 ⎟⎟ ⎝ ⎠ ⎝ ⎠ σ definiert die sog. Fensterbreite und kann für diverse Vektoren von unterschiedlicher Größe sein. SVMs mit Gauß-Kernel wiesen von der Verarbeitungsweise eine gewisse Ähnlichkeit mit selbstorganisierenden Netzen auf: ( ) Auf einen Eingabevektor reagieren die Neuronen der Eingabeschicht umso stärker, je näher sie r sich an x befinden. Es wird allerdings kein Gewinner ermittelt. Netze dieser Art heißen Radial Basis Function Netze 216 Spezielle Algorithmen (SAl) 4.5.3 SVM Support Vector Machines 133 kommen aus dem Gebiet des "machine learning" bzw. der "statistical learning theory". Sie sind ein Ansatz, der mit Hilfe von Kernelfunktionen 134 einige Neuronale Netze zur Mustererkennung und Funktionsapproximation zusammen fassen kann. Ziel r Gesucht ist eine Funktion f ( x ,α ) , die alle Beispiele richtig klassifiziert. Binäre r r r r Klassifikation ist bspw. an Hyperebenen orientiert: {x :, w ⋅ x + b = 0} , α ≡ (w, b ) ∈ R n +1 bestimmt die Lage der Hyperebenen im n-dimensionalen Raum. Für die "learning machine" erhält man: r r r r LM = { f ( x , (w, b )) = sgn( w ⋅ x + b)}135 Klasse 1 Klasse 2 r r Abb.: Lineare Trennbarkeit in zweidimensionalen Raum. Sie ist definiert durch w ⋅ x + b = 0 . Punkte auf der linken Seite werden Klasse 1, Punkte auf der rechten Seite Klasse 2 zugeordnet. r α ≡ (w, b ) ∈ R n +1 bestimmt die Lage der Hyperebenen im n-dimensionalen Raum. Die Wahl des Parameters erfolgt durch Auswahl einer Menge von Trainingsdaten r r r r (Stichprobe): {( x1 , y1 ), ( x 2 , y 2 ),..., ( xl , y l )} . xi bezieht sich auf den n-dimensionalen r Raum, y ∈ {− 1,1} . Die Stichprobe wird gezogen mit einer Wahrscheinlichkeit P( x , y ) . Die Stichprobe ist nicht unbedingt repräsentativ. Ein Klassifizierer von bestimmter Qualität ist bestimmt durch das Merkmal: gute Generalisierungsfähigkeit. Risiko r Aus einer Menge von Mustern, die einer Zufallsverteilung P ( x , y ) unterliegen, werden r 'l' Muster gezogen. Das Risiko, dass f ( x ,α ) die Mustermenge nicht separieren r r 1 kann, kann so formuliert werden: R (α ) = ∫ y − f ( x , α ) dP( x , α ) . 2 133 http://de.wikipedia.org/wiki/Support_Vector_Machine vgl. 2.3.6 135 Linear Learning Machine 134 217 Spezielle Algorithmen (SAl) r 1 y − f ( x ,α ) : Differenz zwischen der gewünschten Ausgabe und der tatsächlich 2 vorliegenden Antwort. r Die Parameter α sind so zu bestimmen, dass f ( x ,α ) das Risiko über die r r Funktionsklassen f ( x ,α ) minmiert. Da P ( x , y ) unbekannt ist, kann der Risikowert selbst für gegebene α nicht berechnet werden. Das Risiko muß geschätzt werden. r 1 l Das empirische Risiko Remp (α ) = ∑ y i − f ( xi , α kann dagegen aus der 2l i =1 gemessenen mittleren Fehlergröße aus den Trainingsdaten der Länge l und spezieller Wahl von α bestimmt werden. Remp (α ) repräsentiert jedoch nicht immer r R(α ) : Es kann sein, dass f ( x ,α ) so schlecht gewählt wurde, dass gerade die gewählten Muster auf sie passen und keinen Fehler haben. Das ist bspw. bei einer Tabelle der Fall. Das empirische Risiko ist dann 0, das reale Risiko sehr hoch. Auf das reale Risiko kann vom empirischen Risiko nur durch Hinzufügen eines Vertrauensterms geschlossen werden. Im Rahmen der statistischen Lerntheorie leitete Vapnik folgende Obergrenze einer Klassifikationsmaschine für die Generalisierungsfähigkeit her, die in 1 − η Prozent der Fälle eingehalten wird: R(α ) ≤ Remp (α ) + θ (h, l ,η ) Die obere Grenze ist abhängig vom empirischen Risiko und dem Vertrauensterm θ (h, l ,η ) - h: VC-Dimension des verwendeten Hypothesenraums - l: Anzahl der Muster 2l ⎞ ⎛ ⎛η ⎞ h⎜ log + 1⎟ ⋅ log⎜ ⎟ h ⎠ ⎝ 4⎠ θ ( h, l , η ) = ⎝ l Die VC-Konfidenz wächst monoton mit h VC-Dimension 136 r Die VC-Dimension ist eine Eigenschaft für gegebenen Funktionsklassen f ( x ,α ) : - - 136 r Die VC-Dimension einer Menge von Funktionen f ( x , α ) ist definiert als die maximale Anzahl von Trainingspunkten, die durch diese Klasse von Funktionen in allen möglichen Zuordnungen separiert werden kann(, d.h. l Punkte können auf 2 zerschlagen werden Die VC-Dimension liefert Maße für die Kapazität von Funktionsklassen. Vapnik, Chervonenkis 218 l mögliche Weisen Spezielle Algorithmen (SAl) Abb.: 3 Punkte, die nicht durch eine Linie verbunden sind, können durch eine gerichtete Hyperebene im 2-dimensionalen Raum getrennt werden. Bei 4 Punkten im zweidimensionalen Raum ist dies nicht möglich. Die VC-Dimension ist 3. Die VC-Dimension eines n-dimensionalen Raums ist n+1. Support Vector Machines , die den Gauss'schen Kernel implementieren haben eine unbegrenzte VC-Dimension. Die VC-Dimendisionen für polynomiale Kerne (Polynome vom Grad p) ist bestimmt ⎛ p + d l − 1⎞ ⎟⎟ , d l ist die Dimension des Raums in durch den Binomialkoeffizienten: ⎜⎜ p ⎠ ⎝ dem die Daten liegen. Hier ist die VC-Dimension begrenzt, sie wächst aber stark mit dem Grad. 219 Spezielle Algorithmen (SAl) 4.6 Backpropagation Netzwerke 4.6.1 Beschreibung Zweischichtige Neuronale Netze sind nur mit Einschränkungen zur Abbildung von Mustern geeignet 137 .Backpropagation Netzwerke bestehen aus drei oder mehreren Neuronenschichten. Die erste Neuronenschicht dient zur Aufnahme von externen Eingabewerten, die letzte Neuronenschicht ist zur Wiedergabe der Ausgabewerte vorgesehen. Die inneren Neuronen sind verdeckt ("hidden"). Sie ermöglichen die Lösung von Problemen, die eine interne Aufbereitung von Eingabewerten erfordern. Bsp.: "Exklusives ODER XOR" 138 Eingabe1 0 0 1 1 Eingabe2 0 1 0 1 Ausgabe 0 1 1 0 Neuronale Netze haben die Neigung, auf ähnliche Eingaben auch ähnliche Ausgaben zu erzeugen. Die XOR-Operation verlangt, daß ausgerechnet die ähnlichen Eingaben mit unterschiedlichen Ausgaben beantwortet werden müssen. Schaltet man zwei Netze hintereinander und lehrt beide das XOR-Problem, dann teilen sich die Netze die Arbeit. Abb. : Netzstruktur zur Lösung des XOR-Problems Das erste Netz erzeugt aus der Eingabe einen Zwischencode, das 2. Netz lernt die richtigen Antworten auf die "heimlichen" Zwischenwerte. Jedes Neuron einer Schicht ist mit jedem Neuron der vorgelagerten und nachfolgenden Schicht verbunden. Die Richtung des Informationsflusses verläuft vorwärtsgerichtet (Forwardpropagation) von Neuronen der Eingabeschicht über die Neuronen der Zwischenschicht(en) zu Neuronen der Ausgabeschicht (Feedforward-Netzwerke). 137 138 vgl. 4.4 vgl. 4.4 220 Spezielle Algorithmen (SAl) Auf diese Weise werden die Werte über ihre gewichteten Verbindungen zu den Einheiten des Ausgabe- Vektors nach vorn propagiert. Danach vergleicht man den erhaltenen Ausgabevektor. Die Differenz zwischen tatsächlichem und gewünschtem Ergebnis ist eine Funktion der aktuellen Verbindungsgewichte. Diese werden mit Hilfe einer Lernregel, die den Namen Backpropagation 139 trägt so angepaßt, daß sich der Fehler des Systems verkleinert. Von der Ausgangsebene wird der Einfluß der Ebenen auf alle Fehler zurückgerechnet. Abb. : Propagation und Backpropagation Diese Verfahrensweise wird zyklisch für alle Muster wiederholt. bis der Fehler einen ausreichend kleinen Wert erreicht hat. Bei diesem Verfahren sind beliebig viele Zwischenschichten möglich, da die Fehlerkorrektur schrittweise von der Ausgangsebene zur Eingangsebene vorgenommen wird. Eine zu große Anzahl von Zwischenschichten erfordert mehr (langsame) Matrizenoperationen ohne einen entsprechenden Gewinn an Auswahlmöglichkeiten. Eine geringe Anzahl von Zwischenschichten verkleinert den Rechenaufwand, verhindert aber eine korrekte Mustererkennung. 139 nach: D.E. Rumelhart, G.E. Hinton, R.J. Williams: Learning Internal Representations by Error Propagation in: David E. Rumelhart, James L. McClelland (Hrsg.) : Parallel Distributed Processing. Explorations in the Microstructure of Cognition, Volume 1: Foundations, Cambridge (Mass.), The MIT Press 221 Spezielle Algorithmen (SAl) 4.6.2 Grundlagen Herleitung des Backpropagation Algorithmus Ziel des Backpropagation-Lernverfahrens ist: Bestimmen von Verbindungsgewichten wij mit denen das Netzwerk die vorgegebene Menge von Eingabemustern auf die entsprechenden Zielmuster möglichst fehlerfrei abbilden kann. Ein Maß für die Abbildungsfähigkeit (Leistungsfähigkeit) des Netzwerks ist der mittlere quadratische Fehler über alle Musterpaare (MSE): (1) E = ∑ E p = ∑∑ (t pk − o pk ) 2 p p k t pk − o pk : Lernfehler eines Ausgabeneurons k für ein vorgegebenes Muster. Durch das Verändern der einzelnen Verbindungsgewichte wij ändert sich der Ausgabewert von Neuronen der Ausgabeschicht und damit auch E. E → W → → w' w' ' Abb. : Energieverlauf Die Minimierung von E kann über die Verkleinerung der Lernfehler Ep für jedes Muster angestrebt werden, d.h.: Bestimmen solcher wij , die die Lernfehler Ep des Musters reduzieren. Das Backpropagation-Verfahren beruht auf einem → Gradientenabstiegsverfahren 140. Im Punkt w' wird die Tangente auf der MSEOberfläche bestimmt und auf der Tangente um eine gewisse vorgegebene Länge → abgestiegen. Man erhält den Gewichtsvektor w' ' , bestimmt wieder die Tangente und wiederholt das Verfahren. Der Backpropagation Algorithmus sucht das Minimum der Fehlerfunktion eines bestimmten Lernproblems durch Abstieg in der Gradientenrichtung entlang der Fehlerfunktion. Die Kombination der Gewichte eines Netzes, die den Berechnungsfehler minimiert, wird als Lösung des Lernproblems betrachtet. Der Gradient 141 für die Fehlerfunktion muß für alle Punkte des Gewichtsraums existieren, d.h. die partiellen Ableitungen der Fehlerfunktion nach den einzelnen Gewichten müssen überall definiert sein. Der Backpropagation Algorithmus arbeitet in zwei Schritten: Feedforward-Schritt und Backpropagation Schritt. Im Feedforward Schritt wird am Eingang ein Testmuster angelegt, und die Ausgabe (Output) berechnet. Aus dem errechneten Output und der gewünschten Ausgabe wird mit Hilfe der Fehlerfunktion der Fehler berechnet: 140 vgl. 3.5.1 141 Gradient: Steigungsfaktor der Tangente 222 Spezielle Algorithmen (SAl) (2) E = ∑ (t k − ok ) 2 k Der Fehler wird dann im Backpropagation Schritt von der Ausgabeschicht aus auf die Gewichte der einzelnen Schichten aufgeteilt. Die Gewichte werden dabei so modifiziert, dass sich der Fehler reduziert. x1 x2 … (i) xni ... Feedbackward 1 wij (j) ... 1 w jk (k) ... Feedforward Abb. : Beispiel eines zweistufigen Multilayer-Perzeptrons. In der Feedforward-Richtung wird die Ausgabe für ein angelegtes Eingangsmuster berechnet. In der Backpropagation-Richtung wird in der Trainingsphase der fehler am Output auf die Gewichte zwischen den Schichten aufgeteilt. Am Beginn der Trainingsphase werden alle Gewichte zufällig initialisiert. Im Feedforward-Schritt wird ein Eingangsmuster angelegt und die Ausgabe des Netzes berechnet: net j = ∑ wij ⋅ xi . Die Ausgabe der Neuronen im „j“-Layer ist dann: i o j = F (net j ) . Im nächsten Schritt wird die Ausgabe der Schicht k berechnet: net k = ∑ w jk ⋅ o j . Die Ausgabe der Neuronen im „k“-Layer ist dann: ok = F (net k ) . Zur j 1 ⋅ ∑ (t k − ok ) 2 benutzt. Der Faktor 2 k ½ wurde zur Erleichterung der nachfolgenden Berechnung eingeführt. Zur Korrektur der Gewichte wählt man weiteren Berechnung wird die Fehlerfunktion E = (3) Δwij = −ε ∂E p ∂wij Da der minimale Fehler gesucht ist, werden die Gewichte um einen Bruchteil des negativen Gradienten korrigiert (Veränderungen der Verbindungsgewichte sind proportional zur Steigung der Fehlerfunktion, die Lernrate ε ist Proportionalitätsfaktor). 223 Spezielle Algorithmen (SAl) E w jk (t + 1) = w jk (t ) − ε ∂E ∂w jk Steigender Gradient Fallender Gradient g = k ⋅w+b k < 0 g = k ⋅w+b k > 0 w wenn E ' (W ) < 0, verkleinere w ⎧ ⎪ Abb.: Optimierung durch Gradientenabstieg: ⎨ wenn E ' ( w) > 0, vergrößere w ⎪wenn E ( w) = 0, lokalesMinimum gefunden ⎩ Da ok = F (net k ) und net k = ∑ w jk ⋅ o j ist, kann mit Hilfe der Kettenregel die partielle j Ableitung ∂E ∂w jk ausgedrückt werden durch: ∂E ∂E ∂net k = ⋅ ∂w jk ∂net k ∂w jk mit ∂net k ∂ = ⋅ ∑ w jk ⋅ o j . Da die partielle Ableitung nur nach einer Komponente der ∂w jk ∂w jk j Summe gebildet wird kann man schreiben: Mit der Definition δ k = ∂net k ∂ = w jk ⋅ o j = o j ∂w jk ∂w jk − ∂E folgt nach Einsetzen in Gleichung (3): ∂net k (4a) Δw jk = ε ⋅ δ k ⋅ o j (für online Lernen) (4b) Δw jk = ε ⋅ ∑ δ k( p ) ⋅ o (j p ) (für „batch“-Lernen der Muster p) p Zur Berechnung des Ausdrucks δ k = − (5) δ k = − ∂E verwendet man wieder die Kettenregel: ∂net k ∂E ∂ok ∂E ⋅ =− ∂o k ∂net k ∂net k 224 Spezielle Algorithmen (SAl) 1.Fall: Ausgabeneuron „k“: Für den ersten Faktor kann man nach Einsetzen der Definition für die Fehlerfunktion ∂E ∂ 1 schreiben: = ( (t k − ok ) 2 ) = −(t k − ok ) . Für den zweiten Faktor aus Gleichung ∂ok ∂ok 2 ∂o k (5) gilt: = F ' (net k ) . Falls als Aktivierungsfunktion die logistische Funktion ∂net k verwendet wird, kann man schreiben: ∂ok = F ' ( net k ) = F ( net k ) ⋅ (1 − F (net k ) = o k ⋅ (1 − ok ) ∂net k So erhält man δ k = (t k − ok ) ⋅ ok ⋅ (1 − ok ) Eingesetzt in Gleichung (4), erhält man für die Änderung der Gewichte: (6) Δw jk = ε ⋅ (t k − ok ) ⋅ ok ⋅ (1 − ok ) ⋅ o j Die Formel gilt allerdings nur für die Gewichte der Neuronen in der Ausgangsschicht. 2. Fall: verdecktes Neuron „j“ Für die Gewichte der internen Neuronen setzt man analog an: (7) Δwij = −ε ⋅ ∂E ∂net j ∂E ∂E = −ε ⋅ ⋅ = −ε ⋅ ⋅ oi = ∂wij ∂net j ∂wij ∂net j ε ⋅ (− ∂E ∂o j ∂E ⋅ ) ⋅ o i = ε ⋅ (− ) ⋅ F ' (net j ) ⋅ o i = ε ⋅ δ j ⋅ o i ∂o j ∂net j ∂o j Ist nur eine interne Schicht vorhanden, entspricht in der vorstehenden Gleichung ∂E oi = xi . Die Berechnung der partiellen Ableitung erfolgt indirekt: ∂o j (8) ⎛ ∂E ∂E ∂net k ∂E = ∑ ⎜⎜ − ⋅ = −∑ ∂net k ∂o j ∂o j k ∂net k k ⎝ ⎞ ∂ ⎟⎟ ⋅ ⎠ ∂o j ∑o k j ⋅ w jk = ∑ δ k ⋅ ∂ w jk = ∑ δ k ⋅ w jk ∂o j k Aus (7) und (8) folgt somit: δ j = F ' (net j ) ⋅ ∑ δ k ⋅ w jk . Der Fehler des Neurons „j“ setzt k sich aus den gewichteten Fehlern δ k aller Nachfolgeneuronen „k“ und den Gewichten von „j“ zu diesem „k“ zusammen. 225 Spezielle Algorithmen (SAl) Das δ eines inneren Neurons kann somit aus den „Deltas“ der Vorgängerschicht berechnet werden. Beginnt man mit der letzten Schicht, der Ausgangsschicht, kann man sich aus Gleichung δ k = (t k − ok ) ⋅ ok ⋅ (1 − ok ) „ δ k “ berechnen. Anschließend kann man den Fehler rückwärts propagieren, um die Deltas der inneren Schichten zu berechnen. Da als Aktivierungsfunktion die sigmoide Funktion 142 verwendet wird, gilt F ' (net j ) = F (net j ) ⋅ (1 − F (net j )) = o j ⋅ (1 − o j ) Somit kann für δ j = o j ⋅ (1 − o j ) ⋅ ∑ δ k ⋅ w jk geschrieben werden. Für die Änderung der k Gewichte in der inneren Schicht ergibt sich somit (9) Δwij = ε ⋅ o j ⋅ (1 − o j ) ⋅ oi ⋅ ∑ δ k ⋅ w jk k Für ein Netzwerk mit nur einer inneren Schicht gilt oi = xi . Zusammenfassung. Die Veränderung der Gewichte erfolgt während des Lernprozesses mit Hilfe des Backpropagation Algorithmus nach folgenden Formeln: δ k = (t k − ok ) ⋅ ok ⋅ (1 − ok ) : Δw jk = ε ⋅ δ k ⋅ o j für die Ausgabeschicht. δ j = o j ⋅ (1 − o j ) ⋅ ∑ δ k ⋅ w jk : Δwij = ε ⋅ δ j ⋅ oi für die inneren Schichten k Verlauf der Fehlerfunktion Das Lernverhalten wird durch den Verlauf der Fehlerfunktion entscheidend beeinflußt. Im ungünstigsten Fall konvergiert der Backpropagation-Lernalgorithmus gegen einen Wert, der nur in einem bestimmten Bereich der Fehlerfunktion minimal ist (lokales Minimum). 142 Einige sigmoide Funktionen und ihre Ableitungen: f ( x) = 1 - linguistische Funktion f ( x) = 1 – Fermi-Funktion −x (1 + e ) mit Ableitung (1 + e − λx ) f ' ( x) = λ ⋅ e – Tangens Hyperbolicus cosh 2 ( x) = 4 −x (e x + e − x ) (e x + e − x ) 2 226 (1 + e − x ) 2 = f ( x) ⋅ (1 − f ( x)) − λx mit Ableitung x −x f ( x) = tanh( x) = (e − e ) f ' ( x) = 1 f ' ( x) = e (1 + e −λx ) 2 = λ ⋅ f ( x) ⋅ (1 − f ( x)) mit Ableitung Spezielle Algorithmen (SAl) E lokale Minima lokales Minimum globales Minimum wij Abb. 2.4-5: Lokales und absolutes Minimum beim Backpropagation Da stets eine Gewichtsveränderung in Richtung des steilsten Abstiegs der Fehlerfunktion erfogt, besteht die Gefahr: 1. Der Fehlerwert oszilliert um ein lokales Minimum 2. Das Tal der Fehlerfunktion kann nicht mehr in Richtung des globalen Fehlermini-mums verlassen werden 227 Spezielle Algorithmen (SAl) 4.6.3 Festlegen von Parameterwerten: Lernrate ε und Momentum Lernrate Ein schnelles Absinken des Fehlerwerts kann durch einen möglichst großen Wert der Lernrate (nahe 1) erreicht werden. Dabei kann aber der Fehlerwert über das Minimum der Fehlerfunktion hinauslaufen. Im folgenden Lernzyklus ist dann eine Veränderung des Verbindungsgewichts in genau entgegengesetzter Richtung erforderlich. Ein wiederholtes Auftreten dieses Effekts führt zu unerwünschten Schwingungen um das Minimum der Fehlerfunktion. Das Oszillieren kann durch Berücksichtigung des vorliegenden Werts der Gewichtsveränderung bei der Berechnung des aktuellen Gewichtsveränderungswerts vermieden werden: Momentum Δwij (t + 1) = ε ⋅ δ j ⋅ oi + α ⋅ Δwij (t ) Der Faktor α gibt an, wie stark der Wert der alten Gewichtsänderung in die Berechnung der neuen Gewichtsänderung einfließen soll. Üblicherweise wird beim Trainieren von Backpropagation-Netzwerken α mit 0.9 und ε nahe dem Wert 0.3 gewählt. Backpropagation mit Momentum-Term wird auch als konjungierter Gradientenabstieg bezeichnet. Anzahl der inneren Einheiten Im Allgemeinen steigert die Anzahl verborgener Schichten die Mächtigkeit des Netzes. Es gibt aber keine Möglichkeit die optimale Anzahl verborgener Schichten für ein spezielles Problem zu bestimmen. Die Praxis hat gezeigt, dass häufig eine verborgene Schicht ausreicht. Auch die optimale Anzahl der Neuronen in einer verborgenen Schicht wird durch das Problem bestimmt. Generell kann Folgendes über die Anzahl der inneren Einheiten in einem Backpropagation Netz festgehalten werden: Anzahl innerer Einheiten zu klein Anzahl innerer Einheiten zu groß Das Backpropagation Netz kann das Problem nicht lösen. Es besteht die Gefahr des Overtraining Das Problem wird nur teilweise durch Generalisierung erkannt Die überflüssigen Einheiten sind ausschließlich zur Erkennung der restlichen Trainingsmuster bestimmt (quasi: lokale Speicherung) Es wird keine grundlegende Ausbildungsvorschrift gefunden 228 Spezielle Algorithmen (SAl) 4.6.4 Testphase (Recall) Die Funktionsweise des Backpropagation-Netzwerks während der Testphase entspricht der Funktionsweise jedes anderen "Feedforward"-Netzwerks. Der Eingabevektor wird an die Eingänge angelegt, die Prozessorelemente berechnen daraus ihre Ausgabe. Ein Eingangsmuster wird angelegt und die Ausgabe des Netzes berechnet: net j = ∑ wij ⋅ xi . Die Ausgabe der Neuronen im „j“-Layer ist dann: i o j = F (net j ) . Im nächsten Schritt wird die Ausgabe der Schicht k berechnet: net k = ∑ w jk ⋅ o j . Die Ausgabe der Neuronen im „k“-Layer ist dann: ok = F (net k ) . j Im Rahmen der Testphase liefern Trainingsdaten Stützstellen, über die das Backpropagation Netz eine Funktion approximiert. Aber auch außerhalb soll das Netz eine genügende Genauigkeit aufweisen. Diese Eigenschaft wird als Generaliserungsfähigkeit des Netzes bezeichnet. Die Generalisierungsfähigkeit wird mit Hilfe einer Testmenge verifiziert, die mit keiner Komponente der Trainingsmenge übereinstimmt. 229 Spezielle Algorithmen (SAl) 5. Konzept und Arbeitsweise von Genetische Algorithmen 5.1 Evolution und Genetik 1. Lernprozeß Evolution Die Natur hat es geschafft, aus Milliarden denkbarer Eiweißmoleküle gerade diejenigen herauszufinden, die organisches Leben ermöglichen. Die Evolution hat schließlich zur Entstehung der DNS (Desoxyribonucleinsäure) geführt, die allem Leben (auf der Erde) als Bauplan zugrundeliegt. Die DNS eines Menschen enthält etwa 3,8 Milliarden Nukleotidbasen 143. Die Entwickling kann kein reiner Zufallsprozeß gewesen sein. Evolution kann vielmehr nur durch einen kollektiven Lernprozeß erklärt werden, wobei erfolgreiche Innovationen „gespeichert“ und erfolglose vergessen werden. Die Evolution stützt sich auf das Prinzip der natürlichen Selektion und die Vererbung von Eigenschaften und Merkmalen. Unter Selektion versteht man das Fortbestehen der Individuen, die am besten an die Umwelt angepaßt sind und das Aussterben der schlechter angepaßten Individuen. Die stärkeren Individuen haben die Möglichkeit, sich fortzupflanzen und ihre Eigenschaften an die nächste Generation weiterzugeben. 2. Chromosomen und Gene Chromosome sind Träger der Gene, der kompletten Erbinformation eines Lebewesens. Die Chromosomen bestehen aus Nukleinsäuren und Proteinen. Die wichtigste Nukleinsäure ist die DNS. Gene stellen bestimmte Abschnitte der DNS dar. Am Aufbau der DNS sind 4 Basen beteiligt: Adenin (A), Cytosin (C), Guamin (G), Thymin (T). Diese Basen bilden die Grundlage für die gesamte genetische Information eines Lebewesens, die als Sequenz der Buchstaben eines genetischen Alphabets A, C, G und T wie bspw. in „...-C-C-T-G-A-G-G-A-G-...“ notiert werden kann. Das bezeichnet man als den sog. Genetischen Code, der als Programm verstanden werden kann, das die Entwickling eines neuen Individuums festlegt. 3. Grundlegende Mechanismen der Vererbung Es liegt bspw. das Chromosom g mit 10 Genen g1 bis g10 vor. g1 g2 g3 g4 g5 g6 g7 g8 g9 g10 Abb. 5.1-1: Chromsom mit 10 Genen Jedes Gen ist durch seine Position im Chromosom bestimmt und kontrolliert eine ganz bestimmte Eigenschaft bzw. ein bestimmtes Merkmal eines Lebewesens wie bspw. seine Augenfarbe. So hat das Gen g4 bspw. den Wert „blau“. 143 Kettenglieder der DNS 230 Spezielle Algorithmen (SAl) Die Gesamtheit der Gene g = (g1, g2, g3, g4, g5, g6, g7, g8, g9, g10) bezeichnet man als den Genotyp des Individuums und meint damit das Erbbild. Das Individuum selbst, der sog. Phänotyp, ist die konkrete Ausprägung der Merkmale. Der Phänotyp muß sich im Kampf um Dasein behaupten. Neue Individuen (bzw. neue Chromosomen) entstehen durch Rekombinationsverfahren. Das vorherrschende Verfahren in der Natur ist die Paarung, die zur Kreuzung der Erbinformation und damit zu neuen Ausprägungen führt. 5.2 Prinzipielle Arbeitsweise genetischer Algorithmen 5.2.1 Arbeitsweise von genetischen Algorithmen Zufällig erzeugte Lösungen des Suchraums der Problemstellung bilden die Ausgangspopulation. Sie wird im Verlauf eines Genetischen Algorithmus durch einen Evolutionsprozeß verbessert. Bei der Suche nach besseren Lösungen kombinieren „Genetische Algorithmen“ das Überleben der tauglichen Individuen einer Population mit einem Austausch der genetischen Information zwischen den einzelnen Individuen. Die Individuen werden mit einer ihrer relativen Tauglichkeit (Fitneß) entsprechenden Wahrscheinlichkeit ausgewählt. Besonders taugliche Individuen erhalten bevorzugt die Möglichkeit zur Weitergabe der Lösungsinformation („survival of the fitest“). GA führen einen zufallsgesteuerten Suchprozeß durch, der durch die Nutzung populationsspezifischer Eigenschaften gesteuert wird. Der evolutionäre Such- und Optimierungsprozeß beruht auf 3 grundlegenden Prinzipien: Auslese (Selektion), Veränderung (Rekombination, Mutation) und Vervielfachung (Reproduktion) von Individuen. Die Grundprinzipien werden bei genetischen Algorithmen durch spezielle „Genmanipulationen“ verwirklicht. Das erfolgt natürlich nicht auf biologischen Chromosomen, sondern auf „Strings“, die bei „GA“ für die Problemlösungen (Gesamtheit der Individuen) stehen. In der Regel sind die Strings „binär“ kodiert, d.h. Jedem Gen einspricht ein Bitwert (Allel) 0 oder 1. 231 Spezielle Algorithmen (SAl) Population zum Zeitpunkt t Kreuzung (crossover) Selektion Mutation Verdrängung Population zum Zeitpunkt t + 1 Abb. 5.2-1: Prinipielle Arbeitsweise genetischer Allgorithmen 4.2.2 Struktur eines genetischen Algorithmus Aus der allgemeinen Arbeitsweise eines genetischen Algorithmus ergibt sich die folgende Struktur eines genetischen Algorithmus, die in zahlreichen Varianten auftauchen kann: Erzeuge eine Anfangspopulation von Individuen P := {p} repeat Bewerte jedes Individuum der Population P Selektiere Individuen zur Reproduktion (Crossover) Erzeuge neue Population P von Individuen until Abbruchkriterium Gib Individuum pmax mit maximaler Fitneß aus Ein genetischer Algorithmus setzt sich dann zusammen aus: - einer Kodierungsvorschrift für die Lösungskandidaten einer Methode, eine Anfangspopulation zu erzeugen eine Bewertungsfunktion (Fitneßfunktion) für die Individuen einer Auswahlmethode auf der Grundlage der Fitneßfunktion 232 Spezielle Algorithmen (SAl) Ein genetischer Algorithmus besteht weiter aus genetischen Operatoren, die die Lösungskandidaten ändern Mutation (zufällige Änderung einzelner Gene Crosover – Rekombination von Chromosomen Werten für die verschiedenen Parameter (z.B. Populationsgröße, Mutationswahrscheinlichkeit) Einem Abbruchkriterium eine festgelegte Anzahl von Generationen wurde berechnet eine festgelegte Anzahl von Generationen lang gab es keine Veränderung eine vorgegebene Mindestlösungsgüte wurde erreicht 5.3 Phasen eines genetischen Suchalgorithmus 5.3.1 Modellierung Durch Bestimmen einer geeigneten Codierung werden Lösungen des Suchraums im Rahmen einer geeigneten Abbildung über Zeichenketten (häufig Bitstrings) modelliert. Die Strings, die zur Darstellung der zulässigen Lösungen gebildet werden, sind die Genotypen einer Population. Die zulässigen Lösungen bezeichnet man als Phänotypen. Ein Phänotyp stellt das äußere Erscheinungsbild eines Individuums dar, das der Informationsgehalt des Genotyps durch Dekodierung bestimmt. 5.3.2 Konfigurierung 1. Genetische Operatoren Elemetare genetische Operatoren sind: 1.1 Selektion Sie besteht in der Auswahl von Individuen, gemessen anhand ihres Tauglichkeitsgrads. Alle ausgewählten Individuen werden bei der Generierung von Nachkommen berücksichtigt. 1.2 Crossover Elternpaare werden zufällig aus ausgewählten Individuen gebildet und daraufhin Nachkommen generiert, die eine genotypische Ähnlichkeit zu den jeweiligen Eltern aufweisen. Die Ähnlichkeit der Individuen wird durch einen Informationsaustauch zwischen den Genotypen der Eltern erreicht. Aus 2 Genotypen werden wieder Kinder (Crossover) gebildet, die dann in eine neue Population eingetragen werden. Die alte Population (Eltern-) bleibt unverändert. Beim Crossover tauschen die Elterm Teile ihres Gen-Strings miteinander aus. Die Stelle, ab der getauscht wird, wird durch Zufall ermittelt. Dazu wird die Zeichenkette des einen Eltermteils an beliebiger Stelle getrennt. An der gleichen Stelle wird auch die Zeichenkette des anderen Elternteils gespalten. Die 4 Teilstücke werden über Kreuz 233 Spezielle Algorithmen (SAl) (Crossover) verbunden. Der vordere Teil des einen mit dem hinteren Teil des anderen Strings. Die so erzeugten „Kinder“ werden dann in die Population der Nachkommen eingetragen. 1.3 Mutation Sie tritt nur „gelegentlich“ bei der Verarbeitung der Zeichenkette auf und bewirkt eine kleine Veränderung des Genotyps. Der Zweck der Mutation besteht in der Vermeidung einer vorzeitigen Spezialisierung der Individuen einer Population. Die Mutation verändert willkürlich Gen-Strings. Startpopulationen werden mit dem Zufallszahlengenerator gebildet. Es kann vorkommen, daß in der kompletten Population kein Gen-String existiert, der einer vom Problem her vorgegebenen Bedingung entspricht. Selektion und Crossover können Zeichenketten manipulieren, nicht an entscheidender Stelle zugleich verändern. Eine zu häufige Mutation ist aber schädlich, da sie ja auch „gute Strings“ zum Schlechten hin verändern kann. Daher läßt man die Mutation mit nur einer geringen Wahrscheinlichkeit auftreten (z.B. 0.1% aller Fortpflanzungen). Welches Gen des Gen-String geändert wird, bleibt dem Zufall überlassen. Nach Selektion, Crossover und Mutation besitzt die Nachkommen-Population zwei neue Mitglieder. Da die Populationsgröße konstant bleiben muß, wird diese Kette von Operatoren eines GA so oft durchgeführt, bis die neue Population vollständig mit Kinder besetzt ist. Damit ist ein Iterationsschritt oder auch ein Generationswechsel beendet und die Eltern-Population wird nicht mehr benötigt. Die NachkommenPopulation wird zur Elternpopulation. Für jedes Element der Population wird wieder die Fitneß ermittelt. Danach bildet man nach der beschriebenen Weise eine neue Nachkommen-Population, etc. Damit ergibt sich der folgende allgemeine Programmablaufplan eines genetischen Algorithmus, die in zahlreichen Varianten auftauchen kann: 234 Spezielle Algorithmen (SAl) Auswahl einer Ausgangspopulation Wahl der Codierung Ermittlung der Fitness der Individuen Selektion der Eltern Auswahl eines Individuums NEIN Matching-pool voll? Auswahl eines oder mehrerer Eltern Zeugung der Nachkommen Auswahl und Rekombination (Reproduktion) Anwendung Mutation Crossover NEIN Neue Generation voll? NEIN JA Abbruchbedingung erfüllt Ende der Optimierung Abb. 5.3-1: Prgrammablaufplan für genetische Algorithzmen 2. Optimierung mit genetischen Algorithmen Genetische Algorithmen sollen im Raum zulässiger Lösungen diejenigen herausfinden, die hinsichtlich einer gegebenen Zielfunktion die günstigsten Eigenschaften besitzen. Die Suche nach dem Optimum führen genetische Algorithmen in mehreren Schritten aus: (1) Festlegen einer Zielfunktion Sie ergibt sich direkt aus dem zu optimierenden Problem (Phänotyp des Problems). Außerdem wird das Abbruchkriterium bestimmt: Entweder nach einer festzulegenden anzahl von Iterationsschritten (Generationen) oder nach dem Auffinden einer zufriedenstellenden Lösung. (2) Kodierung und Festlegung des genetischen Suchraums Genetische Algorithmen arbeiten mit einer Poulation von n Individuen. Zu jedem Individuum sind zulässige Lösungswerte für die Variablen des Optimierungsproblems kodiert. Jedes Individuum besteht aus n Genen (Vektor mit n Komponenten). Bei genetischen Algorithmen wird häufig binär kodiert, d.h. jedem Gen ein Bitwert (Allel) 0 oder 1 zugewiesen. (3) Erzeugen einer Startpopulation binär codierter Lösungen auf zufällige Weise Die Ausgangssituation wird durch eine mit dem Zufallsgenerator benötigte Anzahl von Gen-Strings festgelegt. 235 Spezielle Algorithmen (SAl) (4) Bewertung der Überlebensfähigkeit der einzelnen Individuen unter den gegebenen Umweltbedungungen Alle Nebenbedingungen und die Zielfunktion müssen in der Fitneßfunktion berücksichtigt werden. (5) Selektion zweier Elternindividuen mit fitneßproportionaler Wahrscheinlichkeit Die Selektionswahrscheinlichkeit eines Individuums ergibt sich aus dem Verhältnis des individuellen Fitneßwerts zur Summe aller Fitneßwerte einer Generation. Die Auswahl realisiert das Prinzip „Survival of the fittest“. Individuen mit einem besseren Fitneßfunktionswert erhalten eine größere Wahrscheinlichkeit Nachkommen zu erzeugen als weniger geeignete Individuen. Die Selektion bestimmt, welche Mitglieder der Population (Startpopulation) sich fortpflanzen dürfen. Zur Vermeidung von Inzucht ist aber auch weniger geeigneten Mitgliedern (im bescheidenen Maße) die Fortpflanzung erlaubt. Eine gute Selektion erreicht man mit dem Roulette-Verfahren. Dieses bewirkt, daß die Wahrscheinlichkeit mit der ein Genstring bei der Selektion ausgewählt wird, proportional zu seiner Güte ist. Die Anzahl der Fächer im Roulette-Rad entspricht der Anzahl seiner Genotypen in einer Population. Der Genotyp mit höchster Qualität bekommt das größte Feld zugewiesen. Die Aufteilung geht abwärts bis zum schlechtesten Gen-String, der das kleinste Feld zugewiesen bekommt. Markierung Genotyp 5 Genotyp 4 Genotyp 1 Genotyp 2 Genotyp 6 Genotyp 3 Auswahl eines Individuums: - Drehe Glücksrad Wähle Chromosom dessen Sektor an der Markierung liegt Auswahl der Zwischenpopulation: - Wiederhole die Auswahl so oft, wie es Individuen in der Population gibt. Abb. 5.3-2: Roulette-Rad Je höher die Tauglichkeit eines Lebewesens ist, desto breiter ist das Fach und umso wahrscheinlicher ist es, daß die Roulette-Kugel in einem solchen Fach hängenbleibt. Alle Tauglichkeitswerte werden aufsummiert und daraus der mittlere Gütewert ermittelt: 236 Spezielle Algorithmen (SAl) f rel ( s ) = f abs ( s ) ist die Ausfallwahrscheinlichkeit eines Individuums (sog. ∑s '∈ p (t ) f abs (s' ) fitnessproportionale Selektion 144) Ein guten Lebewesen (Genotyp) erzeugt einen Gütewert über dem Mittelwert. Die Gesamtgüte geteilt durch den Gütewert eines Genotyps ergibt dessen Wahrscheinlichkeit. Mit einem Zufallszahlengenerator kann dann die Selektion für einen Genotyp vorgenommen werden. Die daraus resultierenden Eltern werden nach Fitness im folgenden Generationsschritt benutzt, alle übrigen werden vergessen. Häufig verwendet man auch die folgende Form der Selektion (mit dem Parameter r, 0 < r < populations _ groesse und p c , 0 < p c < 1 ): - - Wähle aus der Population p(t) zufällig (aber unter Berücksichtigung der Fitneß) r Chromosomen (mit Zurücklegen) Bestimme für jedes für jedes dieser r Chromosomen, ob es am Crossover teilnimmt (Wahrscheinlichkeit p c ) oder mutiert wird (Wahrscheinlichkeit 1 − p c ) und wende die genetischen Operatoren an. Wähle aus p (t ) zufällig (aber unter Berücksichtigung der Fitneß populations _ groesse − r Chromosomen (ohne Zurücklegen) Diese populations _ groesse − r Chromosomen werden unverändert übernommen (6) Crossover und Mutation Mit der vorher festgelegten Crossover-Rate (Rekombinatinswahrscheinlichkeit, empfohlen wird ein Wert zwischen 0.6 und 1) wird bestimmt, ob überhaupt ein Crossover stattfinden soll. Dann wird gleichverteilt-zufällig ein für beide Elternindividuen identischer Crossover-Punkt bestimmt. Durch Tausch der Teilstücke werden Nachkommen erzeugt. Gene werden nur mit einer sehr geringen Wahrscheinlichkeit (Mutationsrate, beliebte Werte sind 0.001 bzw. 0.01) invertiert. Die Mutation soll dem frühzeitigen Konvergieren des genetischen Algorithmus entgegenwirken. (7) Wiederholung der Schritte (5) und (6) bis die Nachfolgegeneration die Größe der Elterngeneration erreicht hat bzw. der Schritte ab Schritt (4), bis das Abbruchkriterium erfüllt ist. Anderenfalls endet der Algorithmus und die Ergebnisse werden ausgegeben. In der Regel wird die Optimierung nach einer vorher festgelegten, als ausreichend angesehen Anzahl von Generationen abgebrochen. Wählt man aus der letzten Population den Genotyp aus, der die höchste Güte besitzt, hat man meistens ein brauchbares Ergebnis der Optimierung. Noch besser ist es, bei jeder erzeugten Population den jeweils besten Genotyp herauszusuchen und festzuhalten. Wird in einer späteren Generation eine besserer Genotyp gefunden, so wird dieser gespeichert. Eine Alternative besteht darin, sich ebenfalls den besten Genotyp einer Population zu merken und über eine bestimmte Anzahl von Populationen zu beobachten. Stellt man dabei keine Verbesserung des jeweiligen besten Genstrings fest, dann kann die Optimierung beendet werden. 144 Die absolute Fitness f abs (s ) darf in diesem Fall nicht negativ sein, ggf. ist ein positiver Wert zu addieren und negative Werte sind Null zu setzen. Die Fitneß muß zu maximieren sein. (Sonst würden schlechte Individuen mit hoher Wahrscheinlichkeit gewählt). 237 Spezielle Algorithmen (SAl) 3. Einfacher genetischer Algorithmus (Simple GA) begin Festlegen der Zielfunktion Bestimmen eines Abbruchkriteriums Codierung Festelegen der genetischen Operationen Erzeugen der Startpopulation binär codierter Lösungen auf zufällige Weise DO DO Selektion zweier Elternindividuen mit fitneßproportionaler Wahrscheinlichkeit Crossover mit Wahrscheinlichkeit in Höher der Crossover-Rate Mutation einzelner Bits der so entstandenen Nachkommen mit Wahrscheinlichkeit in Höhe der Mutationsrate Übername beider Nachkommen in die Nachfolgegeneration Aktuelle Generation = Nachfolgegeneration WHILE Nachfolgegeneration unvollständig WHILE Abbruchkriterium noch nicht erfüllt end 5.3.3 Die Realisierungsphase Sie umfaßt alle Arbeiten zur Umsetzung des Entwurfs in ein lauffähiges Optimierungsverfahren (Implementierung, Bestimmen von Werten für externe Parameter). Bsp.: Bestimme das Maximum der Funktion f(x)=x2 für x-Werte, die zwischen 0 und 31 liegen. 1. Codierung des Eingabebereichs 0..31 über einen binären String (durch eine 5 Bit umfassende Binärzahl). 2. Zufällige Auswahl einer Ausgangspopulation. 3. Berechnung der Fitness. 4. Aus dem Roulette-Rad werden die aktuellen Elternpaare ermittelt, die die neue Population erzeugen sollen. Zeichenkette Nr. 1 2 3 4 Summe Durchschnitt Max AnfangsPopulation 0 1 1 0 1 1 1 0 0 0 0 1 0 0 0 1 0 0 1 1 x-Wert f(x)=x2 13 24 8 19 169 576 64 361 1170 293 576 Es wurden ermittelt: „String 1“ wird einmal kopiert „String 4“ wird einmal kopiert „String 2“ wird zweimal kopiert „String 3“ wird überhaupt nicht kopiert 5. Crossover a) Die Zeichenketten werden zur Paarung zufällig ausgewählt b) Ausgewählte Paare werden an zufällig ausgewählten Punkten gepaart. Im vorliegenden Bsp. werden String 1 und String 2 an Punkt 4 gekreuzt, String 3 und String 4 an Punkt 2. 6. Mutation 238 Spezielle Algorithmen (SAl) Die Wahrscheinlichkeit für eine Mutation soll hier 0.001 sein. Über die 20 Bitpositionen ergibt das einen Erwartungswert vom 20 * 0.001 = 0.02 bits für eine Mutation währen einer Generationenfolge. Hier würde keine Bitposition von einer Änderung betroffen sein. 7. Test der neuen Population Population nach der Reproduktion 0 1 1 0.1 1 1 0 0.0 1 1.0 0 0 1 0.0 1 1 Partner (zufällig ausgew.) 2 1 4 3 Crossover Punkt Neue Population x-Wert f(x)=x2 169 576 64 361 0 1 1 1 12 25 27 16 144 625 729 256 1754 439 729 1 1 1 0 1 0 0 0 0 0 1 0 0 1 1 0 Implementierung 145: #include #include #include #include <stdlib.h> <stdio.h> <time.h> <math.h> #define random(num) (rand()%(num)) #define randomize() srand((unsigned)time(NULL)) #define #define #define #define #define POPULATION_SIZE CHROM_LENGTH PCROSS PMUT MAX_GEN 10 5 0.6 0.5 50 struct population { int value; unsigned char string[CHROM_LENGTH]; unsigned int fitness; }; struct population pool [POPULATION_SIZE]; struct population new_pool[POPULATION_SIZE]; int selected[POPULATION_SIZE]; int generations; // select int select(double sum_fitness) { double r, parsum; int i; parsum = 0; r = (double)(rand() % (int)sum_fitness); for (i = 0; i < POPULATION_SIZE, parsum <= r; i++) parsum += pool[i].fitness; return(-i); } int flip(double prob) { double i; i = ((double)rand()) / RAND_MAX; 145 max.CPP 239 Spezielle Algorithmen (SAl) if ((prob == 1.0) || (i < prob)) return (1); else return (0); } // crossover void crossover(int parent1, int parent2, int child1, int child2) { int site; int i; if (flip(PCROSS)) site = random(CHROM_LENGTH); else site = CHROM_LENGTH - 1; for (i = 0; i < CHROM_LENGTH; i++) { if ((i <= site) || (site == 0)) { new_pool[child1].string[i] = pool[parent1].string[i]; new_pool[child2].string[i] = pool[parent2].string[i]; } else { new_pool[child1].string[i] = pool[parent2].string[i]; new_pool[child2].string[i] = pool[parent1].string[i]; } } } // mutation void mutation() { for (int i = 0; i < POPULATION_SIZE; i++) { for (int j = 0; j < CHROM_LENGTH; j++) if (flip(PMUT)) pool[i].string[j] = ~new_pool[i].string[j] & 0x01; else pool[i].string[j] = new_pool[i].string[j] & 0x01; } } // evaluate int evaluate(int value) { return /*(pow((double) value, 2))*/ value * value; } // encode // (kodiert einen Integer-Wert in eine binaere Zeichenkette) void encode(int index, int value) { for (int i = 0; i < CHROM_LENGTH; i++) pool[index].string[CHROM_LENGTH - 1 - i] = (value >> i) & 0x01; } // decode // dekodiert eine binaere Zeichenkette in eine Ganzzahl int decode(int index) { int value = 0; 240 Spezielle Algorithmen (SAl) for (int i = 0; i < CHROM_LENGTH; i++) value += (int)pow(2.0,(double) i) * pool[index].string[CHROM_LENGTH - 1 - i]; return (value); } // initialize_population // (erzeugt und initialisiert eine Population void initialize_population() { randomize(); for (int i = 0; i < POPULATION_SIZE; i++) encode(i,random(2^CHROM_LENGTH)); } void statistics() { int i, j; printf("\n; Generation: %d\n;SelectedStrings\n;",generations); for (i = 0; i < POPULATION_SIZE; i++) printf("%d", selected[i]); printf("\n"); printf("\nX\tf(x)\tNew_String\tX"); for (i = 0; i < POPULATION_SIZE; i++) { printf("\n%d\t%d\t", pool[i].value, pool[i].fitness); for (j = 0; j < CHROM_LENGTH; j++) printf("%d", pool[i].string[j]); printf("\t\t%d",decode(i)); } } void main(void) { int i; double sum_fitness, avg_fitness, old_avg_fitness; generations = 1; avg_fitness = 1; initialize_population(); do { if (avg_fitness <= 0.0) break; old_avg_fitness = avg_fitness; sum_fitness = 0; // fitness evaluation for (i=0; i < POPULATION_SIZE; i++) { pool[i].value = decode(i); pool[i].fitness = evaluate(pool[i].value); sum_fitness += pool[i].fitness; } avg_fitness = sum_fitness / POPULATION_SIZE; for (i = 0; i < POPULATION_SIZE; i++) selected[i] = select(sum_fitness); for (i = 0; i < POPULATION_SIZE; i = i + 2) crossover(selected[i], selected[i+1],i,i+1); mutation(); statistics(); printf("\nimprovement: %f\n", avg_fitness / old_avg_fitness); } while ((++generations < MAX_GEN) /*&& (avg_fitness >= 0.0) */ && ( ((avg_fitness / old_avg_fitness) > 1.005) || ((avg_fitness / old_avg_fitness) < 1.0) ) ); system("Pause"); } 241 Spezielle Algorithmen (SAl) Nach 50 Generationen zeigt sich folgende Ausgabe: Abb. 5.3-2: Resultat nach 50 Generationen 5.3.4 Verfahrensbewertung bzw. Verfahrensverbesserung Im Anschluß an eine Realisierung des Algorithmus sollte eine Verfahrensbewertung erfolgen sowie eine Verfahrensverbesserung, die sich von einer Verfahrenserweiterung bis hin zur erneuten Abarbeitung aller Phasen der Entwicklung eines GA erstrecken kann. 5.3.5 Güte eines genetischen Algorithmus Zu Beurteilung eines Genetischen Algorithmus betrachtet man meist sein Konvergenzverhalten sowie seine -geschwindigkeit. Bezüglich des Konvergenzverhaltens vergleicht man die Qualität, also die Zielfunktionswerte, der von einen Genetischen Algorithmus berechneten Lösungen mit denen anderer Verfahren. Reicht zur Lösung des Problems die Erzeugung eines guten Individuums, so wählt man natürlich das beste Individuum der Population als Vergleichswert. Ist man jedoch an einer guten Gesamtpopulation interessiert, so vergleicht man die mittlere Fitneß aller Mitglieder der Population mit der bereits bekannten Lösung. Mit dem Begriff der Konvergenzgeschwindigkeit wird bewertet, wie schnell, das heißt nach Ablauf wie vieler Generationen, Lösungen bestimmter Qualität hervorgebracht werden. Zur Darstellung der Konvergenzgeschwindigkeit werden Diagramme benutzt, in denen die erreichte Lösungsqualität in Abhängigkeit der Generationen dargestellt wird. Insgesamt scheint die Modellierung des Evolutionsprozesses ein gelungener Kompromiß zwischen rechenzeitintensiven Verfahren, die das Auffinden von optimalen Lösungen garantieren, und einfachen deterministischen Heuristiken zu sein, die zwar sehr schnell, aber leider häufig schlechte Lösungen produzieren. Durch die Festlegung der Anzahl der Generationen kann sich der Benutzer eines Genetischen Algorithmus zwischen beiden Extremen entscheiden. Erlaubt er nur eine kleine Anzahl von Generationen, werden nur relativ schlechte Lösungen gefunden, während eine große Generationenzahl das Auffinden fast optimaler 242 Spezielle Algorithmen (SAl) Lösungen wahrscheinlich macht. Die stochastische Beeinflussung der deterministischen Operatoren eines Genetischen Algorithmus erlaubt die Überwindung lokaler Optima in Suchraum, und die Organisation der Suche mittels einer Population von Punkten im Suchraum (und nicht eines einzelnen Punktes) verkleinert die Gefahr, daß keiner der Suchpunkte in der Lage ist, einen verbesserten Nachkommen zu produzieren. Genetische Algorithmen, so scheint es, sind auf dem besten Wege, zumindest die Bearbeitung der großen Klasse der kombinatorischen Optimierungsprobleme zu (r)evolutionieren. 5.5 Anwendungen 5.5.1 Ein genetischer Algorithmus für das Problem des Handlungsreisenden 1. Aufgabenstellung Das Travelling-Salesman-Problem - ein Reisender sucht die kürzeste Rundtour durch verschiedene Städte - läßt sich zwar ganz einfach beschreiben und anschaulich darstellen, doch ist es nach wie vor Forschungsgegenstand ganzer Expertengruppen. Der Grund hierfür liegt auf der Hand: Bis heute ist kein Algorithmus bekannt, der weniger als exponentiellen Aufwand benötigt, um die optimale Rundreise zu ermitteln. In gewisser Hinsicht kann das TSP sogar als klassisches Grundproblem der kombinatorischen Optimierung (z.B. Reihenfolgenprobleme) angesehen werden, was den repräsentativen Umgang mit Lösungsalgorithmen, gerade auf Hinblick auf komplizierter gestaltete Alltagsprobleme, um so interessanter macht. 2. Repräsentation des Lösungsraums und Crossover-Operatoren Die Repräsentation des Lösungsraums erfolgt durch eine Zeichenkette, die die Identifikation zu den Städten der Rundreise (Knotennummern) angibt, z.B.: 1 2 3 4 5 6 7 8 9 10. In dieser Zeichenkette liegt eine aufsteigende Ordnung der Knotennummern zu bspw. 10 Städten vor, die in der Regeln nicht die kürzeste Rundreise repräsentiert. Zur Bildung geeigneter Kandidaten füe eine kürzeste Rundreise stehen eine Reihe von Varianten des Crossover bereit bereit (parallel matched crossover (PMX), order crossover (OX) und cycle crossover (CX)) 146. Beim CX-Operator wird bspw. bezogen auf 2 zu kombinierende Touren A = 9 8 2 1 7 4 5 10 6 3 B = 1 2 3 4 5 6 7 8 9 10 folgendes Auswahlverfahren getroffen: Gestartet wird in A von links und dann die folgende Stadt vom ersten Elternteil ausgewählt: 146 vgl. David E. Goldberg: Genetic Algorithms in Search, Optimization, and Machine Learning, AddisonWesley Publishing Company, Reading / Massachusetts, ... 243 Spezielle Algorithmen (SAl) A‘ = 9 - - - - - - - - Die Auswahl der 9 erfordert die Wahl von 1 aus dem zweiten Elternteil und bestimmt die „1“ an Postion 4 in A‘: A‘ = 9 - - 1 - - - - - B = 1 2 3 4 5 6 7 8 9 10 Unter der 1 in A‘ steht 4, in A wird als nächste Größe 4 ausgesucht: A‘ = 9 - - 1 - 4 - - - B = 1 2 3 4 5 6 7 8 9 10 Unter der 4 in A‘ steht eine 6, sie wird in A‘ als nächste Größe bestimmt: A‘ = 9 - - 1 - 4 - - 6 B = 1 2 3 4 5 6 7 8 9 10 Jetzt käme 9 an die Reihe, das führt aber zu einem Kurzzyklus. Deshalb werden die restlichen Stringpositionen einfach getauscht: A‘ = 9 2 3 1 5 4 7 8 6 10 3. Selektion Neben dem Crossover beeinflußt der Selektionsoperator das Verhalten eines Genetischen Algorithmus wesentlich. Zu diesem Zweck werden Individuen hoher Qualität häufiger zur Rekombination ausgewählt. Sie sollen öfter Nachkommen generieren, die deren gute Eigenschaften erben. Man sagt auch, daß die Population durch die Selektion einem Lernprozeß unterworfen wird, da mit zunehmender Generationenzahl der Anteil der qualitativ guten Individuen ansteigt. Aber Vorsicht: Eine zu häufige Wahl desselben Individuums bewirkt eine schnelle Durchsetzung der gesamte Population mit dem genetischen Material nur eines Individuums. Dieses kommt in etwa dem Effekt der Inzucht gleich: Durch den Verlust der genetischen Vielfalt innerhalb einer Population wird eine Qualitätssteigerung durch die Evolution unmöglich. Die Population stagniert dann häufig in einem schlechten lokalen Minimum. Außerdem sollte berechnet werden, daß auchqualitativ schlechte Individuen genetisches Material enthalten können, welches beim Crossover mit guten Lösungen zu Evolutionsschüben führt. Gerade die Tatsache, daß auch schlechte Individuen unverzichtbar für die evolutionäre Entwicklung einer Population sind, ist von vielen Autoren Genetischer Algorithmen beobachtet worden. Zum Schutz vor diesen Problemen werden nur Kinder in die Population aufgenommen, die sich von der Elternpopulation durch mindestens ein Merkmal unterscheiden. Der Fairneß halber werden aber trotzdem Nachkommen einer Generation bevorzugt, die die gleichen Merkmale haben wie die Eltern, um eine gewisse Dynamik zu gewährleisten und den "Circle of Life" nachzubilden. Damit nicht alle Individuen miteinander verglichen werden müssen, wird angenommen, daß Lösungen mit verschiedener Fitneß auch verschiedene Gencodes besitzen. 4. Fitneß 244 Spezielle Algorithmen (SAl) Allgemein wird unter Fitneß die Anpassung von Individuen an ihre Umwelt verstanden. Klar ist, daß zur Lösung von kombinatorischen Optimierungsaufgaben Individuen gefragt sind, deren Zielfunktionswert - im Beispiel die Länge der zurückgelegten Strecke - optimal ist. Hier bietet es sich an, eine minimale Fitneß als erstrebenswert zu erachten, da die Länge der gefahrenen Rundreise ein exzellenter Repräsentant dafür ist. 5. Applet mit einem genetischen Algorithmus zum Rundreiseproblem http://fbim.fh-regensburg.de/~saj39122/dkst/Index.htm 5.5.2 Ein genetischer Algorithmus für das Packproblem 1. Aufgabenstellung 147 Ein Kasten (Rechteck) ist so mit einer vorgegebenen Anzahl von Kästchen (Rechtecken) aufzufüllen, daß die Stapeldichte möglichst groß, der Stapel möglichst klein ist. 2. Problembeschreibung Mathematiker reihen diese Probleme in die Klasse der kombinatorischen Optimierungsprobleme. Sie faßt Aufgaben zusammen, deren Lösung das Durchsuchen eines riesigen, vordefinierten Suchraums erfordert. Gesucht wird nach Lösungen, die bzgl. einer Zielfunktion besonders niedere oder hohe Werte liefern (z.B. TSP-Problem 148). Beim Packproblem ist das Ziel: Minimierung der Stapelhöhe. Zunächst ist der Suchraum, in dem dieses Ziel erreicht werden soll, „unendlich“ groß: Man kann, wenn irgendwo nicht zu füllende Lücken auftreten, Bausteine beliebig nach links oder rechts, unten oder oben verschieben. Verlangt man aber, daß in einer zulässigen Lösung keines der Rechtecke im Behälter nach links oder nach unten verschoben werden kann, ohne ein anderes Rechteck oder den Rand des Behälters zu überlappen, so wird der Suchraum endlich. Diese Forderung soll als BLBedingung (BL = bottom/left) bezeichne werden. Da ein Rechteck flach oder stehend gestapelt werden kann (Schrägstellungen sind ausgeschlossen), gibt es bei m unterscheidbaren Rechtecken 2 m ⋅ m! Möglichkeiten, die Rechtecke zu ordnen. 3. Codierung 149 147 Das Packproblem wurde beschrieben (einschl. Lösungsangaben in Pseudocode-Darstellung) in: mc, Mai 1991. Alle Angaben zum Algorithmus wurden aus dieser Quelle übernommen. Hier wurde der Lösungsalgorithmus neu in objektorientierter Form überführt und als Java-Applet bereitgestellt. 148 vgl. 4.4.1 149 Alle Angaben zur Codierung sind übernommen aus: Elegant tiefstapeln von Berthold Kröger in mc, Mai 1991, S. 72 - 88 245 Spezielle Algorithmen (SAl) 4. Operationen 150 5. Applet mit dem genetischen Algorithmus für das Packproblem http://fbim.fh-regensburg.de/~saj39122/dkst/Index.htm 5.5.3 Genetic Function Finder http://fbim.fh-regensburg.de/~saj39122/prmeha/index.html 150 vgl. Berthold Kröger: Elegant tiefstapeln in mc, 1991, S. 72-88 246