13.05.2015 Aufrufe

Graphen

Graphen

Graphen

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

Algorithmen und Datenstrukturen 2<br />

Prof. Dr. C. Stamm, B. Scheuner christoph.stamm@fhnw.ch Tel.: 056 462 47 44<br />

1 Einführung in <strong>Graphen</strong><br />

Ein Graph ist eine Menge von Knoten, welche durch Kanten verbunden sind. Bis jetzt haben Sie eine<br />

spezielle Teilmenge der <strong>Graphen</strong> die Bäume kennen gelernt. Es gibt jedoch noch viele andere Arten<br />

von <strong>Graphen</strong>. <strong>Graphen</strong> dienen ganz allgemein der Veranschaulichung von Zusammenhängen zwischen<br />

verschiedenen Objekten. Dabei werden die Objekte meistens als Knoten und die Zusammenhänge<br />

als Kanten dargestellt.<br />

Ein ganz offensichtliches Beispiel ist das Internet. Es<br />

besteht aus Endgeräten und Routern (Knoten) und aus<br />

Netzwerk-Verbindungen (Kanten) dazwischen. Ein Problem<br />

im Internet ist das Auffinden einer möglichst<br />

schnellen Verbindung zwischen zwei Endgeräten. Üblicherweise<br />

gibt es zwischen zwei Knoten immer mehr als<br />

nur eine Verbindung. Aber welche dieser Verbindungen<br />

?<br />

ist nun die kürzeste oder die schnellste<br />

Das nebenstehende Liniennetz eines Verkehrsbetriebes<br />

ist auch ein Graph. An jeder Haltestelle können Sie sich<br />

ein Bild dieses <strong>Graphen</strong> ansehen. Hier fragen sich die<br />

Benutzer, welches der schnellste oder billigste Weg von<br />

einer Haltestelle zur nächsten ist, oder auf welcher<br />

ssen.<br />

Route sie am wenigsten umsteigen mü<br />

Man kann aber noch ganz andere Probleme als <strong>Graphen</strong><br />

darstellen. Z.B. kann das Finden einer besten (z.B.<br />

kürzesten, einfachsten, schnellsten) Besichtigungstour<br />

einer Stadt mit vielen Sehenswürdigkeiten als <strong>Graphen</strong>problem<br />

interpretiert werden. Jede Sehenswürdigkeit<br />

wird dabei als Knoten dargestellt. Zwei Knoten sind<br />

genau dann mit einer Kante verbunden, wenn die beiden<br />

Sehenswürdigkeiten über einen direkten Weg verbunden<br />

sind. Nun möchten Sie z.B. eine Tour finden, bei der Sie<br />

alle Sehenswürdigkeiten besichtigen können und dabei<br />

so wenig wie möglich Kilometer zurücklegen müssen.<br />

Sie sehen, dass <strong>Graphen</strong> in unserer modernen Welt sehr<br />

weit verbreitet sind. Sehr viele Situationen lassen sich in einem Graph veranschaulichen; meist mit<br />

dem Ziel, Probleme zu lösen. Das Lösen dieser Probleme ist Gegenstand der <strong>Graphen</strong>theorie. Als<br />

erstes werden wir nun definieren, was ein Graph ist, und welche Eigenschaften er hat. Danach werden<br />

wir einige bekannte Algorithmen betrachten.<br />

In Wikipedia 1 finden Sie ein Glossar der <strong>Graphen</strong>theorie mit ca. 250 Begriffen. Ein Teil dieser Begriffe<br />

kennen Sie schon von den Bäumen und aus dem Fach Diskrete Mathematik.<br />

1.1 Definitionen<br />

Graph: Ein Graph besteht aus einer Knotenmenge V (vertices) und einer Kantenmenge E (edges).<br />

Dabei gilt:<br />

(i) |V| = n > 0<br />

(ii) E = {(u, v) | u, v ∈ V} wobei u und v nicht verschieden sein müssen. Eine Kante ist also ein<br />

Paar von Knoten. Falls u = v gilt, dann sprechen wir von einer Schleife oder Schlinge, also<br />

einer Kante, welche einen Knoten mit sich selber verbindet.<br />

|E| = m ≥ 0<br />

Notation: V = {1, 2, ..., n} und E = {(1,2), (3,2), ..., (2,4)}<br />

Knoten: Der Grad eines Knoten in einem <strong>Graphen</strong> entspricht der Menge der Kanten, welche den<br />

Knoten als (einen) Anfangs- bzw. Endpunkt haben. Der Grad wird angegeben als deg(v). Eine<br />

1 http://de.wikipedia.org/wiki/Glossar_<strong>Graphen</strong>theorie<br />

1


Kante ist zu ihren Anfangs- und Endknoten inzident. Alle Knoten, mit denen ein Knoten durch eine<br />

Kante verbunden ist, heissen adjazente Knoten. Diese adjazenten Knoten werden meist auch als<br />

Nachbarn bezeichnet.<br />

Kanten: Eine Kante verbindet zwei Knoten miteinander. Dabei kann sie eine Richtung haben (gerichtet),<br />

oder aber auch ungerichtet sein. Wenn sie eine Richtung hat, wird sie als Pfeil gezeichnet<br />

und als Paar von Knoten (u, v) symbolisiert, wobei u der Startknoten und v der Zielknoten ist. Eine<br />

ungerichtete Kante wird als Verbindungslinie gezeichnet und als Menge {u, v} symbolisiert.<br />

Neben der Richtung kann eine Kante auch noch ein Gewicht haben. Solche Kanten heissen gewichtete<br />

Kanten. Gewichte stellen zum Beispiel die Entfernung zweier Knoten oder die Bandbreite<br />

einer Internetleitung dar.<br />

Sind zwei Knoten durch mehrere direkte Kanten verbunden, wird diese Gruppe als Mehrfachkante<br />

zusammengefasst.<br />

Ist ein Knoten mit sich selber durch eine Kante verbunden, wird diese Kante als Schlinge bezeichnet.<br />

(Für den Grad eines Knoten wird diese Kante zweimal gezählt.)<br />

Zusammenhängende <strong>Graphen</strong>: Ein Graph ist zusammenhängend, wenn von jedem Knoten jeder<br />

andere Knoten über Kanten erreicht werden kann. Ist ein Graph nicht zusammenhängend, besteht<br />

er aus mehreren Komponenten. Das Tramnetz einer Stadt ist üblicherweise ein zusammenhängender<br />

Graph. Alle Tramnetze der Schweiz gemeinsam betrachtet, sind jedoch ein nicht zusammenhängender<br />

Graph.<br />

2<br />

Mehrfachkante<br />

3<br />

0<br />

3<br />

Schlinge<br />

1 2<br />

3<br />

Baum: Ein Baum ist ein spezieller zusammenhängender Graph. Er zeichnet sich dadurch aus, dass<br />

er keine Kreise und somit auch keine Schlingen enthält (siehe Unterkapitel Kreise).<br />

1.2 Einfache <strong>Graphen</strong><br />

Ein einfacher Graph ist ein ungerichteter Graph ohne Mehrfachkanten und ohne Schlingen. In einfachen<br />

<strong>Graphen</strong> und auch in allen anderen ungerichteten <strong>Graphen</strong> können folgende Aussagen gemacht<br />

werden:<br />

• Die Summe aller Knotengrade ist gerade, sie entspricht dem Zweifachen der Anzahl Kanten m.<br />

∑ v in V deg(v) = 2⋅m<br />

• Die Anzahl der Knoten mit ungeradem Knotengrad ist gerade.<br />

Beweis: Die Summe aller Knotengrade ist gerade. Die Summe aller geraden Knotengrade ist gerade.<br />

Eine gerade Zahl minus eine gerade Zahl muss wieder gerade sein. Somit ist die Summe aller<br />

ungeraden Grade wieder gerade. Diese Summe kann nun nur gerade sein, wenn es eine gerade<br />

Anzahl von ungeraden Graden gibt.<br />

Formal: ∑ v in V deg(v) = ∑ v in V deg gerade (v) + ∑ v in V deg ungerade (v) = 2 m<br />

∑ v in V deg gerade (v) ist gerade, weil die Summe von geraden Zahlen immer gerade ist.<br />

∑ v in V deg ungerade (v) muss auch gerade sein, weil die Summe der beiden gerade ist.<br />

Nehmen wir nun an, dass wir eine ungerade Anzahl ungerader Grade haben. Dann können<br />

wir immer Paare (mit gerader Summe) bilden, bis auf einen Grad. Dass diese Summe nie gerade<br />

sein kann ist einleuchtend.<br />

1.2.1 Vollständige <strong>Graphen</strong> (K n )<br />

K 2<br />

K 3 K 4<br />

K 5<br />

Ein vollständiger Graph ist ein einfacher Graph. Er hat aber die zusätzliche Eigenschaft, dass von<br />

jedem Knoten aus alle andern Knoten direkt mit einer Kante verbunden sind. Solche <strong>Graphen</strong> können<br />

für die Darstellung eines Tourniers verwendet werden, bei dem jedes Team gegen jedes andere Team<br />

spielen muss.<br />

2


1.2.2 Kreise (C n )<br />

Ein Kreis ist ein einfacher Graph, bei dem alle Knoten den<br />

Grad 2 haben. Von jedem Knoten kommt man auf zwei Wegen<br />

zu jedem anderen Knoten.<br />

1.2.3 Bipartite <strong>Graphen</strong><br />

Ein einfacher Graph heisst bipartit, wenn die Knotenmenge in zwei disjunkte Teilmengen aufgeteilt<br />

werden kann und wenn alle Kanten nur Verbindungen zwischen den beiden Teilmengen herstellen. Es<br />

gibt also keine Kanten zwischen zwei Knoten aus der gleichen Teilmenge.<br />

Ein bipartiter Graph heisst vollständig, wenn jeder Knoten aus der einen Teilmenge mit allen Knoten<br />

aus der andern Teilmenge verbunden ist. Wir bezeichnen einen vollständigen bipartiten Graph mit<br />

(K a,b ).<br />

Solche <strong>Graphen</strong> können zum Beispiel bei einer Partnerschaftsvermittlung gezeichnet werden. Jeder<br />

Mann wird mit einer potentiellen Partnerin verbunden und umgekehrt.<br />

K 3,4 K 4,1<br />

1.2.4 Hyperwürfel<br />

Eine etwas spezielle Art eines <strong>Graphen</strong> ist der Hyperwürfel. Dieser Graph ist wiederum ein einfacher<br />

Graph. Seine Knoten haben jedoch ganz bestimmte Namen. Ein Hyperwürfel der Dimension d hat die<br />

Knotenmenge, welche aus allen binären Folgen der Länge d besteht. Formal heisst dies: V= { {0,1} d }.<br />

Eine Kante verbindet immer genau dann zwei Knoten, wenn sich deren Binärfolge in nur einer Stelle<br />

unterscheiden. Z.B. werden die zwei Knoten mit den Binärfolgen 1011 und 1001 miteinander verbunden.<br />

Solche Hyperwürfel werden zum Beispiel in der Codierungstheorie verwendet.<br />

1.2.5 Planare <strong>Graphen</strong><br />

Planare <strong>Graphen</strong> sind einfache <strong>Graphen</strong>, welche so gezeichnet werden können, dass sich keine zwei<br />

Kanten überschneiden. Über planare <strong>Graphen</strong> lassen sich sehr viele Aussagen machen. Zwei davon<br />

werden hier erläutert. Um diese Aussagen jedoch zu verstehen, muss erst noch ein neuer Begriff<br />

eingeführt werden:<br />

Gebiet oder Fläche: Ein Gebiet ist eine zusammenhängende Fläche, welche von mindestens drei<br />

Kanten begrenzt wird.<br />

1. Bäume sind planar.<br />

Idee: Durch ihre spezielle Struktur können alle Bäume so gezeichnet werden, dass sich keine zwei<br />

Kanten überschneiden.<br />

2. Eulersche Polyederformel<br />

Sei G ein planarer, zusammenhängender, einfacher Graph mit n Knoten, m Kanten und f Gebieten.<br />

Dann gilt: n – m + f = 2.<br />

Beweis: Aus 1 ist bekannt, dass Bäume planar sind. In jedem <strong>Graphen</strong> kann ein Baum (Spannbaum)<br />

aufgespannt werden, der alle Knoten beinhaltet.<br />

Ein Baum mit n Knoten hat m = n – 1 Kanten. Es existiert nur ein Gebiet, f = 1. Daraus folgt:<br />

n – (n – 1) + 1 = 2.<br />

Nun werden die restlichen Kanten des <strong>Graphen</strong> sukzessive hinzugefügt. Dabei fällt auf, dass bei<br />

jeder Kante die hinzugefügt wird, ein neues Gebiet erzeugt wird.<br />

Die beiden Bilder zeigen die zwei Möglichkeiten.<br />

Entweder wird vom umgebenden Gebiet<br />

ein Teilgebiet abgetrennt oder ein inneres<br />

Gebiet wird geteilt. Auf diese Weise werden die<br />

fehlenden k Kanten hinzugefügt.<br />

n – (n – 1 + k) + 1 + k = n – n +1 – k +1 + k = 2<br />

3


3. Das Verhältnis von Gebieten zu Kanten: 3f ≤ 2m<br />

Beweis: Ein Gebiet wird von mindestens drei Kanten begrenzt. Eine Kante begrenzt immer zwei<br />

Gebiete. 3f bezeichnet alle Randkanten. Dabei ist aber jede Kante doppelt gezählt.<br />

4. Kanten und Regionen in planaren <strong>Graphen</strong><br />

Für jeden einfachen planaren <strong>Graphen</strong> G = (V,E) mit |V| ≥ 3 gilt:<br />

• m ≤ 3n – 6<br />

• f ≤ 2n – 4<br />

Beweis: n – m + f = 2 und 3f ≤ 2m sind gegeben. Aus der Polyederformel folgt: f = 2 – n + m. Setzen<br />

wir diesen Wert für f in (3f ≤ 2m) ein erhalten wir: 3(2 – n + m) ≤ 2m. Daraus folgt:<br />

6 – 3n + 3m ≤ 2m ⇒ 6 – 3n + m ≤ 0 ⇒ m ≤ 3n – 6.<br />

Die zweite Aussage lässt sich analog dazu beweisen (Hausaufgabe).<br />

5. K 5 und K 3,3 sind nicht planar.<br />

K 5 oder K 3,3 sind die beiden kleinsten nicht planaren <strong>Graphen</strong>, was direkt aus dem Satz von Kuratowski<br />

2 folgt. Der Satz von Kuratowski sagt: „Ein endlicher Graph ist genau dann planar, wenn er<br />

keinen Teilgraphen enthält, der durch Unterteilung von K 5 oder K 3,3 entstanden ist. Unterteilung bedeutet<br />

hier das beliebig oft wiederholbare (auch nullmalige) Einfügen von neuen Knoten auf Kanten.<br />

Mit Teilgraph ist hier ein Graph gemeint, der aus dem ursprünglichen <strong>Graphen</strong> durch Entfernen von<br />

Knoten bzw. Kanten entsteht.“ Somit erlaubt der Satz von Kuratowski zu entscheiden, ob ein Graph<br />

planar ist oder nicht.<br />

Ein anderer Beweis, dass K 5 nicht planar ist, folgt aus den Forderungen aus Punkt 4.<br />

Beweis für K 5 : Dieser Graph besitzt n = 5 Knoten und 10 Kanten. In (m ≤ 3n – 6) ergibt dies<br />

10 ≤ 15 – 6 = 9 (stimmt also nicht)<br />

1.3 Gerichtete <strong>Graphen</strong> (Digraphen)<br />

Im Unterschied zu den allgemeinen <strong>Graphen</strong> besitzen die Kanten gerichteter <strong>Graphen</strong> eine Richtung.<br />

Das bedeutet, dass eine Kante zwischen zwei Knoten nur in einer Richtung durchlaufen werden darf.<br />

Gerichtete <strong>Graphen</strong> finden ihre Anwendung in ganz verschiedenen Gebieten. In einem Projektplan<br />

werden die Aktivitäten mit einer gerichteten Kante verbunden um anzuzeigen, wie der zeitliche Ablauf<br />

sein muss. In einer Strassenkarte werden Einbahnstrassen mit einer Richtung versehen.<br />

Eingangsgrad: Der Eingangsgrad (Indegree) eines Knoten entspricht der Anzahl gerichteter<br />

Kanten, die in den Knoten hineinführen: indeg(v)<br />

Ausgangsgrad: Der Ausgangsgrad (Outdegree) eines Knoten entspricht der Anzahl gerichteten<br />

Kanten, die aus dem Knoten hinausführen: outdeg(v)<br />

Die Summe der Eingangsgrade aller Knoten ist gleich der Summe der Ausgangsgrade aller Knoten.<br />

∑ indeg(v) = ∑ outdeg(v)<br />

2 z.B. http://de.wikipedia.org/wiki/Satz_von_Kuratowski<br />

4


1.4 Lernkontrolle<br />

1.4.1 <strong>Graphen</strong>eigenschaften<br />

Hier sind einige <strong>Graphen</strong> abgebildet. Geben Sie für jeden <strong>Graphen</strong> die Grade der Knoten an. Versuchen<br />

Sie dann so viele Eigenschaften wie möglich festzuhalten. Mit Eigenschaften sind hier gemeint:<br />

• Zu welcher Gruppe von <strong>Graphen</strong> gehört der Graph? Z.B. Bäume, Kreise, vollständig bipartite<br />

<strong>Graphen</strong>, einfacher Graph, etc.<br />

• Ist der Graph zusammenhängend?<br />

• Etc.<br />

a) b) c)<br />

d)<br />

e)<br />

f)<br />

1.4.2 Summe der Knotengrade<br />

Im Kapitel über einfache <strong>Graphen</strong> wurde gesagt, dass es immer eine gerade Anzahl von Knoten mit<br />

ungeradem Grad geben muss. Wie sieht dies für die Anzahl der Knoten mit geradem Grad aus?<br />

1.4.3 Planare <strong>Graphen</strong><br />

Lassen sich die die untenstehenden <strong>Graphen</strong> planar zeichnen? Be- oder widerlegen Sie die Planarität<br />

mit den Formeln aus dem Kapitel über planare <strong>Graphen</strong>. Zeichnen Sie die planaren <strong>Graphen</strong> planar.<br />

Schreiben Sie auch dazu, was die <strong>Graphen</strong> darstellen.<br />

a) b)<br />

c) d)<br />

1.4.4 Kurzaufgaben<br />

a) Wie viele Kanten hat der K 5 ?<br />

b) Wie viele Kanten hat der K n ?<br />

c) Wie viele Kanten hat der K 3,3 ?<br />

d) Wie viele Kanten hat der K a,b ?<br />

e) Wie viele Kanten hat der C 4 ?<br />

f) Wie viele Kanten hat der C n ?<br />

g) Zeichnen Sie für die folgende Situation einen <strong>Graphen</strong>, der die Situation widergibt: Sie laden 5<br />

Freunde ein. Jeder Gast kennt zwei andere Gäste. Zeichnen Sie einen zusammenhängenden<br />

<strong>Graphen</strong>.<br />

h) Kann ein Graph mit 11 Knoten und 30 Kanten planar sein?<br />

i) Kann ein Graph mit 5 Knoten und 6 Gebieten planar sein?<br />

j) Kann ein Graph mit 5 Knoten, 10 Kanten und 8 Gebieten planar sein?<br />

5


2 Speicherung von <strong>Graphen</strong><br />

Nach der Einführung kennen Sie die grundlegende Struktur von <strong>Graphen</strong>. In vielen Computerprogrammen<br />

wird mit <strong>Graphen</strong> gearbeitet, z.B. Navigation, Routenplanung im Abfuhrwesen, Internet<br />

Routing Protokoll. Alle diese Programme speichern in irgendeiner Form einen oder mehrere verschiedene<br />

<strong>Graphen</strong>. Oft werden diese <strong>Graphen</strong> nicht nur im Hauptspeicher sondern auch auf dem Externspeicher<br />

gehalten.<br />

Die Art der Speicherung der <strong>Graphen</strong> kann auf die Effizienz der auszuführenden Algorithmen einen<br />

Einfluss haben. Das ist eigentlich nichts Neues, wie Sie schon längst bei anderen Datenstrukturen<br />

gesehen haben. Aus diesem Grund ist es wichtig, <strong>Graphen</strong> speicher- und laufzeiteffizient abzuspeichern.<br />

Im Folgenden lernen Sie vier unterschiedliche Arten der Speicherung kennen.<br />

2.1 Adjazenzmatrix<br />

Der Beispielgraph ist der Graph G=(V,E) mit V={1,2,3,4,5} und E= {(1,3), (1,3), (3,4), (2,3), (2,5), (5,5)}<br />

1<br />

3<br />

4<br />

2<br />

5<br />

1 2 3 4 5<br />

1 0 0 2 0 0<br />

2 0 0 1 0 1<br />

3 0 0 0 1 0<br />

4 0 0 0 0 0<br />

5 0 0 0 0 1<br />

In einer Adjazenzmatrix werden adjazente Knotenpaare gespeichert. Es wird vermerkt, welche Knoten<br />

durch eine Kante miteinander verbunden sind.<br />

Die Adjazenzmatrix ist eine Matrix der Grösse n × n, wobei n die Anzahl der Knoten bezeichnet. Sind<br />

zwei Knoten (u und v) mit einer gerichteten Kante verbunden wird in der Adjazenzmatrix in der u-ten<br />

Zeile und der v-ten Spalte eine 1 eingetragen. Sind die beiden Knoten mit mehr als einer Kante verbunden,<br />

wird die Anzahl der Kanten eingetragen. Für u und v gilt 1 ≤ u ≤ n, 1 ≤ v ≤ n.<br />

Im Beispiel ist ein Digraph mit seiner Adjazenzmatrix dargestellt. Es ist erkennbar, dass Mehrfachkanten<br />

zusammengezählt werden. Treten Schlingen auf, sind diese in der Diagonalen der Matrix ersichtlich.<br />

Besteht die Diagonale aus lauter Nullen, ist der Graph schlingenfrei.<br />

In ungerichteten <strong>Graphen</strong> ist die Matrix symmetrisch, weil sowohl (u, v) = 1 als auch (v, u) = 1 eingetragen<br />

wird. Selbstverständlich lässt sich in diesem Fall die Speicherung leicht optimieren, indem nur<br />

eine Dreiecksmatrix gespeichert wird.<br />

Wenn der Graph gewichtet ist, dann wird das Gewicht der Kante in die Zelle eingetragen. Bei gewichteten<br />

<strong>Graphen</strong> ist es unüblich, dass Mehrfachkanten auftreten (je nach Problem müsste dieser Umstand<br />

anders behandelt werden). Man kann in einer Adjazenzmatrix nicht unterscheiden, ob die Kanten<br />

gewichtet sind oder ob Mehrfachkanten aufgetreten sind.<br />

Bemerkung: Bei dieser Art der Speicherung mit einem herkömmlichen Array werden die Knoten nur<br />

implizit in Form des Array-Index gespeichert. Alle weiteren Eigenschaften von Knoten müssen separat<br />

gespeichert werden.<br />

2.2 Inzidenzmatrix<br />

Der Beispielgraph ist der Graph G = (V, E) mit V = {1, 2, 3, 4, 5} und E = {{1,3}, {1,3}, {3,4}, {2,3}, {2,5},<br />

{5}}<br />

2<br />

1<br />

3<br />

1<br />

3<br />

4<br />

4<br />

2<br />

5<br />

6<br />

5<br />

1 2 3 4 5 6<br />

1 1 1 0 0 0 0<br />

2 0 0 0 1 1 0<br />

3 1 1 1 1 0 0<br />

4 0 0 1 0 0 0<br />

5 0 0 0 0 1 1<br />

In der Inzidenzmatrix wird eingetragen, welche Kanten zu welchen Knoten inzident sind.<br />

Die Inzidenzmatrix ist eine (n × m)-Matrix. Dabei werden nicht nur die Knoten nummeriert, sondern<br />

auch die Kanten. Gehört eine Kante e mit Index j zu einem Knoten v, wird in der v-ten Zeile und der j-<br />

6


ten Spalte eine 1 eingetragen. Ansonsten wird eine 0 eingetragen. Für v und j gelten 1 ≤ v ≤ n, 1 ≤ j ≤<br />

m.<br />

Im Beispiel ist eine Inzidenzmatrix für einen ungerichteten <strong>Graphen</strong> gezeigt. Für gerichtete <strong>Graphen</strong><br />

kann die Darstellung nicht einfach übernommen werden. Mann muss definieren können, ob ein inzidenter<br />

Knoten der Ursprung oder das Ziel ist. Man könnte z.B. für Ursprung eine 1 eintragen und für<br />

Ziel eine -1.<br />

Gewichte der Kanten können in der Inzidenzmatrix berücksichtigt werden indem der Wert des Gewichts<br />

in der Matrix eingetragen wird.<br />

Bemerkung: Bei dieser Art der Speicherung werden Knoten und Kanten nur implizit in Form ihrer<br />

Indizes erfasst. Zusätzliche Informationen zu Kanten und Knoten müssen deshalb in einer separaten<br />

Struktur gespeichert werden.<br />

2.3 Adjazenzlisten<br />

Der Beispielgraph G = (V, E) ist gerichtet aber ungewichtet.<br />

Es sei: G = (V, E) mit V = {1, 2, ..., 9} und E = {(1,2),<br />

(1,3), (1,7), (4,6), (5,4), (6,1), (6,5), (6,6), (7,5), (9,8)}<br />

Wie bei der Adjazenzmatrix werden bei dieser Speicherung<br />

die Verbindungen zwischen zwei Knoten in den<br />

Vordergrund gestellt. Es wird also gespeichert, welcher<br />

Knoten mit welchem anderen Knoten benachbart ist.<br />

Die Grundlage der Speicherung ist ein Knoten-Array.<br />

Dieses Array hat so viele Felder, wie es Knoten gibt.<br />

Jedem Knoten wird eine Position im Array zugeteilt. Im<br />

Knoten-Array werden mindestens die Anfangszeiger auf<br />

verkettete Listen gespeichert. In diesen Listen sind die<br />

Knoten gespeichert, zu welchen eine direkte Verbindung<br />

durch eine Kante besteht. Die Kante (u, v) aus dem Graph<br />

führt zu einem Eintrag in der Liste an der Stelle u im<br />

Array.<br />

Diese Speicherung benötigt Ө(n + m) Speicherplatz. Adjazenzlisten unterstützen viele Operationen,<br />

z.B. das Verfolgen von gerichteten Kanten in <strong>Graphen</strong>. Andere Operationen dagegen werden nur<br />

schlecht unterstützt, insbesondere das Hinzufügen und Entfernen von Knoten.<br />

2.4 Speicherung mit doppelt verketteten Listen<br />

Der Beispielgraph ist derselbe<br />

wie vorhin.<br />

Bei dieser Speicherung werden<br />

die Knoten und die Kanten als<br />

Objekte behandelt. Es wird<br />

ihnen also zugestanden, mehr<br />

Information als nur den Namen<br />

zu enthalten.<br />

Die Basis bildet eine doppelt<br />

verkettete Liste, welche die<br />

Knotenobjekte enthält. Jedes<br />

Knotenobjekt speichert dann<br />

eine Liste mit den von ihm<br />

ausgehenden Kanten-Objekten.<br />

Auch diese Liste ist doppelt<br />

verkettet.<br />

Ein Kantenobjekt enthält drei<br />

Referenzen. Zwei Referenzen<br />

werden für die doppelt verkettete<br />

Kantenliste benötigt. Die<br />

dritte Referenz zeigt auf den<br />

Knoten, auf den die gerichtete Kante zeigt. Zusätzlich können im Kantenobjekt noch weitere Informationen<br />

wie z.B. das Gewicht der Kante gespeichert werden.<br />

7


2.5 Lernkontrolle<br />

2<br />

1<br />

2<br />

3<br />

1<br />

4<br />

3<br />

4<br />

5<br />

5<br />

2.5.1 Adjazenzmatrix<br />

a) Geben Sie die Adjazenzmatrix für die beiden <strong>Graphen</strong> an.<br />

b) Bei welchem <strong>Graphen</strong> ist der Speicherplatz besser ausgenutzt?<br />

c) Denken Sie dass der Speicherplatz generell gut ausgenützt ist?<br />

d) Gibt es <strong>Graphen</strong>, für die diese Speicherung gut, bzw. sehr schlecht ist?<br />

2.5.2 Inzidenzmatrix<br />

a) Geben Sie die Inzidenzmatrix für die beiden <strong>Graphen</strong> an.<br />

b) Bei welchem <strong>Graphen</strong> ist der Speicherplatz besser ausgenutzt?<br />

c) Denken Sie dass der Speicherplatz generell gut ausgenützt ist?<br />

d) Gibt es <strong>Graphen</strong>, für die diese Speicherung gut, bzw. sehr schlecht ist?<br />

2.5.3 Adjazenzlisten<br />

Speichern Sie den oben rechts stehenden <strong>Graphen</strong> mit Hilfe von Adjazenzlisten ab.<br />

8


3 <strong>Graphen</strong>algorithmen<br />

Es gibt sehr viele Probleme, welche sich im Zusammenhang mit <strong>Graphen</strong> stellen. Sie werden hier nur<br />

einen ganz kleinen Teil dieser Probleme und deren Lösungsansätze kennen lernen.<br />

3.1 Topologisches Sortieren<br />

Eine topologische Sortierung basiert auf einer vergleichenden Relation. In einem gerichteten <strong>Graphen</strong><br />

besteht durch die Kantenrichtung eine solche vergleichende Relation zwischen je zwei benachbarten<br />

Knoten. Die Kantenrichtung kann dabei gelesen werden wie zum Beispiel: „grösser als“, „folgt auf“<br />

oder „ist Nachfolger von“. In ungerichteten <strong>Graphen</strong> besteht diese vergleichende Relation nicht und<br />

daher kann keine topologische Sortierung erreicht werden.<br />

Beispiel: Wir müssen einen Projektplan erstellen. Als erstes stellen wir fest, welche Aktivitäten zu<br />

erledigen sind: „Analyse“, „Implementierung“, „Einkauf der Hardware“, „Test“, „Installation der Hardware“,<br />

„Installation der Software“. Es ist klar, dass diese Aktivitäten eine zeitliche Abhängigkeit haben.<br />

Wir müssen beispielsweise zuerst die Hardware kaufen, bevor wir sie installieren können.<br />

Folgender Projektplan könnte nun erstellt werden:<br />

Einkauf<br />

Hardware<br />

Installation<br />

Hardware<br />

Analyse<br />

Implementierung<br />

Test<br />

Installation<br />

Software<br />

Wir sehen hier die zeitliche Abhängigkeit (Relation „kommt nach“). Wenn wir dieses Projekt nun<br />

alleine durchführen müssen, können wir nicht so parallel arbeiten, wie das hier dargestellt ist. Wir<br />

müssen eine sequenzielle (topologisch sortierte) Abfolge haben. Davon gibt es mehrere. Hier drei<br />

Beispiele:<br />

Analyse → Einkauf Hardware → Installation Hardware → Implementierung → Test → Installation Software<br />

Analyse → Einkauf Hardware → Implementierung → Installation Hardware → Test → Installation Software<br />

Analyse → Implementierung → Test → Einkauf Hardware → Installation Hardware → Installation Software<br />

Eine topologische Sortierung eines Digraphen ist also eine vollständige Ordnung der Knoten, die mit<br />

der durch die gerichteten Kanten ausgedrückten partiellen Ordnung verträglich ist.<br />

3.1.1 Zyklenfreiheit<br />

Ein Zyklus oder Kreis ist eine Folge von Kanten bei der Anfangs- und Endpunkt dieselben sind. Eine<br />

Schlinge ist der kleinstmögliche Zyklus mit genau einem Knoten.<br />

Nicht für alle Digraphen kann eine topologische Sortierung erzeugt werden. Die zentrale Eigenschaft<br />

von topologisch sortierbaren Digraphen ist, dass sie keine Zyklen enthalten, also zyklenfrei sind.<br />

In manchen Problemstellungen ist es sehr wichtig, dass die <strong>Graphen</strong> zyklenfrei sind. Beim Projektplan<br />

darf es nicht sein, dass zwei Vorgänge je voneinander abhängen. Auch in Computerprogrammen ist<br />

es oft nicht erlaubt, dass zyklische Abhängigkeiten bestehen: Wenn Klasse C1 von C2 abhängt, C2<br />

von C3 abhängt und C3 von C1 abhängt, dann besteht ein zyklische Abhängigkeit.<br />

Satz: Jeder zyklenfreie Graph hat eine topologische Sortierung.<br />

3.1.2 Algorithmus<br />

Die Idee des Algorithmus ist es, mit Knoten zu arbeiten, welche keine eingehenden Kanten haben,<br />

d.h. die indeg(v) = 0 besitzen. Diese Knoten können nicht zu einem Zyklus gehören, da sie von keinem<br />

anderen Knoten erreicht werden können. Es ist sofort einleuchtend, dass es in einem topologisch<br />

sortierbaren <strong>Graphen</strong> mindestens einen Knoten v mit indeg(v) = 0 geben muss, weil es sonst keinen<br />

ersten Knoten in der topologischen Sortierung geben würde. Dieser Knoten wird markiert und bildet<br />

den Anfang der topologischen Sortierung. Alle markierten Knoten werden konzeptionell der Reihe<br />

nach aus dem <strong>Graphen</strong> entfernt. Dadurch erniedrigt sich der Eingangsgrad anderer Knoten und es<br />

gibt wieder einen oder mehrere neue Knoten mit indeg(v) = 0.<br />

9


Input: Ein gerichteter Graph G. Alle Knoten kennen ihren Eingangsgrad.<br />

Output: Eine topologische Sortierung von G oder die Information, dass es keine solche Sortierung<br />

gibt.<br />

L := { v ∈ V | indeg(v) == 0 }.<br />

for (i := 1..n) do<br />

if (L == {}) then<br />

es gibt keine Sortierung Exit<br />

endif<br />

entferne den ersten Knoten u aus L und gebe ihn mit i aus<br />

forall (Kanten e inzident zu u) do<br />

v := Zielknoten von e<br />

indeg(v) := indeg(v) – 1<br />

if (indeg(v) == 0) then<br />

füge v in L ein<br />

endif<br />

entferne e<br />

endforall<br />

entferne u<br />

endfor<br />

Dieser Pseudocode liefert eine topologische Sortierung eines <strong>Graphen</strong> wenn dieser zyklenfrei ist. Die<br />

Laufzeit dieses Algorithmus beträgt O(n + m). Die äussere for-Schleife wird für jeden Knoten einmal<br />

durchlaufen, also n mal. Die innere forall-Schleife geht für den Knoten u all seine ausgehenden,<br />

inzidenten Kanten durch. Im Ganzen werden in allen forall-Schleifen alle Kanten einmal besucht.<br />

Beispiel<br />

1 2<br />

3<br />

2<br />

3<br />

3<br />

4<br />

5 6<br />

4<br />

5 6 4 5 6<br />

3<br />

4 6 4<br />

6<br />

6<br />

Die markierten Knoten sind die Knoten mit indeg(v) = 0. In jedem Schritt wird ein Knoten dieser Art<br />

aus dem <strong>Graphen</strong> entfernt. Die resultierende topologische Sortierung ist:<br />

1 → 2 → 5 → 3 → 4 → 6<br />

3.2 Durchlaufen von <strong>Graphen</strong><br />

Wie schon bei den Bäumen besteht auch hier der Bedarf, alle Knoten in einer geordneten Weise zu<br />

besuchen und eventuell auszugeben. Gerade im Zusammenhang mit der Speicherung eines <strong>Graphen</strong><br />

auf dem Externspeicher (Serialisierung) ist es wichtig, alle Knoten zu besuchen und die Knotenobjekte<br />

und inzidenten Kantenobjekte abzuspeichern.<br />

Wiederum gibt es zwei Strategien mit denen alle Knoten besucht werden können: Die eine Strategie<br />

ist die Tiefensuche (DFS = depth first search), die andere ist die Breitensuche (BFS = breadth first<br />

search). Beide Strategien durchlaufen alle Knoten und alle Kanten, und haben sogar dieselbe Laufzeit.<br />

10


3.2.1 Tiefensuche (DFS)<br />

Bei der DFS-Strategie wird der Graph zunächst „in die Tiefe gehend durchsucht“. Die DFS-Strategie<br />

ist eine Verallgemeinerung des Preorder-Durchlaufprinzips bei Binärbäumen. Das Prinzip ist das<br />

folgende:<br />

• Kanten werden ausgehend von dem zuletzt entdeckten Knoten<br />

v, der mit noch unerforschten Kanten inzident ist, erforscht. w<br />

• Erreicht man von v aus einen noch nicht erforschten Knoten w,<br />

so verfährt man mit w genauso wie mit v.<br />

• Wenn alle mit w inzidenten Kanten erforscht sind, erfolgt ein<br />

Backtracking zu v.<br />

Es gibt viele verschiedene Arten, wie die DFS-Strategie implementiert werden kann. Die folgende ist<br />

also nur eine von vielen.<br />

v<br />

DFS mit drei Farben<br />

Bei dieser Methode gibt es drei Arten von Knoten (weiss, grau, schwarz). Am Anfang ist jeder Knoten<br />

weiss. Das bedeutet, dass er noch nicht entdeckt worden ist. Sobald ein Knoten entdeckt worden ist,<br />

wird er grau. Wenn ein Knoten komplett abgearbeitet ist, wird er schwarz gefärbt. Dies ist der Fall,<br />

wenn er das Backtracking zum Vorgängerknoten einleitet.<br />

Des Weiteren wird für jeden Knoten noch abgespeichert, wer sein Vorgänger ist. D.h., von welchem<br />

Knoten aus er entdeckt worden ist. Der Vorgänger wird in einem Array α gespeichert. Selbstverständlich<br />

wäre es auch möglich, den Vorgänger direkt im Knoten zu speichern.<br />

DFS (Graph G)<br />

forall (v in V) do<br />

farbe[v]:= weiss<br />

α[v]:= null<br />

endforall<br />

forall (v in V) do<br />

\\ so werden alle Zusammenhangskomponenten gefunden<br />

if (farbe[v] == weiss) then<br />

DFS-Visit(v)<br />

endif<br />

endforall<br />

DFS-Visit(v)<br />

farbe[v]:= grau<br />

forall (w adjazent zu v) do<br />

if (farbe[w] == weiss) then<br />

α[w]:= v<br />

DFS-Visit(w)<br />

endif<br />

endforall<br />

farbe[v]:= schwarz<br />

Durch dieses Verfahren werden alle Zusammenhangskomponenten gefunden. Jeder Knoten und jede<br />

Kante werden einmal besucht. Am Ende enthält α die Informationen eines Spannbaums für den <strong>Graphen</strong>,<br />

falls dieser zusammenhängend ist.<br />

Laufzeitanalyse: Die Methode DFS(G) benötigt offensichtlich O(n) Schleifendurchläufe. Die Methode<br />

DSF-Visit betrachtet für den Knoten v alle inzidenten, ausgehenden Kanten. Also dauern alle for-<br />

Schleifen von DFS-Visit zusammen ∑ v∈V outdeg(v) = m. Somit ergibt sich eine Laufzeit von O(n + m)<br />

11


Beispiel Tiefensuche<br />

1 2<br />

3 1 2 3<br />

1<br />

2 3<br />

4 5 6<br />

4 5 6<br />

4<br />

5 6<br />

1 2<br />

3 1 2<br />

3 1 2 3<br />

4 5 6<br />

4 5 6<br />

4<br />

5 6<br />

1 2<br />

3 1 2 3<br />

1<br />

2 3<br />

4 5 6<br />

4 5 6<br />

4 5 6<br />

1 2<br />

3 1 2 3<br />

1 2 3<br />

4 5 6<br />

4 5 6<br />

4<br />

5 6<br />

Als erstes wird in diesem Beispiel die Tiefensuche von Knoten 1 aus bis zum Knoten 6 gemacht.<br />

Danach sind alle Knoten ausser Knoten 4 abgearbeitet. Als letztes Knoten wird noch Knoten 4 gefunden<br />

und abgearbeitet.<br />

3.2.2 Topologische Sortierung mittels DFS<br />

Aus dem Algorithmus für die Tiefensuche kann ein zweiter Algorithmus für die topologische Sortierung<br />

von zyklenfreien <strong>Graphen</strong> gewonnen werden:<br />

DFS-Topological-Sort<br />

Counter := n<br />

rufe DFS(G) auf {<br />

sobald ein Knoten schwarz gefärbt wird, setze nummer[v] := counter;<br />

counter--<br />

}<br />

3.2.3 Breitensuche (BFS)<br />

Der Algorithmus für die Breitensuche lautet wie folgt:<br />

1. Für einen Knoten werden die noch nicht besuchten Nachbarknoten gesucht.<br />

2. Jeder Nachbarknoten wird ans Ende einer Warteschlange eingefügt.<br />

3. Solange die Warteschlange nicht leer ist, wähle den nächsten Knoten und beginne wieder bei 1.<br />

Nachfolgend sehen Sie den Algorithmus in Pseudocode. Der Knoten s bezeichnet dabei den Startknoten<br />

der Breitensuche. Q ist die Warteschlange (Queue).<br />

BFS(Graph G, Knoten s)<br />

forall (v in V) do<br />

v.gefunden := false<br />

// alle Knoten unentdeckt<br />

endforall<br />

s.gefunden := true<br />

Q = {s}<br />

while (Q nicht leer) do<br />

entferne das vorderste Element u aus Q<br />

forall (v in der Nachbarschaft von u) do // Alle Knoten die von u aus mit einer<br />

// Kante erreicht werden können<br />

12


if (v.gefunden == false) then<br />

v.gefunden := true<br />

füge v in Q ein<br />

endif<br />

endforall<br />

endwhile<br />

Dieser Algorithmus benötigt O(n + m) Zeit (wie DFS). Die while-Schleife wird so lange ausgeführt,<br />

bis es keine unentdeckten Knoten mehr gibt (also n mal). Für jeden Knoten wird die Nachbarschaft<br />

betrachtet. Dies sind so viele adjazente Knoten, wie der Knoten inzidente Kanten besitzt. Im Ganzen<br />

wird die forall-Schleife also m-mal ausgeführt.<br />

3.3 Kürzeste Pfade<br />

Problem 1: Finde den kürzesten Pfad zwischen zwei Knoten.<br />

Beispiel: Im Liniennetz der SBB möchten Sie den kürzesten Weg zwischen Ihrem Wohnort und<br />

Brugg-Windisch finden, damit Sie nicht zu viel Zeit für Ihren Schulweg verlieren.<br />

Problem 2: Finde alle kürzesten Pfade zwischen einem Knoten s und allen anderen Knoten des<br />

<strong>Graphen</strong>.<br />

Beispiel: Sie wollen herausfinden, wer aus Ihrer Klasse den kürzesten Schulweg hat.<br />

Problem 3: Finde für jedes Knotenpaar des <strong>Graphen</strong> den kürzesten Pfad.<br />

Beispiel: Sie erhalten einen Plan mit allen Standorten der FHNW. Nun dürfen Sie sich Ihr Schulgebäude<br />

selber aussuchen. Sie suchen dasjenige mit der geringsten Entfernung zu Ihrem<br />

Wohnort.<br />

Die beiden Algorithmen, die in diesem Kapitel vorgestellt werden, lösen beide Problem 2. Die Algorithmen<br />

sind nach ihren Erfindern benannt. Der berühmteste Algorithmus wurde 1959 von E.W.<br />

Dijkstra vorgeschlagen. Vor ihm entwickelten R. Bellman(1956) und L. Ford(1958) unabhängig voneinander<br />

denselben Algorithmus. Sowohl der Algorithmus von Dijkstra als auch der von Bellman und<br />

Ford beschäftigen sich mit kürzesten Pfaden zwischen einem Knoten und allen anderen Knoten im<br />

<strong>Graphen</strong>, dieses Problem wird auch als single source shortest path-Problem bezeichnet. Ausgehend<br />

von einer Quelle (source) werden die kürzesten Pfade zu allen anderen Knoten im <strong>Graphen</strong> berechnet.<br />

Was aber unterscheidet diese beiden Algorithmen, wenn sie doch dasselbe Ziel haben?<br />

Beide Algorithmen funktionieren für gewichtete <strong>Graphen</strong> (gerichtet und ungerichtet) mit positiven<br />

Kantenwerten. Der Algorithmus von Bellman und Ford kann zusätzlich noch mit gerichteten <strong>Graphen</strong><br />

arbeiten, deren Kantengewichte negativ sein dürfen.<br />

3.3.1 Einleitung, Begriffe<br />

Die hier definierten Begriffe sind recht intuitiv. Trotzdem müssen sie definiert werden, um Missverständnissen<br />

vorzubeugen.<br />

Pfad:<br />

Ein Pfad ist eine endliche Folge P = (v 0 , e 1 , v 1 , ... , e k , v k ) mit k ≥ 0 mit v i aus<br />

V und e i = (v i-1 , v i ) aus E. In einem Graph ohne Mehrfachkanten ist ein Pfad<br />

alleine durch die Folge von benachbarten Knoten definiert.<br />

Gewichtsfunktion: Die Gewichtsfunktion weist jeder Kante ein Gewicht zu. Diese Funktion wird<br />

meist mit w (weight) bezeichnet. w(e) = Gewicht der Kante e.<br />

Länge eines Pfades: Die Länge eines Pfades in einem gewichteten <strong>Graphen</strong> G = (V, E) mit Gewichtsfunktion<br />

w ist bestimmt durch:<br />

wP ( ) = ∑ we (<br />

i<br />

)<br />

k<br />

i=<br />

1<br />

Das ist die Summe der Kantengewichte der verwendeten<br />

Kanten.<br />

Distanz, Abstand: Der Abstand zweier Knoten u, v ist der kürzeste Pfad zwischen den beiden<br />

Knoten. Bezeichnet wird dieser Abstand mit dist(u, v). Kann v von u aus<br />

nicht erreicht werden ist der Abstand unendlich. Ein Knoten hat zu sich selber<br />

den Abstand 0.<br />

Weitere Voraussetzung: Der Graph, auf dem die Berechnung kürzester Pfade durchgeführt wird, ist<br />

ein einfacher Graph, er enthält also keine Schlingen oder Mehrfachkanten.<br />

13


Lemma: Sei s ein ausgezeichneter Knoten. Dann gilt: dist(s, v) ≤ dist(s, u) + w(u, v) für alle u, v in V.<br />

Beweis: dist(s, v) ist der Wert des kürzesten Pfades zwischen s und v. Wird dabei die Kante {u, v} für<br />

die Berechnung benutzt, so ist dist(s, u) + w(u, v) = dist(s, v). Wenn sie nicht benutzt wird, kann<br />

dist(s, v) nicht grösser sein als dist(s, u) + w(u, v), weil ansonsten nicht der kürzeste Pfad gefunden<br />

worden ist.<br />

3.3.2 Allgemeine Methoden<br />

Die beiden Algorithmen zur Berechnung der kürzesten Pfade verwenden die gleiche Initialisierung<br />

init(Graph G, Knoten s) und dieselbe Methode test(…). Der Graph G kann gerichtet oder<br />

ungerichtet sein. Der Knoten s ist der Startknoten von dem aus die kürzesten Pfade berechnet werden.<br />

Die Werte d[v] enthalten den Wert des momentan kürzesten Pfades von s nach v. Diese Werte<br />

sind dabei obere Schranken für dist(s, v). Am Ende der Algorithmen enthält d[v] den Wert des kürzesten<br />

Pfades von s nach v.<br />

init(Graph G, Knoten s)<br />

forall (v in V) do<br />

d[v] := ∞<br />

// α[v]:= null //enthält den Nachbarknoten über den der kürzeste Pfad geht<br />

endforall<br />

d[s]:= 0 // s hat zu sich selber Abstand 0<br />

Die Methode test(u, v) testet, ob eine gegebene Kante (u, v) den momentanen kürzesten Pfad<br />

von s nach v abkürzen kann. Es wird dabei getestet, ob die Summe aus der Distanz der Kante (u, v)<br />

und der Länge des Pfades von s nach u kleiner ist als die bisherige Länge des Pfades von s nach v.<br />

boolean test(u,v)<br />

if (d[v] > d[u] + w(u, v)) then<br />

d[v] := d[u] + w(u, v);<br />

// α[v] := u //enthält den Nachbarknoten über den der kürzeste Pfad geht<br />

endif<br />

Die Verweise auf den Knoten, von dem aus der eine Knoten erreicht worden ist, werden in dieser<br />

Situation noch nicht gebraucht.<br />

3.4 Der Algorithmus von Dijkstra<br />

Dieser Algorithmus ist ein Greedy-Algorithmus. Greedy-Algorithmen (gierige Algorithmen oder Raffkealgorithmen)<br />

bilden in der Informatik eine spezielle Klasse von Algorithmen. Sie zeichnen sich dadurch<br />

aus, dass sie immer denjenigen Folgezustand auswählen, der zum Zeitpunkt der Wahl den grössten<br />

Gewinn bzw. das beste Ergebnis verspricht. Bei Greedy-Algorithmen gibt es kein Backtracking. Eine<br />

Lösung, die einmal als fix gekennzeichnet worden ist, wird im weiteren Verlauf nicht mehr verändert.<br />

Dieser Algorithmus ist sehr ähnlich zum BFS-Algorithmus. Der BFS liefert für den Fall, in dem alle<br />

Gewichte dieselben sind, eine Lösung für dieses Problem.<br />

3.4.1 Prinzip und Pseudocode<br />

Der Algorithmus von Dijkstra 3 arbeitet mit einer „Wellenfront“-Strategie. Für alle Knoten hinter der<br />

Front ist der endgültige Distanzwert bereits ermittelt worden. Wir nennen diese Knoten permanent und<br />

speichern sie in PERM ab. Für diese Knoten v gilt also: dist(s, v) = d[v]. Die Knoten vor der Front sind<br />

noch nicht bearbeitet worden. Für alle Knoten auf der Front ist die Berechnung in Gange. Diese Knoten<br />

werden in einer Prioritätswarteschlange PQ gespeichert. Die Prioritäten entsprechen den momentanen<br />

Abständen zum Startknoten.<br />

Anfangszustand: Am Anfang ist die Menge der permanenten Knoten leer. Die Menge der Knoten auf<br />

der Front enthält nur den Startknoten s.<br />

3 http://de.wikipedia.org/wiki/Algorithmus_von_Dijkstra<br />

14


• Nimm einen Knoten u aus PQ, welcher den kleinsten Abstand zu s hat.<br />

• Für alle Nachbarknoten v von u überprüfe mittels test(u, v) die Distanz. Wenn nötig wird diese<br />

Distanz aktualisiert. Ist der Nachbarknoten v schon in PQ, wird seine Priorität dort aktualisiert. Ist<br />

er noch nicht in dieser Menge, wird er mit dem aktuellen Wert der Distanz in diese Menge eingefügt.<br />

• Nimm den Knoten u in die Menge der permanenten Knoten auf.<br />

Pseudocode<br />

Gegeben ist ein gewichteter Graph G mit nicht negativen Knotengewichten w und ein Startknoten s.<br />

Dijkstra (G =(V, E), w, s)<br />

init(G, s)<br />

PERM := {}<br />

// Menge der permanent markierten Knoten<br />

PQ := Prioritätswarteschlange // z.B. Min-Heap<br />

PQ.insert(s, d[s]) // füge s mit Prio d[s] = 0 in PQ ein<br />

while(!PQ.isEmpty()) do // es wurden noch nicht alle Distanzen definitiv gesetzt<br />

u := PQ.removeMin() // u ist ein Knoten mit der kleinsten Distanz<br />

forall(v benachbart zu u) do // Alle Knoten die von u aus mit einer Kante<br />

// erreicht werden können<br />

if (d[v] == ∞) then<br />

PQ.insert(v, d[v])<br />

endif<br />

if (test(u, v)) then // Teste ob dieser Pfad kürzer ist.<br />

// Wenn ja, wird in Test(u,v) d[v] heruntergestzt.<br />

PQ.updatePrio(v, d[v]) // setze die Priorität von v in PQ auf d[v]<br />

endif<br />

endforall<br />

PERM.add(u)<br />

// füge u in die Menge PERM ein<br />

endwhile<br />

Bemerkung<br />

Die Distanzwerte können auch in den Knoten selber gespeichert werden. Um zu markieren, ob ein<br />

Knoten schon als permanent markiert worden ist, kann man dies auch im Knoten selber speichern.<br />

3.4.2 Laufzeitanalyse<br />

In jedem Durchlauf der while-Schleife wird genau ein Knoten in die Menge PERM eingefügt. Das<br />

Einfügen in eine ungeordnete Menge kann in O(1) gemacht werden. Da n Knoten vorhanden sind,<br />

resultiert für das Einfügen die Zeitkomplexität O(n).<br />

Das Minimum in der Prioritätswarteschlange muss n mal gesucht werden, da jeder Knoten einmal das<br />

Minimum ist. Wie schnell das Minimum aus der Prioritätswarteschlange geholt und entfernt werden<br />

kann, hängt von der Realisierung der Prioritätswarteschlange ab. Auch die Zeit, die für das Einfügen<br />

und das Aktualisieren des Schlüssels benötigt wird, hängt von der Prioritätswarteschlange ab. Jeder<br />

Knoten wird einmal in die Prioritätswarteschlange eingefügt. Der Wert muss höchstens m mal verändert<br />

werden. Generell sieht die Laufzeit also so aus:<br />

T = O(n) + n·T min aus PQ extrahieren + n·T einfügen in PQ + m·T aktualisieren in PQ<br />

Nehmen wir nun an, dass die Prioritätswarteschlange durch einen binären Min-Heap realisiert worden<br />

ist. Dann wird die gesamte Laufzeit zu:<br />

T = O(n) + n·O (log n) + n·O(log n) + m·O(log n)) = O(n log n + m log n)<br />

T = O ((n + m) log n)<br />

3.4.3 Problem bei negativen Gewichten<br />

Wie schon an früherer Stelle angedeutet, setzt der Algorithmus von Dijkstra nicht negative Kantengewichte<br />

voraus. Die folgenden Bilder zeigen ein Beispiel, bei dem mit dem Algorithmus von Dijkstra<br />

Probleme auftreten. Nachdem die Distanz zum oberen Knoten auf 1 gesetzt und dieser Knoten in die<br />

Menge PERM aufgenommen wurde, wird er als Nachbar vom Knoten rechts unten noch einmal in der<br />

Prioritätswarteschlange PQ erwartet, obwohl er in PQ gar nicht mehr enthalten ist.<br />

15


1<br />

∞<br />

1<br />

1<br />

1<br />

1<br />

0 - 4 0 - 4 0 -4<br />

2<br />

2<br />

2<br />

∞<br />

2<br />

2<br />

3.5 Der Algorithmus von Bellman und Ford<br />

Der Algorithmus von Bellman und Ford, der hier erklärt wird, kann auch mit negativen Gewichten<br />

umgehen (wenn der Graph gerichtet ist). Wenn negative Gewichte zugelassen sind, tritt ein grundsätzliches<br />

Problem auf: es kann ein Kreis negativer Länge von s aus erreichbar sein und damit<br />

dist(s, v)= –∞ für alle Knoten v gelten, die von s aus erreichbar sind. Denn dieser Kreis kann unendlich<br />

viele Male durchlaufen werden und vermindert den Wert des Pfades bei jedem<br />

Durchlaufen.<br />

Ein ungerichteter Graph enthält immer Zyklen, ausser es handelt sich um einen Baum.<br />

Wenn nun eine Kante negatives Gewicht hat, ist schon ein Kreis negativer Länge<br />

vorhanden.<br />

Dieser Algorithmus ist kein Greedy-Algorithmus. Die Werte der Abstände können sich<br />

bis zum Schluss verändern. Natürlich werden aber auch hier Zwischenwerte gespeichert und aktualisiert.<br />

3.5.1 Prinzip und Pseudocode<br />

• Es gibt n Phasen, wobei in der ersten Phase initialisiert wird.<br />

• In jeder weiteren Phase wird jede Kante mit test(u, v) überprüft und d[v] aktualisiert.<br />

• Am Ende enthält das Array d[v] für alle Knoten v die Distanz zwischen s und v.<br />

Bellman-Ford(G = (V, E), w, s)<br />

init(G,s)<br />

for (k := 1..n-1) do<br />

forall ((u,v) in E) do<br />

test(u, v)<br />

endforall<br />

endfor<br />

-3<br />

-3<br />

-3<br />

Die genaue Vorgehensweise wollen wir anhand des oben stehenden <strong>Graphen</strong> illustrieren. Dabei ist es<br />

zentral, dass wir eine Kantenreihenfolge festlegen, in der alle Kanten des <strong>Graphen</strong> besucht werden.<br />

Obwohl der Algorithmus für alle möglichen Kantenreihenfolgen funktioniert, hat die Wahl Einfluss<br />

darauf, wie früh die kürzesten Distanzen entdeckt werden. Mit gewissen Reihenfolgen werden die<br />

korrekten Distanzen erst in der letzten Iteration gefunden, während mit anderen Kantenreihenfolgen<br />

die kürzesten Distanzen bereits in der ersten Iteration gefunden werden.<br />

Für unser Beispiel wählen wir die folgende Kantenreihenfolge: (A,C), (B,C), (B,D), (s,A), (C,D), (A,B).<br />

Phasen k d[s] d[C] d[C] d[D] d[A] d[D] d[B]<br />

0 (init) 0 ∞ ∞ ∞ ∞ ∞ ∞<br />

1 3 3 – 1 = 2<br />

2 3 + 2 = 5 2 + 2 = 4 2 + 8 = 10 4 + 3 = 7<br />

3, 4, 5 0 4 4 7 3 7 2<br />

3.5.2 Laufzeitanalyse<br />

Die Laufzeit dieses Algorithmus ist, im Gegensatz zum Algorithmus von Dijkstra, von keiner Datenstruktur<br />

abhängig. Die for-Schleife wird (n – 1)-mal durchlaufen. Bei jedem Durchlauf werden alle m<br />

Kanten geprüft. Also ist die Laufzeit O(n·m).<br />

Falls ein Kreis negativer Länge auftritt, ist die Laufzeit für die Berechnung unendlich gross. Dass die<br />

Schleife nur n mal ausgeführt wird, verhindert eine endlose Laufzeit beim Algorithmus.<br />

16


3.6 Lernkontrolle<br />

3.6.1 Topologische Sortierung<br />

a) Kann ein ungerichteter Graph topologisch sortiert werden?<br />

b) Geben Sie für den folgenden <strong>Graphen</strong> eine topologische Sortierung an.<br />

1<br />

4<br />

6<br />

2<br />

3 5 7<br />

c) Zeichnen Sie noch zwei Kanten ein, welche die topologische Sortierung nicht beeinträchtigen.<br />

3.6.2 Durchlaufen von <strong>Graphen</strong><br />

Zeichnen Sie im folgenden <strong>Graphen</strong> die verschiedenen Schritte einer a) Tiefensuche und b) Breitensuche<br />

ein. Startknoten ist der Knoten 1. Geben Sie auch an, in welcher Reihenfolge die Knoten gefunden<br />

werden.<br />

1<br />

4<br />

6<br />

2<br />

3<br />

5 7<br />

3.6.3 Kürzeste Pfade<br />

a) Sie haben gesehen, dass die Laufzeit des Dijkstra-Algorithmus O((n + m) log n) ist. Rechnen Sie<br />

nun aus, wie die Laufzeit wäre, wenn anstatt der Prioritätswarteschlange eine sortierte Liste verwendet<br />

würde.<br />

b) Sie erhalten einen <strong>Graphen</strong> und sollen nun herausfinden, ob er eine Kante mit negativem Gewicht<br />

enthält. Schreiben Sie in Pseudocode eine entsprechende Testmethode. Geben Sie zudem die<br />

Laufzeit Ihres Algorithmus an.<br />

c) Bestimmen Sie eine möglichst scharfe untere Grenze der Laufzeit für einen Algorithmus zur<br />

Überprüfung von Kanten mit negativem Gewicht.<br />

17


4 Spannbäume<br />

Spannbäume sind Bäume die aus Kanten eines <strong>Graphen</strong> bestehen. Sie wissen, dass ein Baum mit n<br />

Knoten genau n-1 Kanten enthält. Für einen Spannbaum werden also n – 1 Kanten aus der Menge<br />

der Kanten ausgesucht, so dass ein zusammenhängender Graph entsteht. In diesem <strong>Graphen</strong> gibt es<br />

zwischen zwei Knoten genau einen Weg gibt.<br />

Wie ein Spannbaum berechnet wird, wissen Sie im Prinzip schon. Die Suchwege bei Breiten- und<br />

Tiefensuche bilden einen Spannbaum. Im Code von BFS und DFS ist das Berechnen eines Spannbaumes<br />

schon „eingebaut“. Speichert jeder Knoten den adjazenten Knoten, von dem er entdeckt<br />

wurde, dann entspricht dies dem Speichern des Vaters im Spannbaum.<br />

Das Beispiel zeigt einen Spannbaum, der bei der Breitensuche vom grauen Knoten aus berechnet<br />

wurde.<br />

Ein Spannbaum kann auch als Gerüst eines <strong>Graphen</strong> gesehen werden. Spannbäume gibt es nur in<br />

zusammenhängenden <strong>Graphen</strong>. (Damit ein Graph zusammenhängend ist, muss er deshalb mindestens<br />

n-1 Kanten haben.)<br />

Übungsaufgaben<br />

Geben Sie für den folgenden <strong>Graphen</strong> einen möglichen Spannbaum an.<br />

Versuchen Sie im folgenden <strong>Graphen</strong> einen Spannbaum zu finden, dessen Kanten möglichst kurz<br />

sind.<br />

4.1 Minimale Spannbäume<br />

In der Praxis braucht man meist nicht irgendeinen Spannbaum, sondern ist auf der Suche nach einem<br />

minimalen Spannbaum. Minimale Spannbäume werden an den verschiedensten Orten eingesetzt:<br />

Telekommunikationsfirmen versuchen ein möglichst kurzes Hauptnetz zu haben. Das spart Materialund<br />

Unterhaltskosten. Es muss dabei sichergestellt werden, dass alle Ortschaften am Netz angeschlossen<br />

sind. Vertriebsfirmen möchten ihre Verteillager so legen, dass die Wege zwischen den<br />

einzelnen Lagern möglichst kurz sind.<br />

18


4.2 Formale Definitionen<br />

Spannbaum:<br />

Ein Spannbaum ST (= Spanning Tree) eines <strong>Graphen</strong> G = (V, E) besteht<br />

aus n – 1 Kanten der Menge E. Diese Kanten bilden einen zusammenhängenden<br />

<strong>Graphen</strong>, der alle Knoten aus V enthält.<br />

Minimaler Spannbaum: Ein minimaler Spannbaum MST (= Minimum Spanning Tree) ist ein Spanbaum,<br />

bei dem die Summe der Kantengewichte minimal ist.<br />

(Es kann in einem <strong>Graphen</strong> mehr als einen minimalen Spannbaum geben.)<br />

4.3 Algorithmus von Prim<br />

Der Algorithmus von Prim, auch Prim-Dijkstra-Algorithmus genannt, funktioniert analog zum Dijkstra-<br />

Algorithmus. Der einzige Unterschied liegt darin, dass der Abstand zum Spannbaum und nicht der<br />

Abstand zum Startknoten als Priorität (für die Prioritätswarteschlange) verwendet wird.<br />

Der Abstand eines Knoten zum Spannbaum ist gleich dem kleinsten Gewicht einer Kante, die den<br />

Knoten mit einem Knoten des Spannbaums verbindet.<br />

4<br />

7 1<br />

8<br />

Im Beispiel hat der graue Knoten vier inzidente Kanten, die ihn mit Knoten aus dem bisherigen<br />

Spannbaum verbinden. Die Kante mit dem geringsten Gewicht hat das Gewicht w(e) = 1. Somit hat<br />

der graue Knoten den Abstand 1 zum Spannbaum.<br />

Abstand von v zum minimalen Spannbaum MST:<br />

dist(v, MST) = min { w(e) | e = { v, u } ∈ E und v, u ∈ V}<br />

Ist der Knoten v zu keinem Knoten des Spannbaums adjazent, ist die Distanz unendlich.<br />

4.3.1 Prinzip<br />

Vor der Implementierung des Prim-Algorithmus wird hier das Prinzip vorgestellt. Als erstes wird die<br />

Startphase beschrieben. Danach wird gesagt, wie der Spannbaum in jedem Schritt um eine Kante<br />

wächst. Als letztes wird noch die Endbedingung genannt.<br />

Start: Als Ausgangspunkt wird ein beliebiger Knoten genommen. Dieser ist zu Beginn der einzige<br />

Knoten im Spannbaum. Der Spannbaum enthält noch keine Kante.<br />

Wachsen: Es wird der Knoten gesucht, der den kleinsten Abstand vom Spannbaum hat. Dieser<br />

Knoten darf aber nicht schon im Spannbaum enthalten sein.<br />

Der Knoten wird dann über die minimale Kante mit dem Spannbaum verbunden und in die<br />

Menge der Knoten des Spannbaums aufgenommen.<br />

Ende: Wenn alle Knoten im Spannbaum enthalten sind, dann ist die Berechnung zu Ende.<br />

4.3.2 Beispiel<br />

Gegeben ist ein Graph mit nebenstehender Adjazenzmatrix.<br />

Die Werte in den Feldern geben die<br />

Kantengewichte an.<br />

s 1 2 3 4 5 6<br />

s 0 2 0 5 7 0 0<br />

1 2 0 8 6 0 3 0<br />

2 0 8 0 0 0 2 4<br />

3 5 6 0 0 1 0 5<br />

4 7 0 0 1 0 0 1<br />

5 0 3 2 0 0 0 2<br />

6 0 0 4 5 1 2 0<br />

19


In diesem Beispiel wird ein Spannbaum ausgehend vom Knoten s berechnet. Für jeden Schritt werden<br />

die folgenden Informationen angegeben. Ein Bild zeigt den momentanen minimalen Spannbaum (linke<br />

Spalte). Der Knoten, der als nächstes in den Spannbaum aufgenommen wird, die Queue und die<br />

Menge der Knoten, die im Spannbaum sind, werden in der mittleren Spalte angegeben. In der Spalte<br />

ganz rechts werden die Abstände der Knoten zum aktuellen Spannbaum angegeben.<br />

Queue: {s}<br />

s 1 2 3 4 5 6<br />

MST: { }<br />

0 ∞ ∞ ∞ ∞ ∞ ∞<br />

s<br />

1 2<br />

3<br />

5<br />

Knoten: s<br />

Queue: {1,3,4}<br />

MST: { }<br />

s 1 2 3 4 5 6<br />

0 2 ∞ 5 7 ∞ ∞<br />

4<br />

6<br />

s<br />

1 2<br />

3<br />

5<br />

Knoten: 1<br />

Queue: {5, 3, 4, 2}<br />

MST: {s}<br />

s 1 2 3 4 5 6<br />

0 0 8 5 7 3 ∞<br />

4<br />

6<br />

s<br />

1 2<br />

3<br />

5<br />

Knoten: 5<br />

Queue: {2, 6, 3, 4}<br />

MST: {s, 1} s 1 2 3 4 5 6<br />

0 0 2 5 7 0 2<br />

4<br />

6<br />

s<br />

1 2<br />

3<br />

5<br />

Knoten: 2<br />

Queue: {6, 3, 4}<br />

MST: {s, 1, 5} s 1 2 3 4 5 6<br />

0 0 0 5 7 0 2<br />

4<br />

6<br />

s<br />

1 2<br />

3<br />

5<br />

Knoten: 6<br />

Queue: {4, 3}<br />

MST: {s, 1, 5, 2} s 1 2 3 4 5 6<br />

0 0 0 5 1 0 0<br />

4<br />

6<br />

s<br />

1 2<br />

3<br />

5<br />

Knoten: 4<br />

Queue: {4}<br />

MST: {s, 1, 5, 2, 6 } s 1 2 3 4 5 6<br />

0 0 0 1 0 0 0<br />

4<br />

6<br />

s<br />

1 2<br />

3<br />

5<br />

Knoten: 3<br />

Queue: { }<br />

MST: {s, 1, 5, 2, 6, 4} s 1 2 3 4 5 6<br />

0 0 0 0 0 0 0<br />

4<br />

6<br />

20


Übungsaufgaben<br />

a) Zeichnen Sie den <strong>Graphen</strong>, der zu der nachfolgend gegebenen Adjazenzmatrix (links) gehört.<br />

Erzeugen Sie dann den minimalen Spannbaum für diesen <strong>Graphen</strong>. Füllen Sie dabei die rechte<br />

Tabelle der Gewichte aus (rechts). Geben Sie in dieser Tabelle in der Spalte ganz links an, welches<br />

der aktuelle Knoten ist (analog zu den vorherigen Beispielen).<br />

b) Geben Sie die Menge der Kanten an, die im minimalen Spannbaum enthalten sind. Z.B. {s, 3}<br />

c) Welches Gewicht hat der hier erzeugte minimale Spannbaum.<br />

d) Hätte in einem Schritt auch eine andere Kante (bzw. ein anderer Knoten) in den MST aufgenommen<br />

werden können?<br />

s 1 2 3 4 5 6<br />

s 0 1 0 10 0 0 0<br />

1 1 0 2 0 0 7 0<br />

2 0 2 0 0 8 5 0<br />

3 10 0 0 0 2 0 0<br />

4 0 0 8 2 0 3 0<br />

5 0 7 5 0 3 0 4<br />

6 0 0 0 0 0 4 0<br />

s<br />

s 1 2 3 4 5 6<br />

0 ∞ ∞ ∞ ∞ ∞ ∞<br />

4.3.3 Implementierung<br />

Für die Implementierung des Algorithmus von Prim können Methoden analog zu denen des Dijkstra-<br />

Algorithmus verwendet werden. Diese müssen jedoch etwas abgeändert werden. Zuerst werden die<br />

abgeänderten Hilfsmethoden danach die Hauptmethode beschrieben.<br />

Hilfsmethoden<br />

Die beiden Hilfsmethoden init(…) und test(…) müssen nur wenig angepasst werden. Das zentrale<br />

Element, welches ändert, ist d[v]. Dieser Wert entspricht nun dem Abstand vom Spannbaum.<br />

init(Graph G, Knoten s)<br />

forall (v in V) do<br />

d[v] := ∞ // minimaler Abstand zum Spannbaum<br />

α[v] := null // enthält den Nachbarknoten, über den der kürzeste Weg geht<br />

endforall<br />

d[s] := 0<br />

// s ist der erste Knoten des Spannbaums und hat deshalb den<br />

// Abstand 0 vom Spannbaum<br />

Die Methode init(…) initialisiert für jeden Knoten den Abstand vom Spannbaum. Am Anfang haben<br />

alle Knoten bis auf den Startknoten den Abstand unendlich.<br />

Die Methode test(…) wird aufgerufen, wenn eine neue Kante (und somit auch ein neuer Knoten u) in<br />

den MST aufgenommen wird. Die Methode wird dabei verwendet, um zu testen, ob die Nachbarn v<br />

des neuen Knoten u über die inzidenten Kanten von u schneller erreicht werden können als über<br />

Kanten inzident zu den anderen Knoten des MST.<br />

boolean test(e := {u,v})<br />

if (w(e) < d[v]) then // Wenn die aktuelle Kante e geringeres Gewicht hat,<br />

// als das aktuelle Minimum<br />

d[v] := w(e); // wird der Wert des Minimus auf das Gewicht der Kante e gesetzt<br />

α[v] := u;<br />

endif<br />

In dieser Variante wird bei einem erfolgreichen Test für den Knoten v der Knoten u als derjenige<br />

Knoten gespeichert, über den v am schnellsten erreicht wird. Man könnte aber auch die Kante speichern,<br />

über die der Knoten v mit dem geringsten Gewicht erreicht wird. Das Ziel beider Methoden ist<br />

es, dass am Ende des Algorithmus nachvollziehbar ist, welche Kanten den MST bilden.<br />

21


Hauptmethode<br />

Die Hauptmethode benötigt die Hilfsmengen MST und queue. Wie schon erwähnt, ist die Priorität der<br />

Knoten in der Prioritätswarteschlange gleich dem Abstand vom Spannbaum und nicht gleich dem<br />

Abstand vom Startknoten s.<br />

Vorarbeiten: Mit der Methode init(…) wird der Anfangszustand der Abstände initialisiert. Alle<br />

Knoten haben einen unendlichen Abstand von s. Nur s hat Abstand 0.<br />

Eine Prioritätswarteschlange PQ und eine Menge zur Speicherung der Spannbaumknoten<br />

MST wird erzeugt.<br />

Schleife: Solange noch nicht alle Koten im Spannbaum aufgenommen sind, wird die Schleife<br />

ausgeführt.<br />

1. Entnehme den Knoten u mit der geringsten Distanz zum Spannbaum aus der<br />

Prioritätsschlange PQ.<br />

2. Füge den Knoten in die Menge der Knoten des Spannbaums ein (MST).<br />

3. Überprüfe mit test(…), ob Nachbarknoten v von u über u eine geringere Distanz<br />

zum Spannbaum haben.<br />

4. Verändere die Prioritäten der Nachbarn entsprechend dem Ergebnis.<br />

prim (G :=(V,E), Gewichtsfunktion w, Startknoten s)<br />

init(G, s)<br />

MST := {}<br />

PQ := Prioritärsschlange<br />

PQ.insert(s, d[s])<br />

while (|MST| < n) do<br />

u := PQ.removeMin()<br />

MST.insert(u)<br />

forall (Nachbarn v von u) do<br />

if (d[v] == ∞) then<br />

PQ.insert(v, d[v])<br />

endif<br />

if (test({u,v}) then<br />

PQ.updatePrio(v, d[v])<br />

endif<br />

endforall<br />

endwhile<br />

22

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!