Benutzer-Werkzeuge

Webseiten-Werkzeuge


graphen:start

Dies ist eine alte Version des Dokuments!


Graphen

Graphen bestehen aus Knoten und Kanten. Kanten verbinden jeweils zwei Knoten. Kanten können gerichtet sein (d.h. sie können nur in einer Richtung durchlaufen werden, ähnlich wie eine Einbahnstraße) oder ungerichtet und sie können Gewichte besitzen (z.B. Länge einer Strecke). Die Gewichte werden an den Kanten notiert. Graphen, deren Kanten keine Gewichte besitzen, heißen ungewichtet (auch: unbewertet).

Folgen von Kanten, die zwei Knoten verbinden heißen Pfad. Ein Pfad, dessen Anfangs- und Endknoten übereinstimmt heißt geschlossener Pfad. Ein Knoten A heißt von einem anderen Knoten B aus erreichbar, wenn es von B nach A einen Pfad gibt. Ein geschlossener Pfad, der keine Kante zweimal enthält, heißt Zyklus. Ein Graph, der mindestens einen Zyklus besitzt, heißt zyklisch.

Ein ungerichteter Graph heißt zusammenhängend, wenn von jedem Knoten zu jedem anderen ein Pfad existiert.

Ein gerichteter Graph heißt

  • stark zusammenhängend, wenn von jedem Knoten zu jedem anderen ein Pfad existiert
    (damit ist natürlich insbesondere gemeint, dass die Kanten in der vorgegebenen Richtung durchlaufen werden),
  • schwach zusammenhängend, wenn er als ungerichteter Graph zusammenhängend wäre und
  • ansonsten unzusammenhängend (auch bei ungerichteten Graphen).

Beispiel 1

Dieser Graph ist gewichtet (die Kantengewichte geben die Straßen-Distanz in Kilometern an), ungerichtet, zyklisch und nicht zusammenhängend.

Knoten wie Helgoland in Beispiel 3, die zu keinem anderen Knoten eine Kante besitzen, heißen isoliert.

Aufgabe 1

Beschreibe die folgenden Graphen mit möglichst vielen der obigen Fachwörter.

a)b)c)d)e)

Lösung

Aufgabe 2

Derselbe Graph kann auf unterschiedliche Weise als Diagramm dargestellt werden. Welche der Diagramme im Bild rechts stellen denselben Graph dar?

Lösung

Aufgabe 3

Nennen Sie je zwei Anwendungsbeispiele, die als gerichtete, ungerichtete oder gewichtete Graphen dargestellt werden können.

Adjazenzmatrix

Die oben verwendete graphische Darstellung ist zwar anschaulich, für algorithmische Verarbeitung aber häufig eher ungünstig. Eine andere Darstellung von Graphen ist ihre Adjazenzmatrix (man nennt zwei Knoten, die durch eine Kante verbunden sind, auch adjazent). Dabei wird eine Art Tabelle aufgestellt, deren Spalten- und Zeilenanzahl der Anzahl der Knoten entspricht (die im Folgenden verwendeten Zeilen- und Spaltenbeschriftungen sind nicht Teil der Adjazenzmatrix und dienen nur der Veranschaulichung). Die Adjazenzmatrix enthält dann als Eintrag

  • 0, wenn es keine Kante zwischen dem links stehenden und dem oben stehenden Knoten gibt
  • 1, wenn es eine Kante zwischen den Knoten gibt und der Graph ungewichtet ist
  • ansonsten das Kantengewicht

Beispiel 2

(Adjazenzmatrix eines gerichteten Graphen)

Knoten 1 Knoten 2 Knoten 3
Knoten 1 0 0 1
Knoten 2 1 0 0
Knoten 3 0 0 0

Beispiel 3

Knoten 1 Knoten 2 Knoten 3
Knoten 1 0 22 17
Knoten 2 22 0 0
Knoten 3 17 0 0

Die Adjazenzmatrix eines ungerichteten Graphen ist immer symmetrisch bzgl. der Diagonale von links oben nach rechts unten.

Aufgabe 4

Modellieren Sie den folgenden Graphen als Adjazenzmatrix. Lösung

Aufgabe 5

Zeichne eine graphische Darstellung des durch die folgende Adjazenzmatrix gegebenen Graphen:

A B C D E
A 8 5
B 6
C 2 4 1
D 5
E 4

Lösung

Modellierung von Graphen durch Adjazenzmatrizen

Graphen werden in Java-Programmen oft implementiert, indem man ihre Adjazenzmatrix in einem zweidimensionalen Integer-Array speichert.

Zweidimensionales int-Array?

Du erinnerst Dich vielleicht noch an eindimensionale Arrays, z.B.

int[] zahlen = new int[7];   //Das Array zahlen enthält 7 Elemente: zahlen[0] bis zahlen[6].
zahlen[0] = 12;              // speichert im ersten Element die Zahl 12
zahlen[5] = 4;               // speichert im 6. Element die Zahl 4;
println(zahlen[5]);   // Gibt das 6. Element aus

Falls Du Dein Wissen über Arrays auffrischen möchtest, schau' Dir die Wiki-Seite aus Jgst. 10 dazu an!

Hier zur Wiederholung eine kleine Fingerübung zu eindimensionalen Arrays (Umkehrung eines Arrays).
Ein zweidimensionales Array speichert "ein Schachbrett voller Zahlen". Die Speicherung von bspw. $4 \times 3$ Zahlen geht so:

int[][] zahlen = new int[4][3];    // Der erste Index geht von 0 bis 3, der zweite von 0 bis 2
zahlen[2][1] = 10;
zahlen[0][2] = 14;

Man kann sich vorstellen, dass das Array zahlen aufgebaut ist wie ein Schachbrett. Der erste Index bezeichnet die Zeile des Feldes, der zweite die Spalte. Nach Ablauf dieses Programms sieht die Belegung des Arrays so aus:

zahlen012
00014
1000
20100
3000

Im Array gespeichert sind natürlich nur die 12 Werte der blauen Zellen. Die fettgedruckten Zeilen- bzw. Spaltenköpfe dienen nur zur Veranschaulichung.

Beispiel 4 - Speicherung eines gerichteten Graphen

Das folgende Programm speichert den Graphen rechts als Adjazenzmatrix:

Die Klasse Graph

Wir entwickeln eine Klasse Graph, die nützliche Methoden zur Verwaltung und Analyse von Graphen bereitstellt.

Den im Testprogramm unten generierten Graphen siehst Du in graphischer Darstellung links neben dem Programmkasten. So kannst Du das Programm schrittweise ausführen und gleichzeitig den Programmverlauf am Graphen mitverfolgen.

Aufgaben:

  • Erstelle eine Methode void adjazenzmatrixAusgaben(), die die Adjazenzmatrix folgendermaßen auf dem Bildschirm ausgibt:
0 1 0
1 0 1
0 1 0
  • Erstelle eine Methode istIsoliert(int knoten), die genau dann true zurückgibt, wenn der Knoten isoliert ist (Definition siehe oben).

Tiefensuche

Wir wollen eine Methode istVerbundenRekursiv(int startknoten, int zielknoten) schreiben, die genau dann true zurückliefert, wenn es einen Pfad vom Startknoten zum Zielknoten gibt. Wir gehen nach folgender Strategie vor:

  • 1.) Überprüfe, ob startknoten == zielknoten. Falls "ja", gib true zurück.
  • 2.) Überprüfe für jeden von startknoten aus erreichbaren Knoten i, ob istVerbundenRekursiv(i, zielknoten) == true ist. Falls "ja", gib true zurück.

Dieses Vorgehen kann bei zyklischen Graphen zu einer unendlichen Methodenaufrufkette führen. Daher muss der Algorithmus speichern, welche Knoten er schon besucht hat und diese bei 2.) ausnehmen. Dies geschieht mittels eines Arrays boolean[] schonBesucht, das für jeden Knoten einen booleschen Wert speichert, der angibt, ob der Knoten schon besucht wurde. Dieses Array wird anfangs in der Methode istVerbunden instanziert und mit false-Werten befüllt. Diese übergibt die Refernz auf dieses Array an die Methode istVerbundenRekursiv. Bei 1.) wird schonBesucht[startknoten] = true gesetzt und bei 2.) wird eine Referenz auf das Array schonBesucht bei jedem weiteren Aufruf von istVerbundenRekursiv weitergereicht.

Die fertigen Methoden istVerbunden und istVerbundenRekursiv sind im obigen Programm schon eingebaut. Der Graph, der im Hauptprogramm aufgebaut wird, ist rechts dargestellt.

Die Methode folgt einem uninformierten Suchverfahren (uninformiert bedeutet hier, dass dem Verfahren über die Einträge der Adjazenzmatrix hinaus keine weiteren Informationen zur Verfügung stehen) mit dem Namen Tiefensuche (engl. depth-first search, DFS). "Bei der Tiefensuche wird zunächst jeder Pfad vollständig in die Tiefe beschritten, bevor abzweigende Pfade beschritten werden." (siehe den entsprechenden Artikel in Wikipedia, der auch eine schöne Animation dazu zeigt).

Ein zweites uninformiertes Suchverfahren wäre die Breitensuche (engl. breadth-first search, BFS). Bei dieser werden "zunächst alle Knoten beschritten, die vom Ausgangsknoten direkt erreichbar sind. Erst danach werden Folgeknoten beschritten." (siehe den entsprechenden Artikel in Wikipedia, und die sehr anschauliche Animation dort.)

Keines der beiden Suchverfahren ist für das allgemeine Problem der Suche in Graphen „besser“ geeignet als das andere.

Beispiel für Tiefen- und Breitensuche

Überprüft man in diesem Graphen, ob es eine Verbindung von Knoten 1 nach Knoten 5 gibt, so durchläuft die Tiefensuche ihn in der Reihenfolge 1,2,4,3 (Sackgasse), 5, die Breitensuche in der Reihenfolge 1,2,3,4,5. Dabei wurde angenommen, dass immer zuerst der Knoten mit der kleinsten Knotennummer besucht wird.

Aufgabe (nicht leicht…):

Erweitere das Programm so, dass die Methoden istVerbunden in dem Fall, dass sie einen Pfad gefunden hat, diesen in der Form 7→0→1→2→3→4 ausgibt.

Lösung

Breitensuche

Schreibe die Methode istVerbunden der Klasse Graph so um, dass sie die Strategie "Breitensuche" nutzt, um herauszufinden, ob vom Startknoten aus der Zielknoten zu erreichen ist.

  • Du wirst eine Warteschlange brauchen, die int-Werte speichern kann. In der Programmbox unten habe ich schon eine für Dich hinterlegt.
  • Ergänze dein Programm so, dass es ausgibt, welche Knoten es gerade untersucht und welche es in der Warteschlange ablegt. Vergleiche seine Ausgabe mit dem Graphen links!

Auf der linken Seite siehst Du eine graphische Darstellung des im Hauptprogramm generierten Testgraphen.

Lösung

graphen/start.1696405403.txt.gz · Zuletzt geändert: 2023/10/04 07:43 von Martin Pabst

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki