Ãbung 1
Ãbung 1
Ãbung 1
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Algorithmen und Datenstrukturen 2<br />
Prof. Dr. C. Stamm christoph.stamm@fhnw.ch Tel.: 056 462 47 44<br />
Übung 1<br />
Ausgabe: KW 38<br />
Besprechung: KW 43<br />
1 Verkettete Liste<br />
Eine verkettete Liste (linked list) ist eine dynamische Datenstruktur zur Speicherung von Objekten, in<br />
Java zur Speicherung von Referenzen auf Objekte. Insofern ist eine verkettete Liste einem Array von<br />
Referenzen ähnlich. Der grosse Unterschied besteht aber darin, dass das Array eine fixe maximale<br />
Länge besitzt, während eine verkettete Liste wachsen und schrumpfen kann, eben dynamisch ist. Auf<br />
ein Element des Arrays kann über den Index direkt zugegriffen werden, da die Abspeicherung der<br />
Elemente in aneinander grenzenden Speichereinheiten erfolgt und somit die Speicheradresse direkt<br />
berechnet werden kann. Im Gegensatz dazu speichert die verkettete Liste die Elemente an beliebigen<br />
Orten auf dem Heap. Daher kann auf Listenelemente nicht über einen Index zugegriffen werden,<br />
stattdessen müssen sie gesucht werden. Ausgehend vom Ausgangspunkt (Anker) kann jeweils von<br />
einem Element auf das nächste Element (bei doppelt verketteten Listen auch noch auf das vorangehende<br />
Element) zugegriffen werden.<br />
Verkettete Listen eigenen sich dafür, eine unbekannte Anzahl von Objekten abzuspeichern, wenn kein<br />
direkter Zugriff auf die einzelnen Objekte verlangt wird und die Anzahl der Objekte stark schwankt. Sie<br />
sind geeignet zur Speicherung von Teilen eines Ganzen, beispielsweise von Bildobjekten in einer<br />
Zeichnung. Sie liegen oft Speichern wie Stack (LIFO = Last In, First Out) oder Queue (FIFO = First In,<br />
First Out) zu Grunde, wenn die Zahl ihrer Elemente grossen Schwankungen unterworfen ist.<br />
Es gibt einfach verkettete Listen und doppelt verkettete Listen. Im ersten Fall kann nur in einer Richtung<br />
durch die Liste traversiert werden, im anderen Fall in beiden Richtungen. Verkettete Listen können<br />
als lineare Listen oder Ringlisten (das Ende ist wieder mit dem Anfang verkettet) ausgeführt<br />
werden. Sie können ein oder mehrere Einstiegspunkte haben. Oft werden am Anfang und/oder am<br />
Schluss einer Liste so genannte „dummy“-Elemente angefügt, welche keine Nutzinformation enthalten.<br />
Dies hat den Vorteil, dass in den Methoden zur Bearbeitung der Liste keine Fallunterscheidungen<br />
für Randelemente gemacht werden müssen.<br />
einfach verkettete Liste<br />
doppelt verkettete Liste<br />
Anker<br />
Ringliste<br />
Anker<br />
Anker<br />
Jedes Listenelement muss neben der Nutzinformation noch die notwendigen Referenzen zur Verkettung<br />
enthalten. Oft setzt sich deshalb ein Listenelement aus einem Objekt, welches die Verkettungsinformation<br />
enthält, und einer Referenz auf ein Nutzobjekt zusammen.<br />
Beim Verarbeiten von Listen muss darauf geachtet werden, dass jedes Listenelement an einer direkt<br />
oder indirekt zugänglichen Referenz befestigt bleibt. Bei allen Umgebungen, welche keinen Garbage-<br />
Collector haben, muss darauf geachtet werden, dass durch die Listenbearbeitung keine Memory-<br />
Leaks (Speicherlecks) entstehen.<br />
1
1.1 Information Hiding<br />
Alle zuvor eingeführten Datenstrukturen dienen zur dynamischen Verwaltung von Objektmengen. Die<br />
Benutzerin einer dynamischen Datenstruktur möchte die Datenstruktur benutzen können, ohne etwas<br />
über den inneren Aufbau wissen zu müssen. Selbstverständlich braucht sie ein Manual, welches ihr<br />
Auskunft darüber gibt, welche Methoden vorhanden sind und welche Operationen damit erledigt<br />
werden können. Die internen Implementierungsdetails werden jedoch von der Benutzerin versteckt.<br />
Man spricht in diesem Zusammenhang von Information Hiding.<br />
Solange die Benutzerin nur einzelne Objekte in der dynamischen Datenstruktur abspeichern und<br />
diese später wieder lesen möchte, reicht es vollkommen aus die notwendigen Speicher- und Lesebefehle<br />
zu kennen. Der innere Aufbau der Datenstruktur hat zwar einen Einfluss auf die Geschwindigkeit,<br />
ist aber zweitrangig aus funktionaler Sicht. Anders sieht es jedoch aus, wenn die Benutzerin<br />
gezielt alle Objekte der Liste besuchen und eventuell lesen möchte. Entweder weiss sie dann über<br />
den internen Aufbau der Datenstruktur Bescheid (wie zum Beispiel beim Array) oder sie erhält ein<br />
Instrumentarium, um alle Objekte der Reihe nach besuchen zu können. Da ein Offenlegen der internen<br />
Implementierung zu diesem Zweck das Information-Hiding-Prinzip torpedieren würde, ist es wohl<br />
ratsam, ein spezielles Instrumentarium für das Durchlaufen aller Objekte anzubieten. Ein solches<br />
Instrumentarium könnte zum Beispiel ein Iterator sein.<br />
1.2 Iterator<br />
Ein Iterator erlaubt es Ihnen, alle Elemente einer dynamischen Datenstruktur nacheinander zu durchlaufen<br />
(iterieren). Dies ist analog zum Durchlaufen aller Elemente eines Arrays, wo Sie eine Laufvariable<br />
so initialisieren, dass Sie zuerst aufs erste Element zugreifen können und dann in einer for-<br />
Schleife jeweils die Laufvariable erhöhen, bis Sie schliesslich am Ende des Arrays angelangt sind.<br />
Das Collection-Framework von Java verwendet ein solches Iteratorkonzept: Mit der Methode list-<br />
Iterator() aus der abstrakten Basisklasse AbstractList erhalten Sie eine Referenz auf ein neu<br />
erzeugtes Objekt der Klasse ListIterator. Dieses neue Iterator-Objekt zeigt standardmässig<br />
auf den Anfang der Liste Sie können es anschliessend verwenden, um mit der Methode hasNext()<br />
zu überprüfen, ob schon alle Elemente durchlaufen worden sind und falls nicht, mit der Methode<br />
next() das nächste Element zu durchlaufen und zu bekommen.<br />
List l;<br />
// Liste l erzeugen und mit Daten (Strings) füllen<br />
ListIterator it = l.iterator();<br />
while(it.hasNext()) {<br />
System.out.println(it.next());<br />
}<br />
2 Stack<br />
Der Stack (Stapel) ist eine dynamische Datenstruktur, welche dadurch charakterisiert ist, dass man<br />
nur auf das oberste Element des Stapels zugreifen (top), ein neues Element auf den Stapel legen<br />
(push) oder das oberste Element des Stapels entfernen (pop) kann.<br />
Ein Stack kann basierend auf einer einfach verketteten Liste aufgebaut werden. Üblicherweise zeigt<br />
der Anker auf das oberste Element des Stapels, d.h. die Operation push entspricht einem Einfügen<br />
am Anfang der Liste und die Operation pop einem Entfernen am Anfang der Liste.<br />
2
3 Aufgaben<br />
3.1 Stack<br />
Schreiben Sie eine eigene generische Klasse Stack und verwenden Sie als interne Repräsentation<br />
eine verkettete Liste. Bieten Sie nach aussen mindestens die folgenden Methoden an:<br />
T top( )<br />
/ / l i est di e Dat en des ober st en El ement es<br />
void push(T dat a) / / l egt neue Dat en auf den St apel<br />
T pop( )<br />
/ / l öscht das ober st e El ement des St apel s und gibt<br />
/ / den Inhalt zurück<br />
bo o l e / / gia bt t r ue n zur ück, wenn i der s St apel E l eer m i st p t y<br />
3.2 Sackbahnhof<br />
In einem Sackbahnhof mit drei Gleisen befinden sich auf den Gleisen S1 und S2 zwei Züge mit Waggons<br />
für Zielbahnhof A bzw. B. Gleis S3 sei leer.<br />
Betrachten Sie S1, S2 und S3 als Stapel und erstellen Sie ein Programm, bei dem Sie beliebige Züge<br />
eingeben können und das diese Züge so umordnet, dass anschliessend in S1 alle Waggons für A und<br />
in S2 alle Waggons für B stehen.<br />
Schreiben Sie Ihre Gedanken zuerst in Pseudocode auf und setzen Sie sich erst anschliessend an<br />
den Computer, um das Pseudocode-Programm in Java umzusetzen.<br />
S3<br />
BABA<br />
AAB<br />
S2<br />
S1<br />
3.3 Stack-Computer<br />
Ein Stack-Computer ist vergleichbar mit einem wissenschaftlichen HP-Taschenrechner, der Post-Fix-<br />
Operationen abarbeitet. Eine simple Addition in Post-Fix-Notation wird als „3 4 +“ geschrieben. Die<br />
Argumente (hier 3 und 4) werden auf den Stack gelegt und der Operator (im Bespiel +) liest die zwei<br />
obersten Operanden vom Stack, führt die Operation aus und legt das Resultat wiederum auf den<br />
Stack. Eine komplexere Eingabesequenz wäre beispielsweise: „1 10 2 – 3 1 + / +“. Hier wird als<br />
Resultat 3 erwartet.<br />
Entwickeln Sie ein Java-Programm, welches die Texteingabe mit System.in.read() zeichenweise<br />
einliest und beim Ende der Zeichenkette den obersten Wert des Stacks ausgibt. Beachten Sie dabei<br />
bitte, dass die Eingabe nicht mit einen String-Reader eingelesen und mit einem Splitter in einzelne<br />
Tokens aufgeteilt werden soll, sondern dass Sie nach jedem eingelesen Zeichen sofort entscheiden<br />
können, welche Aktion ausgeführt werden muss.<br />
3