TU Kaiserslautern Prof. Dr. A. Poetzsch-Heffter Dipl.-Inf. J.-M. Gaillourdet Fachbereich Informatik Dipl.-Inf. P. Michel AG Softwaretechnik Übungsblatt 11: Software-Entwicklung 1 (WS 2009/10) Ausgabe: in der Woche vom 18.01. bis zum 22.01. Abgabe: in der Woche vom 25.01. bis zum 29.01. Abnahme: max. zwei Tage nach der Übung Aufgabe 1 Heapsort (Präsenzaufgabe) Sortieren Sie die Liste [23,1701,42,815,13] unter Verwendung des Heapsort-Algorithmus und geben Sie dabei alle entstehenden Bäume an. Kennzeichnen Sie Bäume, welche die Heap-Eigenschaft verletzen. Aufgabe 2 Listen (Präsenzaufgabe) Implementieren Sie einen Verbunddatentyp für die Verwaltung von Listen von double-Werten in Java. Die Liste soll mit Hilfe eines Arrays realisiert werden, d.h. alle Elemente der Liste werden in einem Array der passenden Größe verwaltet. Folgende Verbunde und Prozeduren sollen Sie realisieren: Programmkonstrukt Beschreibung class List { ... } der zu entwerfende Verbunddatentyp List create() Erzeugt eine neue leere Liste. void append(List l, double elem) Hängt ein weiteres Element an die Liste an. int size(List l) Liefert die Anzahl der Listenelemente. double get(List l, int index) Liefert das Element der Liste an der Stelle index. void remove(List l, int index) Entfernt das Element mit index aus l. (Alle nachfolgenden Elemente ändern entsprechend ihren Index.) boolean contains(List l, double elem) Testet, ob elem in l enthalten ist. Aufgabe 3 Listen und Komplexität (Einreichaufgabe) a) Betrachten Sie eine Sequenz von n Aufrufen der Prozedur append mit vorheriger Erzeugung (create) der leeren Liste. Leiten Sie die Aufwandsfunktion A(n) für diese Sequenz von Aufrufen her und bestimmen Sie dann deren Komplexitätsklasse. Als Kostenmaß verwenden Sie einheitliche Kosten von 1 für Zuweisungen, Vergleiche, arithmetischen Operationen, Verbundkomponentenzugriffe und Feldzugriffe. Die Kosten für das Erzeugen eines neuen Verbundobjekts sollen n+1 sein, wobei n die Anzahl der Komponenten des Verbunds sind. Im speziellen Fall von Feldern sollen sie ebenfalls n + 1 betragen, wobei n hier die Länge des Feldes ist. b) Wir betrachten nun eine mögliche Optimierung der implementierten Listen. Dazu erweitern wir den Zustand des Listenverbunds um eine Komponente für die Anzahl der tatsächlich belegten Arrayzellen. Jedesmal wenn das Array vergrößert werden muss, legen wir es größer an als nötig, wir verdoppeln es in der Länge. Implementieren Sie diese Optimierung und passen Sie die restlichen Prozeduren dementsprechend an. Was sind die Vor- und Nachteile dieser Implementierung und in welchen Fällen sind diese besonders relevant? c) Führen Sie erneut eine Analyse der Zeitkomplexität durch, wie Sie es in a) für die einfache Implementierung getan haben. Gehen Sie hier allerdings vereinfachend davon aus, dass es ein k mit n = 2k + 1 gibt. Hinweis: Geben Sie bei Prozeduren mit Fallunterscheidungen verschiedene Aufwandsfunktionen für die einzelnen Fälle an und verwenden Sie an den Aufrufstellen dieser Prozeduren die entsprechend auf die Parameter passende Variante. Aufgabe 4 Speicherverbrauch (Einreichaufgabe) In dieser Aufgabe betrachten wir eine Graphimplementierung, wo Knoten durch Verbunde dargestellt werden, die aus einem Wert und einem Feld von Knoten-Nachfolgern bestehen: class Knoten { int wert; Knoten [] nachfolger ; } Wir wollen nun den Speicherbedarf von Graph-Geflechten bestimmen. Dabei dürfen Sie folgendes annehmen: • Der Speicherverbrauch eines Werts vom Typ int ist 4, der eines Werts vom Typ boolean ist 1. • Der Speicherverbrauch einer Referenz ist 4, egal ob Referenz auf einen Verbund oder ein Feld. • Der Speicherverbrauch eines Feldes ist Länge mal Speicherverbrauch seines Elementtyps plus der Konstanten 4, für den int-Eintrag, der die Länge des Feldes repräsentiert. • Der Speicherverbrauch eines Verbunds ist die Summe des Speicherverbrauchs seiner Komponenten. • Als Speicherverbrauch eines Geflechts – gegeben durch eine erste Referenz – bezeichnen wir die Summe des Speicherverbrauchs aller Verbunde, die zu diesem Geflecht gehören, also von der Referenz aus erreichbar sind. a) Schreiben Sie eine Prozedur int speicherVerbrauch(Knoten k), die rekursiv den Speicherverbrauch des von k erreichbaren Graphen bestimmt. Welche Art von Graphen sind besonders problematisch? Hinweis: Sie können in Ihrer Implementierung eine Listen-Implementierung aus Aufgabe 2 oder Aufgabe 3 verwenden, indem Sie den Element-Typ anpassen. Laden Sie sich die Datei “TestMain.java” von der Vorlesungsseite und kopieren Sie die darin enthaltene main Prozedur in Ihr Programm. Sie testet Ihre speicherVerbrauch-Prozedur mit zwei Graphen, für die als Ergebnis jeweils 336 herauskommen sollte. b) Schätzen Sie die Zeitkomplexität Ihrer speicherVerbrauch-Prozedur für den schlechtesten Fall ab, in Abhängigkeit von der Anzahl n der Knoten des Graphen. c) Entwickeln Sie eine andere Lösung, die – bei gleicher Speicherkomplexität – eine bessere Zeitkomplexität besitzt, indem Sie Anpassungen am Knoten-Verbund vornehmen. Aufgabe 5 Routenplanung (Einreichaufgabe) Am Ende des Abschnitts 4.3 der Vorlesung wurde die Algorithmenentwicklung anhand eines Beispiels erläutert: Routenplanung in einer Stadt. Hier sehen Sie ein Beispiel für einen Stadtplan als Graph mit gewichteten Kanten. Der schnellste Weg vom Startknoten 1 zum Zielknoten 5 führt über die Knoten 2 und 4. Laden Sie sich zu dieser Aufgabe die Datei “Routenplanung.zip” von der Webseite der Vorlesung herunter. Sie enthält u.a. eine Datei namens “Routenplanung.java”, in die Sie bitte Ihre Lösungen einfügen. Es ist jeweils angegeben, wohin welche Lösung gehört. 2 1 2 3 6 3 2 4 1 4 5 9 a) In der Grafik auf der nächsten Seite sehen Sie die Repräsentation des obigen Stadtplans als Geflecht. Erstellen Sie Verbundtypen gemäß dieser Graphik und Code um den Stadtplan als Geflecht anzulegen. b) In der Vorlesung haben Sie gesehen, dass neben dem Graph zur Darstellung der Karte auch noch der Rand des bearbeiteten Areals dargestellt werden muss. Code für den Rand ist in der heruntergeladenen Datei enthalten. Er ist nur als Class-Datei verfügbar. Sie können folgende Klasse und Methoden verwenden. class R.Rand { ... } // Verbund zur Darstellung eines Rand als Heap Rand R. erzeugeRand () { ... } boolean R.leer(Rand r) { ... } Knoten R. naechsterKnoten (Rand r) { ... } // entferne den am naechsten gelegenen // Knoten und liefere ihn zurueck void R. entfernen (Rand r, Knoten k) { ... } // entferne den uebergebenen // Knoten aus dem Rand void R. einfuegen (Rand r, Knoten k) { ... } // fuege einen Knoten zum Rand hinzu Implementieren Sie nun den in der Vorlesung entwickelten Algorithmus zum Finden der kürzesten Route. Testen Sie ihn am obigen Beispiel. c) In dem heruntergeladenen Code ist auch ein Stadtplan enthalten. Testen Sie Ihre Routenplanung mit diesem Anhand der Start- und Zielknoten-Paare, die in der Methode main stehen. Entfernen Sie dazu die entsprechenden Kommentarzeichen. Eine Lösung ist jeweils darunter angegeben. Den gesamten Stadtplan finden Sie in der Datei Stadtplan.pdf. Knoten 0 false dist: inB: pred: ausgehend: 1 ende: gewicht: Kante 2: 1: 0: length: Kante[] 9 3 ende: gewicht: Kante ende: gewicht: Kante Knoten 0 false dist: inB: pred: ausgehend: 4 6 4 Knoten 0: length: 0 1 false Kante[] inB: dist: pred: ausgehend: 2 Kante ende: gewicht: Kante ende: gewicht: 1: 0: length: Kante[] 1 3 2 2 Knoten 0 false dist: inB: pred: ausgehend: 5 ende: gewicht: Kante Kante ende: gewicht: 0: length: 4 false inB: Kante[] 0 dist: pred: 1 Knoten ausgehend: 3