11.02.2015 Aufrufe

Übung 1

Übung 1

Übung 1

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

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

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!