Ein bisschen Graphentheorie Gewichtete Graphen und

Werbung
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
Graphen Teil 1
1. Graphen als Modellierungswerkzeug
2. Darstellung von Graphen
(Adjazenzmatrix und Adjazenzliste)
3. Ein bisschen Graphentheorie
4. Gewichtete Graphen und Dijkstra
S.
1
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
2
1. Graphen als Modellierungswerkzeug
Im ersten Beispiel beschäftigen wir uns mit der Frequenzplanung in Funknetzen1. Abbildung 1 zeigt
die Lage von Funkmasten in der Gemeinde Katlenburg-Lindau2.
Eine Sendestation überdeckt dabei ein bestimmtes geographisches Gebiet. Dabei kann es
vorkommen, dass bestimmte Bereiche von mehreren Sendestationen überdeckt werden. Diese
Situation ist in Abbildung 2 dargestellt.
Abbildung 1: Funkmasten
Abbildung 2: Ausbreitungsbereiche der Sendestationen
Wenn nun mehrere Sendestationen dieselbe Frequenz benutzen, sich aber ihre Sendebereiche
überdecken, kann es zu Störungen kommen. Um diese Störungen zu vermeiden, sollten alle Sender,
die ein gemeinsames Gebiet erreichen, unterschiedliche Sendefrequenzen verwenden. Wir
modellieren dies Problem zunächst einmal, in dem wir uns auf die wichtigsten Informationen
beschränken. Statt Funkmasten verwenden wir Knoten, die wir mit A – G beschriften
(Abbildung 3). Überlappen sich zwei Sendebereiche, dann verbinden wir die zugehörigen Knoten
mit einer Kante, wie in Abbildung 4 dargestellt.
Abbildung 3: Funkmasten als Knoten
Abbildung 4: Knoten und Kanten
Da wir zur Lösung des Problems die genaue geographische Lage der Funkmasten nicht mehr
benötigen, entsteht aus unseren Überlegungen folgender Modellgraph:
1
Die Idee stammt aus dem Buch „Graphentheorie. Eine anwendungsorientierte Einführung“ von Peter Tittmann,
Fachbuchverlag Leipzig im Carl Hanser Verlag, 2003
2
Die Karte ist echt, die Funkmasten (glücklicherweise) nicht.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
3
Ein Graph besteht aus einer Menge von
Knoten und einer Menge von Kanten. Kanten
verbinden jeweils zwei Knoten miteinander.
Um Störungen im Funknetz zu vermeiden, dürfen
zwei Knoten, die über eine Kante miteinander
verbunden sind, nicht in derselben Frequenz
senden.
Eine mögliche Lösung wäre, die Frequenz für
einen Knoten festzulegen und dafür zu sorgen,
dass alle Knoten, die mit diesem Knoten
verbunden sind, auf einer anderen Frequenz
senden. Dann betrachtet man die Nachbarknoten
und sorgt dafür, dass ihre Nachbarn wiederum
Abbildung 5: Modellgraph
eine andere Frequenz nutzen. Nur muss man jetzt
aufpassen, dass man die Überlegungen für den ersten Knoten damit nicht wieder verwirft.
Das wird alles per Hand so unübersichtlich, dass man den Rechner zur Unterstützung braucht.
Vielleicht fällt jedem jetzt schon ein passender Algorithmus zur Frequenzplanung ein, aber
zunächst einmal muss geklärt werden, wie man diesen Graphen zur Verarbeitung mit dem Rechner
darstellen kann. Grundsätzlich gibt es zwei Möglichkeiten. Man verwendet zur Darstellung eines
Graphens entweder eine Adjazenzmatrix oder eine Adjazenzliste.
2. Darstellungen von Graphen
2.1 Adjanzenmatrix:
Wir repräsentieren einen Graphen als zweidimensionale Reihung oder Matrix, deren Einträge
angeben, ob eine Kante zwischen zwei Knoten existiert oder nicht:
A
B
A false true
C
D
E
F
G
true false true false false
B true false false true true false false
C true false false false false false false
D false true
false false true true false
E true true
false true false false true
F false false false true false false true
G false false false false true true false
Die Tabelle ist symmetrisch, was daran liegt, dass ein ungerichteter Graph zugrunde liegt. Wir
kommen an späterer Stelle noch einmal darauf zurück. Weiterhin ist der Graph auch unbewertet,
was sich ebenfalls in der Tabelle widerspiegelt, aber auch dazu später mehr. Kümmern wir uns
zunächst um den entsprechenden Code, der diese Matrix implementiert.
Unsere Knoten hatten die Bezeichnungen 'A' bis 'G'. Das kann in anderen Zusammenhängen anders
sein, weshalb eine Klasse Knoten interessant ist, die hier für unsere Aufgabe angepasst wird und
nur Daten vom Typ char berücksichtigt. Eine Anpassung für andere Zusammenhänge ist
ersichtlich.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
4
Bei der Erstellung eines neuen Knotens, wird die Bezeichnung
übergeben. Neue Knoten kann man in anderen Klassen erstellen mit
dem Befehl:
Wir verwenden diese Klasse nun, um in der Klasse Graph eine Knotenliste zu erstellen, die die
Knoten entsprechend der Reihenfolge in der sie eingefügt werden enthält3.
Bei der Erstellung eines neuen Graphen muss die
Gesamtgröße (hier: 7) angegeben werden.
Weiterhin wird die Adjazenzmatrix mit dem
Wert false initialisiert. Das Attribut anzahl gibt
den Index des ersten freien Platzes in der
Knotenliste an, bzw. die Anzahl bereits
eingefügter Knoten.
Wird ein Knoten eingefügt, ist der Rückgabewert
nur dann true, wenn das Einfügen erfolgreich
war. Ein Knoten kann nur eingefügt werden,
wenn die Anzahl der bereits vorhandenen Knoten
kleiner ist als die maximale Anzahl.
Eine Kante kann nur eingefügt werden, wenn die
zugehörigen Knoten existieren. Es wird zunächst
überprüft an welcher Stelle der Knotenliste diese
Knoten sind, also welchen Index sie haben. Dann
werden die entsprechenden Einträge der Adjazenzmatrix auf true gesetzt. Bei ungerichteten
Graphen ist eine Kante von A nach B, gleichzeitig auch eine Kante von B nach A.
Für ein Knoten wird in der Reihung knotenliste
gesucht, an welcher Stelle (Index) der Knoten zu
finden ist.
Eine Kante wird entfernt, indem die
entsprechenden Einträge in der Adjazenzmatrix
wieder auf false gesetzt werden.
3
Die Implementierungsidee findet sich in ähnlicher Form auch im Lehrbuch „Informatik 4. Rekursive Datenstrukturen.
Softwaretechnik“, Ernst Klett Verlag, 2009. Teile sind daraus entnommen.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
5
Um unseren Beispielgraphen einzugeben, benötigen wir folgende Aufrufe:
Jetzt sind wir in der Lage, einen Algorithmus zur Frequenzplanung in diesem Funknetz zu
entwickeln und vor allem zu implementieren. Doch zuvor noch eine weitere Darstellung von
Graphen zum Zweck einer Implementation. Die meisten Einträge in der Adjazenzmatrix sind
false. Für Kanten, die gar nicht vorhanden sind, sollte kein Speicherplatz unnötig allokiert werden.
Besser ist hier eine dynamische Datenstruktur, die dynamisch mit der Anzahl der Kanten wachsen
oder schrumpfen kann. Als Beispiel einer dynamischen Datenstruktur ist aus anderen
Zusammenhängen der Datentyp „Liste“ sicher bekannt. Adjazenzlisten, als Repräsentanten von
Graphen können diese Datenstruktur sinnvoll nutzen.
2.2. Adjazenzliste:
Die Knoten des Graphen werden in einer Liste gespeichert. Zu jedem Knoten wird eine weitere
Liste angelegt, in der die Nachbarn des Knoten gespeichert werden. Für unseren Graphen ergibt
sich folgende Adjazenzlistendarstellung:
Wobei grünlich unterlegt die Liste
der Knoten des Graphen dargestellt
ist und jeweils lila unterlegt die
Listen der Nachbarn.
Die unterschiedliche Länge der
Nachbarnlisten visualisiert gut die
Vorteile dynamischer Datenstrukturen.
Für den Java-Code bedienen wir uns
der Java-Klasse Vector, die eine
Implementierung des ADT Liste
darstellt.
Zunächst müssen wir die Klasse
Knoten
um eine Liste mit
Nachbarknoten erweitern:
Die Nachbarliste besteht ebenfalls aus
Knoten. Ansonsten bleibt die Klasse
Knoten unverändert.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
6
Die Knotenliste ist jetzt keine Reihung mehr, deren
Größe anfangs festgelegt werden muss, sondern eine
Liste.
Die Signatur der Methoden knotenEinfügen,
kanteEinfügen und kanteEntfernen bleibt erhalten.
Eine Kante „von“ Knoten „bis“ Knoten bedeutet,
dass der Knoten „bis“ als Nachbar in die Nachbarliste des Knotens „von“ eingefügt werden muss und
der Knoten „von“ als Nachbar des Knotens „bis“.
Gerichtete Graphen müssen an dieser Stelle später
modifiziert werden.
Die Methoden add und remove der Klasse Vector
vereinfachen die Methodenstruktur in der Klasse
Graph.
Nachdem die Klasse Graph nun auf die ein oder andere Weise implementiert ist, können wir uns um
die Algorithmen kümmern, die auf Graphen operieren. Algorithmen auf Graphen sind deshalb
interessant, weil viele Problemstellungen mit Hilfe von Graphen modelliert werden können. Im
ersten Beispiel ging es um die Frequenzplanung bei Sendestationen. Um Störungen zu verhindern,
sollten benachbarte Sendestationen nicht in derselben Frequenz senden, sofern sich ihre
Sendebereiche überlappen. Es bleibt jedem selbst überlassen, hier einen funktionierenden Algorithmus anzugeben. Allerdings müssen die Klasse Knoten und die Klasse Graph ein bisschen der
Problemstellung angepasst werden. Hilfreich für jede Art von Lösungsalgorithmus sind zusätzliche
Informationen an den Knoten, eine Markierung beispielsweise, die anzeigt ob ein Knoten schon
bearbeitet wurde oder ein Attribut, welches den gewählten Frequenzbereich für einen Knoten
aufnehmen kann. Diese Klassenerweiterungen der Klassen Knoten und Graph sind in allen
Algorithmen für Graphen notwendig, ihre Umsetzung dürfte jedoch bekannt sein, sie ist zumindest
in diesem Rahmen nicht Gegenstand der Betrachtung.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
7
3. Ein bisschen Graphentheorie
Ein sehr bekannter Graph ist zweifelsohne das „Haus vom Nikolaus“. Ein ganzes Buch widmet sich
diesem Graphen4.
Abbildung 6: Das Haus vom Nikolaus
Das linke der Häuser in Abbildung 6 zeigt einen Graphen, wie wir ihn aus dem ersten Kapitel
kennen mit Knoten und Kanten. Verbindet man nun nacheinander die Knoten A,B,C,D,B,E,A,D,E,
so kann man das Haus „zeichnen“ ohne eine Kante zweimal zu „zeichnen“. Eine Folge von Kanten,
die jeweils durch einen Knoten miteinander verbunden sind, nennt man Pfad. Oder formaler:
Unter einem Pfad von Knoten a nach Knoten b der Länge n in einem Graphen versteht man eine
Folge von n aufeinanderfolgenden Kanten.
Ist der Pfad zusätzlich geschlossen, also Anfangs- und Endknoten identisch, spricht man von einem
Zyklus. Im linken Graphen der Abbildung 6 finden sich die Zyklen A,B,E,A oder B, D, E, A, B
und andere.
Gibt es von jedem Knoten einen Pfad zu jedem anderen Knoten, wie das im linken Haus der
Abbildung 6 der Fall ist, spricht man von einem zusammenhängenden Graphen, jeder Knoten ist
von jedem anderen Knoten aus erreichbar.
Das rechte Haus der Abbildung 6 stellt einen gerichteten Graphen dar. Es existiert eine Kante von
A nach B aber keine Kante von B nach A. Hier muss bei einem Pfad zusätzlich die Richtung der
Kanten berücksichtigt werden. Der Pfad A,D,C des linken Graphen ist kein Pfad im rechten
Graphen. Der Pfad A,B,C,D,B,E,A,D,E ist allerdings auch hier vorhanden. Es ist leicht einzusehen,
dass die Adjazenzmatrix eines gerichteten Graphen nicht mehr symmetrisch sein muss. Allerdings
kann es natürlich Kanten von Knoten a nach Knoten b und von Knoten b nach Knoten a geben. Da
die Adjazenzmatrix nicht mehr symmetrisch ist, muss der Code etwas abgewandelt werden und vor
allem eine Vereinbarung getroffen werden, ob die Zeilen oder Spalten der Matrix den Startknoten
der Kante angeben.
Auch in gerichteten Graphen gibt es möglicherweise Zyklen. Bei gerichteten Graphen unterscheidet
man zwischen stark zusammenhängenden Graphen, wenn es von jedem Knoten einen Weg zu
jedem anderen Knoten gibt und schwach zusammenhängenden Graphen, wenn der Graph als
ungerichteter Graph (bei Ignoranz der Richtung) zusammenhängend wäre. Der obige gerichtete
Graph ist stark zusammenhängend.
4
Manfred Nitzsche: „Graphen für Einsteiger. Rund um das Haus vom Nikolaus“, Vieweg & Sohn Verlag, 2005
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
8
3.1. Eulersche Graphen
Eulersche Graphen sind Graphen, in denen man eine eulersche Tour finden kann, einen ganz
besonderen Pfad, der die folgenden Eigenschaften erfüllt:
• der Pfad enthält keine Kante doppelt
• der Pfad enthält sämtliche Kanten des Graphen
• Anfangs- und Endknoten stimmen überein.
Egal wie man das Haus vom Nikolaus auch zeichnet. Wir können die ersten beiden Anforderungen
erfüllen, denn das sind auch die Voraussetzungen für das Haus vom Nikolaus. Aber so sehr wir uns
bemühen, Punkt drei könnten wir nur erfüllen, wenn zwischen den Knoten A und E eine weitere
Kante eingezeichnet wäre. Das Haus vom Nikolaus ist also (egal ob ungerichtet oder nicht) kein
eulerscher Graph.
Man kann sogar mathematisch beweisen, dass ein zusammenhängender Graph genau dann eulersch
ist, wenn der Grad jedes Knotens gerade ist. (Der Grad eines Knotens ist dabei die Anzahl der
Kanten, die von diesem Knoten ausgehen).
3.2. Hamiltonsche Graphen
Ein Hamiltonscher Graph enthält einen hamiltonschen Kreis. Dies ist ein geschlossener Pfad, der
jeden Knoten des Graphen genau einmal enthält. Ein hamiltonscher Kreis geht also durch jeden
Knoten, muss aber nicht jede Kante des Graphen enthalten. Im Haus vom Nikolaus finden wir
schnell den Pfad A,B,C,D,E,A, der ein hamiltonscher Kreis ist und damit ist das Haus vom
Nikolaus ein hamiltonscher Graph. Wichtig ist, dass zwar Anfangs- und Endknoten gleich sind und
damit doppelt genannt werden, ansonsten aber jeder Knoten nur genau einmal vorkommen darf und
auch der Anfangs- und Endknoten zwischendurch kein weiteres Mal genannt werden darf.
Hamiltonsche Kreise werden uns später wieder begegnen. Damit sei der Graphentheorie jetzt aber
erst einmal genüge getan.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
9
4. Gewichtete Graphen und Dijkstra
Neben der Frequenzplanung in Funknetzen gibt es eine klassische Anwendung gewichteter Graphen
bei der Suche nach kürzesten Verbindungen zwischen zwei Orten. Folgende Abbildungen stammen
aus dem Buch „Abenteuer Informatik“ von Jens Gallenbacher5. Unter der Fragestellung: „Wie
funktioniert ein Routenplaner?“ wird folgende Straßenkarte mit Hilfe eines gewichteten Graphen
modelliert:
Zeichnung 1: Straßenkarte
Zeichnung 2: Graph
Die Ortschaften wurden dabei als Knoten dargestellt und mit ihrem Anfangsbuchstaben beschriftet.
An den Kanten (Straßen) stehen nun Gewichte, die in diesem Fall die Entfernungen zwischen den
Orten in km angeben.
Es ist ersichtlich, dass die Adjazenzmatrix diese Gewichte in irgendeiner Form aufnehmen muss.
Eine Möglichkeit ist die, eine zweidimensionale Reihung von int-Werten zu verwenden. Ein
Zelleneintrag ist dabei die Entfernung zwischen zwei Knoten oder 0, wenn keine Kante zwischen
diesen Knoten existiert. Bei der Verwendung von Adjazenzlisten müssen in die Liste der
Nachbarknoten noch Informationen über die Entfernung hinzugefügt werden. Dies kann man
realisieren, indem man die Klasse Knoten modifiziert und das Aufnehmen von Entfernungen
ermöglicht. Alternativ können die Gewichte an den Kanten natürlich auch Fahrtzeiten zwischen den
Orten sein oder ähnliches.
Jetzt möchte ein Reisender von Imstadt (I) nach Oppenheim (O) und zwar auf dem kürzesten Weg.
Denkt man zunächst, dass die direkte Verbindung über Pappstadt (P) mit 146 km günstig ist, kann
man nach gründlichem Hinsehen eine Route mit nur 123 km über Pappstadt (P), Krupsing (K) und
Flughafen (F) erkennen. Ein Algorithmus, der ausgehend von einem Startknoten die kürzesten
Wege zu allen anderen Knoten im Graphen berechnet, ist der Dijkstra-Algorithmus6, der im Folgenden vorgestellt werden soll.
Um ein Beispiel gründlicher zu bearbeiten mit einem bewerteten (gewichteten) und gerichteten
Graphen, verwenden wir nicht die obige Karte, sondern folgendes Modell einer Straßenkarte mit
vielen Einbahnstraßen ;-) und die entsprechende Adjazenzmatrix:
5
6
Jens Gallenbacher: „Abenteuer Informatik“, Spektrum Verlag, 2007
Benannt nach dem Erfinder Edsger Wybe Dijkstra (1930 – 2002)
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
A
B
C
D
E
S.
10
F
A
B
10
C
D
20
20
70
10 70
E
Abbildung 7: gerichteter gewichteter
Graph
F
10
100
40
40
20
Freie Felder (keine Kanten) werden später mit 0 initialisiert. Eine Kante von A nach B bedeutet
einen Zelleintrag in die Zelle[B][A]. Dies kann natürlich auch genau anders herum realisiert
werden.
Wir wählen als Startknoten Knoten A und suchen den kürzesten Weg zum Knoten F. Außerdem
wollen wir die kürzeste Entfernung von A zu F berechnen. Dabei werden wir uns Stück für Stück
vorarbeiten, ausgehend vom Startknoten zunächst die Nachbarknoten betrachten, die wiederum ihre
Nachbarn betrachten usw. Auf dem Weg müssen wir einige Dinge zwischenspeichern. Das tun wir,
indem die Knoten noch weitere Informationen aufnehmen. Ein Knoten muss als „bearbeitet“
markiert werden können, was wir dadurch realisieren, dass wir ihn anders farbig zeichnen.
Außerdem muss er die Information über sein aktuelles Pfadgewicht (in rot) und seinen aktuellen
Vorgängerknoten des bisherigen kürzesten Weges speichern können (in blau). Unter Pfadgewicht
verstehen wir die Summe der Kantengewichte entlang eines Pfades. Aber das wird gleich alles noch
verständlicher, keine Sorge. Außerdem brauchen wir eine Hilfsliste, die alle Knoten aufnimmt und
stets automatisch nach der Größe der Pfadgewichte (aufsteigend) sortiert. Jetzt geht es los. Wir
beginnen mit der Initialisierung des Graphen und der sortierten Hilfsliste:
Knoten
A
B
C
D
E
F
aktuelles
Pfadgewicht
0
∞
∞
∞
∞
∞
Vorgänger
start null null
null null null
bearbeitet?
nein
nein
nein nein
nein nein
Zunächst werden alle Referenzen auf die Vorgänger (außer beim Startknoten) auf null gesetzt und
die Pfadgewichte auf ∞ .
Wir suchen jetzt den ersten nicht bearbeiteten Knoten in der sortierten Liste. Nicht schwer, das ist
Knoten A. Wir bearbeiten einen Knoten, indem wir für alle seine Nachfolgeknoten die Einträge in
der Liste aktualisieren.
Es ergibt sich folgendes Bild: (Die Liste wird automatisch entsprechend der Pfadgewichte sortiert.)
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
11
Knoten
A
B
D
F
C
E
aktuelles
Pfadgewicht
0
10
20
100
∞
∞
Vorgänger
start A
A
A
null null
bearbeitet?
ja
nein
nein nein
nein nein
Der erste nicht bearbeitete Knoten der Hilfsliste ist jetzt Knoten B. Bearbeitet man Knoten B,
werden die Einträge für die Nachbarknoten (hier: C und D) aktualisiert. Da wir aber kürzeste Wege
suchen, aktualisieren wir Pfadgewicht und Vorgänger nur dann, wenn das Pfadgewicht dadurch echt
kleiner wird. Diesmal ist das nicht der Fall, denn D hat ein aktuelles Pfadgewicht von 20 und ein
Weg über B zu D würde ebenfalls 20 ergeben, also nicht echt kleiner sein. Es ergibt sich somit
folgende Darstellung:
Knoten
A
B
D
C
F
E
aktuelles
Pfadgewicht
0
10
20
30
100
∞
Vorgänger
start A
A
B
A
null
bearbeitet?
ja
nein
nein
nein nein
ja
Das aktuelle Pfadgewicht bei Knoten C entspricht 30, weil das Pfadgewicht von Knoten B = 10 ist
und Knoten B der Vorgänger von C ist. Außerdem beträgt das Kantengewicht von Knoten B nach
Knoten C = 20 und da 20 + 10 = 30, ergibt sich für C ein Pfadgewicht von 30.
Unser nächster Knoten in der Liste ist Knoten D. Wir erhalten:
Knoten
A
B
D
C
E
F
aktuelles
Pfadgewicht
0
10
20
30
60
100
Vorgänger
start A
A
B
D
A
bearbeitet?
ja
ja
nein
nein nein
ja
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
12
Der Pfad nach C über Knoten D ist 90 km lang (20+70) und stellt keine Verbesserung dar, weshalb
der Eintrag von C nicht aktualisiert wird.
Doch bei der Bearbeitung von Knoten C passiert zum ersten Mal das, was man eine Relaxation
nennt. Es findet tatsächlich eine Veränderung aktueller Pfadgewichte statt.
Knoten
A
B
D
C
E
F
aktuelles
Pfadgewicht
0
10
20
30
40
70
Vorgänger
start A
A
B
C
C
bearbeitet?
ja
ja
ja
nein nein
ja
Das aktuelle Pfadgewicht von Knoten E und Knoten F wird nach unten korrigiert und entsprechend
werden ihre Vorgängerknoten verändert. Warum ist das nun so? Knoten E hatte ein aktuelles
Pfadgewicht von 60 bei einem Weg über den Vorgängerknoten D. Knoten C hat ein aktuelles
Pfadgewicht von 30. Das Kantengewicht zwischen Knoten C und Knoten E ist 10. Da 30+10=40
und außerdem 40 < 60 ist, verläuft der bislang kürzeste Weg über Knoten C und ist 40 km lang.
Entsprechendes gilt für F. Ein Weg über Knoten C hat ein Pfadgewicht von 30+40=70 und ist damit
echt kleiner als 100, die Länge bei einem Weg über A.
Im nächsten Schritt finden wir erneut eine Relaxation bei Knoten F:
Knoten
A
B
D
C
E
F
aktuelles
Pfadgewicht
0
10
20
30
40
60
Vorgänger
start A
A
B
C
E
bearbeitet?
ja
ja
ja
ja
nein
ja
Da Knoten F keine Nachfolgeknoten hat, sind wir mit der Bearbeitung am Ende. Der kürzeste Weg
von Knoten A zu Knoten F ist 60 km lang. Den Wert können wir direkt aus der Liste entnehmen.
Aber kann man auch rekonstruieren, wo der Weg entlang führt? Ja, man verfolgt einfach die
Vorgängerknoten in der Liste zurück. Das Ziel war Knoten F. Für den kürzesten Weg muss der
Vorgängerknoten E sein. Der Vorgänger von E ist nach der Liste C, dessen Vorgänger B und dessen
Vorgänger A ist. Der Weg A,B,C,E,F stellt also die kürzeste Verbindung zwischen A und F dar.
Bleibt nur noch eine letzte Aufgabe in diesem Kapitel: Die Implementation in Java.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
13
Als Vorüberlegung fassen wir den Dijkstra-Algorithmus zunächst in einem Struktogramm
zusammen:
Um das zu realisieren, passen wir zunächst einmal die Klasse Knoten etwas an:
Zusätzlich zu einem Namen müssen die Knoten Informationen über das
aktuelle Pfadgewicht, ihren Vorgänger und ihren Bearbeitungsstatus
aufnehmen können.
Für die Hilfsliste werden die Attribute entsprechend dem Beispiel auf
unendlich bzw. null gesetzt.
Mit dieser Methode kann ein Startknoten ausgewählt werden.
set- und get-Methoden für das aktuelle Pfadgewicht.
Außerdem set- und get-Methoden für den Bearbeitungsstatus und den
Vorgänger, der ja bei der Relaxation verändert werden muss.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
14
Jetzt sehen wir uns ein Beispiel für eine Hilfsliste an, die Objekte der Klasse Knoten aufnehmen
kann. Die Hilfsliste muss über eine Sortierfunktion verfügen, die die Knoten entsprechend ihres
aktuellen Pfadgewichtes aufsteigend sortiert.
Die Hilfsliste mit vorher festzulegender Größe nimmt
Knoten der eben besprochenen Klasse auf.
Mit der Methode
werden.
add
können Knoten der Liste hinzugefügt
Diese Methode sorgt dafür, dass die Liste sortiert wird,
entsprechend den Pfadgewichten der einzelnen Knoten. Es
wird ein einfacher SelectionSort-Algorithmus verwendet.
Die Hilfsmethode swap vertauscht dabei zwei Elemente der
Knotenliste.
Für den Dijkstra-Algorithmus gibt diese Methode zurück, ob
alle Knoten der Hilfsliste schon bearbeitet sind oder nicht.
Diese Methode gibt den ersten unbearbeiteten Knoten der
Hilfsliste zurück.
Auch die Klasse Graph muss noch etwas verändert werden, bevor wir den Dijkstra-Algorithmus
angeben können.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
15
Wie bereits angekündigt, verwenden wir
eine Adjazenzmatrix, wobei der erste Index
die Zeile und der zweite die Spalte angibt.
Zunächst wird die Adjazenzmatrix mit 0en
initialisiert.
Diese Methode bleibt unverändert
Da die Matrix nun nicht mehr symmetrisch
ist (gerichteter Graph), muss man auf die
Vereinbarung achten, wie Zeilen und
Spalten gefüllt werden müssen.
Auch diese Methode bleibt unverändert
Die Klasse Graph bekommt eine Methode,
die die Nachfolger zu jedem Knoten
zurückgeben kann. Dazu wird für einen
Knoten in der Adjazenzmatrix nachgesehen, welche Einträge ungleich 0 sind.
Hier müssen Nachbarn zu finden sein. Die
zurückzugebende Reihung wird um ein
Element erweitert, nämlich dem gefundenen Nachfolger.
Außerdem kann das Kantengewicht
zwischen zwei Knoten zurück gegeben
werden.
Virtuelle Lehrerweiterbildung Informatik in Niedersachsen
Kerstin Strecker
Graphen – Teil 1
S.
16
Und hier ist er endlich: Der Dijkstra-Algorithmus in Java implementiert, gemäß des vorgegebenen
Struktogramms:
Herunterladen