Informatik III: Aufgabenblock 5 1 Graphen repräsentieren

Werbung
Informatik III: Aufgabenblock 5
Version (FWM) vom 6. Oktober 2003
Abgabe vor dem 23. Jan. 2004
1 Graphen repräsentieren
!#"$
% &('*)
In den folgenden beiden Aufgabenblöcken werden Programme und Datenstrukturen zum Umbesteht aus einer Knotenmenge
gang mit Graphen entwickelt. Ein gerichteter Graph
von Knoten und einer Kantenmenge
von Kanten. Auf
dem im Folgenden betrachteten Graphen ist eine Funktion
definiert, die jeder Kante
,
einen Wert zuweist. Zur Vereinfachung definieren wir
.
Im ersten Teil dieser Aufgabe wird eine Datenstruktur entwickelt, in der Graphen gespeichert
werden können. Die Eingabe des Graphen erfolgt durch eine Textdatei, die in dem folgenden
Format gegeben ist:
-+ , . / 0 &1243-565-5789
:
:
Anzahl
:
Anzahl
8
% , % +-, der Knoten
der Kanten
+6, ; vier Zahlen:
&124365-5-5<=89
– Kantennummer /
>&1?43-5-5657 – Startknoten
@&12
3-5-5-5< – Zielknoten
A) .
– % ,
Für jede Kante
Wie man leicht nachvollziehen kann, beschreibt diese Datei
6
1
2
3
4
5
6
7
8
8
1
1
2
2
3
3
4
5
2
4
3
4
4
5
6
6
10.0
10.0
10.0
10.0
100.0
10.0
10.0
10.0
folgenden Graphen, bei dem zur besseren Übersicht die Kantennummern an den Kanten stehen.
Um einen Graphen speichern zu können benötigen wir eine Datenstruktur. Zunächst kann
die Eingabedatei direkt in eine Datenstruktur umgesetzt werden, die die Informationen für jede
Kante enthält.
1
2
1
1
4
4
8
5
3
2
6
7
3
6
5
Abbildung 1: Ein Beispielgraph
8
Definieren Sie dazu eine struct, die die Informationen für eine Kante enthält, und einen
Vektor mit solchen Elementen. Schreiben Sie ein Programm, das eine Eingabedatei liest und
eine solche Datenstruktur aufbaut. Denken Sie an die Modularisierung, d.h. Definitionen kommen in eine Header-Datei, die verschiedenen Aufgaben werden in Unterroutinen modularisiert
und nach Aufgabenbereichen in verschiedenen Dateien gesammelt.
2 Knoten-Kanten-Inzidenzmatrix aufstellen
" 8
12 61?
Eine weitere mögliche
Dartstellung eines Graphen ist die Knoten-Kanten-Inzidenzmatrix. Diese
–Matrix über der Menge
enthält eine Zeile für jeden Knoten und eine Spalte
für jede Kante. Für diese Matrix gilt
ist Startknoten der Kante
ist Zielknoten der Kante
sonst
, 1
1 +,
+,
Der Graph aus Abbildung ?? wird also durch die Matrix
1 1
1
1 1
1
1 1 1
1 1
1
1 1
1 1
dargestellt.
Schreiben sie eine Funktion, die diese Matrix anhand der kantenorientierten Repräsentation
des Graphen aufstellt. Die Matrix soll Elemente vom Typ double enthalten.
2
3 Matrix-Multiplikation
) eine Diagonalmatrix, ) obige Knoten-Kanten-Inzidenzmatrix und
@A) . Für gegebene
rechte Seite soll folgendes lineare Gleichungssystem gelöst werden:
(1)
Sei
Zur Berechnung der Matrix müssen
Sie die Matrix
Diagonalelementen von und dann mit multiplizieren.
transponieren, zeilenweise mit den
Verwenden Sie Ihre Matrixmultiplikation aus Aufgabenblock 1. Schreiben Sie zunächst eine
Funktion, die die Matrix berechnet. Die Diagonalelemente der Matrix können aus den
Kantenattributen berechnet werden, die als Zusatzdaten beim Einlesen des Graphen übergeben
, für .
wurden. Es gilt: , , , /
4 Gleichungssystem lösen
Machen Sie sich noch einmal mit den Routinen zum Lösen eines linearen Gleichungssystems
aus Aufgabenblock 2 vertraut. Verwenden Sie die LU-Zerlegung mit Pivotierung und die folgende Rücksubstitution nun in Ihrem Programm zur Lösung des linearen Gleichungssytems (??).
Denken Sie daran, dass die LU-Zerlegung ”in-place“ ausgeführt wird. Das heißt, wenn Sie die
Matrix später noch einmal benötigen, müssen Sie sie vor der LU-Zerlegung kopieren.
12-12 -1 Warum ist das Gleichungssystem nicht lösbar? Ist es über- oder unterbestimmt?
Was
"!#!#! passiert , wenn man auf den Vektor
anwendet? Was ist der Kern von ?
Um das Gleichungssystem zu lösen, müssen Sie eine Komponente des Vektors auf 0 setzen.
Dies bedeutet in dem Gleichungssystem, dass sie eine Zeile und eine Spalte weniger benutzen.
Versuchen Sie erneut, das Gleichungssystem zu lösen. Stellen Sie eigene Graphen zum Testen
& '!#!#!
auf. Der Beispielgraph aus Abbildung ??, mit Variable %$
, hat mit und
)( die Lösung:
u[1]
u[2]
u[3]
u[4]
u[5]
=
=
=
=
=
51
&
1.284
0.8581
???
0.7097
0.2903
Verifizieren Sie mit Hilfe Ihres Programms diese Angaben und bestimmen Sie u[3].
5 Dünn besetzte Matrizen
Lassen Sie sich die Matrix
mit Ihrer Routine zur Matrixausgabe aus Block 2 ausgeben.
3
8 Die Matrix hat nur wenige Einträge, die von Null verschieden sind. Deren Anzahl lässt
sich durch
abschätzen. Solche Matrizen nennen wir dünn besetzt.
Diese Matrixeigenschaft kann man in Anwendungen auf verschiedene Arten nutzen. Hier
wollen wir sie benutzen, um den Rechenaufwand bei der Berechnung der Matrix erheblich zu
reduzieren.
Lassen Sie sich dazu zunächst mit Ihrem Program die Matrizen für folgende Graphen
ausgeben:
1. Ein Graph mit nur einer Kante und zwei Knoten:
2 1
1 1 2
10.0
2. Ein Graph mit nur einer Kante und acht Knoten:
8 1
1 3 6
10.0
3. Noch ein Graph mit nur einer Kante und acht Knoten:
8 1
1 6 2
20.0
4. Eine Kombination der letzten beiden Graphen:
8 2
1 3 6
2 6 2
10.0
20.0
Wie Sie sehen, haben die Matrizen der ersten drei Graphen nur 4 Nichtnulleinträge. Die Matrix des vierten Graphen lässt sich anscheinend durch Aufsummieren der Matrizen des zweiten
und dritten Graphen erzeugen. Dies gilt auch allgemein (Warum?).
Schreiben Sie eine Funktion, die die Matrix durch Aufsummieren erzeugt. Starten Sie
dabei mit einer Matrix, die nur Nullen enthält. Addieren Sie im Folgenden für jede Kante des
Graphen die 4 Nichtnulleinträge zu dieser Matrix.
Vergleichen Sie das Ergebnis mit der vorher verwendeten Methode, indem Sie die Matrizen
und die Lösungen des Gleichungssystems betrachten. Vergleichen Sie auch die benötigte Laufzeit und den Speicherbedarf der beiden Programme (in Abhängigkeit der Anzahl von Kanten und
Knoten im Graphen).
4
6 Knotenorientierte Graphen-Repräsentation
Im Folgenden wollen wir eine weitere Datenstruktur zur Darstellung von Graphen kennenlernen.
Bei einer Reihe von Algorithmen auf Graphen ist es nötig möglichst schnell auf die Menge der
zu einem Knoten inzidenten Kanten zugreifen zu können. Dies kann man durch eine knotenorientierte Datenstruktur, die zu jedem Knoten eine Liste der inzidenten Kanten speichert. Da
die Anzahl der zu einem Knoten inzidenten Kanten von Knoten zu Knoten unterschiedlich sein
kann, sollten Sie diese Datenstruktur als verkettete Liste anlegen. Jedes Element hat einen Eintrag, in dem die Nummer der Kante gespeichert werden kann und einen Zeiger auf das nächste
Element in der Liste.
typedef struct nachbar {
int kante;
int knoten;
struct nachbar * next;
} t_nachbar;
Zum Erzeugen eines Elements vom Typ t_nachbar müssen Sie Speicherplatz reservieren.
Dazu verwenden Sie die malloc Function:
t_nachbar *ersteKante;
...
ersteKante = (t_nachbar *) malloc(sizeof(t_nachbar));
ersteKante->next = ... ;
ersteKante->kante = ... ;
..
Wenn der so reservierte Speicher nicht mehr gebraucht wird, sollte er wieder freigegeben
werden. Dazu wird die Funktion free verwendet, die als Argument einen Zeiger auf einen
Speicherbereich bekommt, der zuvor mit malloc reserviert worden war, und noch nicht mit
free freigegeben worden ist.
Programmieren Sie eine Unterroutine, die aus einer gegebenen Kantenstruktur die Knotenstruktur aufbaut. Starten Sie mit einem Vektor von Zeigern auf t_nachbar, für jeden Knoten
einen Zeiger. Anfangs sollen die Zeiger 0 sein. Nun werden für jede Kante zwei Elemente von
Typ t_nachbar erzeugt, die jeweils in die Listen der beiden an diese Kante grenzenden Knoten
eingefügt werden.
Testen Sie das Resultat, indem Sie eine Routine schreiben, die die entstandene Datenstruktur
ausgeben kann, indem Sie für jeden Knoten die Nachbarknoten auflistet. Vergleichen Sie das
Resultat mit dem Graphen.
7 Debugger benutzen
Bei dynamischen Datenstrukturen wie verketteten Listen macht man leicht Fehler. Daher ist
zur Fehlersuche ein Werkzeug hilfreich. Dies nennt man Debugger, von der englischen Bezeichnung “Bug” für Fehler. Es gibt text-basierte Debugger, hier ist insbesondere das Programm gdb
5
zu nennen, und es gibt graphische Programme, die die Bedienung des gdb einfacher machen.
Ein solches graphisches Programm ist ddd. Mit dem ddd kann man sich sehr leicht verkettete
Listen und ähnliche Datenstrukturen anzeigen lassen. Testen Sie dies, indem Sie Ihr Programm
mit der Kompileroption -g übersetzen (dies ist notwendig, damit der Compiler ausreichend viele Informationen über den Quelltext in dem übersetzten Programm belässt), und den ddd mit
Ihrem Programm als Argument starten.
Die Bedienung des Debuggers erschließen Sie bitte aus den Hilfe-Texten und der Man-page.
Hier nur ein Hinweis zu Vektoren: Zur Anzeige eines Vektors, der zum Beispiel als int a[10]
deklariert ist, geben Sie links oben a[0] @ 10 ein, und klicken dann auf “Display”. Allgemein
geben sie zunächst das erste Element ein, und dann hinter dem @-Zeichen die Anzahl der anzuzeigenden Elemente.
6
Herunterladen