Spezielle Algorithmen - oth

Werbung
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
Herunterladen