.
.
Lineare dynamische Datenstrukturen
Steffen Reith
reith@informatik.fh-wiesbaden.de
Fachhochschule Wiesbaden
21. Mai 2007
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 1 / 28
.
Einleitung
Bis jetzt: Untersuchung von (einigen) Algorithmen und deren Analyse
Dazu wurden nur Variablen und Arrays von fester Gr¨oße benutzt Vorteil: Einfache Handhabung und Deklaration
Nachteile: Unflexibel, da eine feste Grenze existiert und evtl. sehr viel Speicher unn ¨otig verbraucht wird
Um diese Nachteile zu beseitigen ben ¨otigen wir
Datenstrukturen, die
dynamisch wachsen(und) schrumpfen k ¨onnen.
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 2 / 28
.
.
Datenstrukturen und Wiederverwertbarkeit
Ziel: Unsere Programme sollen wiederverwertbar sein (Verbesserung der Softwarequalit ¨at)
Programmierer sollen unsere Datenstrukturen leicht benutzen k ¨onnen
Die Implementierung soll jederzeit ¨anderbar sein (ohne dass der Benutzer diese bemerkt)
Abstraktion von unn ¨otige Details (z.B. Speicherlayout) Dieses Verbergen von Details von Datenstrukturen ist als
Geheimnisprinzip
bekannt (Programming by contract). Solche Datenstrukturen sind auch als abstrakte Datentypen (ADT) bekannt.
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 3 / 28
.
Abstrakte Datentypen
Konkrete Datentypen: Werden direkt aus den Basisdatentypen bzw. C++-Klassen konstruiert (wie schon bekannt).
Abstrakte Datentypen: Bestehen aus einer Spezifikation der Schnittstelle nach außen. Verf ¨ugbare Operationen und deren Semantik wird beschrieben.
Abstrakte Datentypen entsprechen Softwaremodulen (, dem Prinzip des Klassenkonzepts in OO-Sprachen). Folgende Prinzipien kommen zum Einsatz:
Kapselung: Ein ADT darf nur ¨uber seine Schnittstellen benutzt werden
Geheimnisprinzip: Die interne Realisierung eines ADT ist verborgen
Das Geheimnisprinzip kommt schon bei den Basisdatentypen zur Anwendung.
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 4 / 28
.
.
Die Datenstruktur Stack
Eine einfache Datenstruktur ist der Kellerspeicher (engl. Stack).
Ein Stack ist die Verwirklichung des LIFO-Prinzips (LIFO - Last-In-First-Out-Speicher):
Beim Auslesen eines Elements kann nur auf das zuletzt gespeicherte Element zugegriffen werden
Die n ¨achste Ausleseoperation liefert das vorletzte Element, etc.
Anderer Name f ¨ur Stack: Stapel
Die Schnittstelle eines Stapels besteht aus vier Operationen:
void push(Obj data); (Daten auf den Stack legen) Obj pop(); (Oberstes Element entfernen)
Obj top(); (Oberstes Element auslesen und nicht entfernen ) boolean is empty(); (Test ob Stack leer)
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 5 / 28
.
Ein Beispiel
Gegeben sei ein leerer Stack. Nach push(7); push(12);
push(3); push(17); sieht der Stack wie folgt aus:
7 12 3 17
Und drei pop-Operationen ergeben 17, 3 und 12 (in dieser Reihenfolge). ¨Ubrig bleibt:
7
Mit Feldern k ¨onnen wir sicherlich leicht einen Stapel fester Gr¨oße implementieren. Aber:
Wie machen wir das dynamisch?
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 6 / 28
.
.
Interne Darstellung der Daten
Wir ben ¨otigen eine Hilfsdatenstruktur:
struct Node *next; // Referenz
daten
Referenz (Zeiger) auf nächstes Objekt next
T data; // Nutzdaten }
in C/C++
struct Node {
Nutz−
Objekte dieses Hilfsdatentyps k ¨onnen leicht mit new erzeugt werden.
Deutet die Referenz next auf kein anderes Node-Objekt, so wird der Wert null verwendet.
In C++ wird der struct evtl. durch eine Klasse incl. Zugriffsmethoden implementiert.
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 7 / 28
.
Ablauf der Push-Operation
3 12 7
NULL
head
ptr
17 Schritt 1
ptr head Schritt 2
17 3 12 7
NULL
5
17 3 12 7
NULL
head 5
ptr Schritt 3
17 3 12 7
NULL 5
head Schritt 4
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 8 / 28
.
.
Einige Hinweise zur C++-Implementierung
template <class T> class Stack {
private:
// Node<T> ist eigene Klasse (noch implementieren) Node<T> *head; // Zeiger auf top-Objekt
unsigned long numElements; // Anzahl der Elemente public:
Stack(); // Konstruktor
∼Stack(); // Destruktor
void push(T &elem);// ’elem’ auf Stack ablegen T pop();// ein Element vom Stack holen
bool isEmpty(); // Test ob Stack leer ist };
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 9 / 28
.
Generische Datentypen
Die Programmlogik eines int-Stacks unterscheidet sich nicht von der Programmlogik eines Stacks von Kundendatens¨atzen.
Ziel: Die Implementierung sollte unabh ¨angig vom Nutzdatentyp sein.
Dazu dienen in C so genannte
”templates“ (vgl. List<Integer>
myIntList). In JAVA verwendet man
”Generics“.
Solche parametrierbare abstrakte Datentypen sind als
”generische Datentypen“ bekannt.
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 10 / 28
.
.
Laufzeiten & Anwendungen
F ¨ur die Zeitkomplexit ¨at von Operationen auf einen Stapel ergibt sich:
Operation Zeitkomplexit ¨at is empty O(1)
top O(1) push O(1) pop O(1)
Grund: F ¨ur jede Operation sind nur endlich viele elementare Anweisungen auszuf ¨uhren.
Anwendungen von Stapelspeichern:
Speicherung lokaler Variablen von Unterprogrammen Speichern von R ¨ucksprungadressen
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 11 / 28
.
Warteschlangen
Eine Warteschlange (engl. Queue) ist die Verwirklichung des FIFO-Prinzips (FIFO - First In First Out)
Neue Elemente werden hinten an die Schlange angef ¨ugt Alte Elemente werden vorne aus der Warteschlange entfernt
Die Schnittstelle einer Warteschlange besteht aus den Operationen:
append(Obj data); (H ¨ange ein Element hinten an) Obj get(); (Entferne ein Element am Ende)
boolean is empty(); (Test ob Queue leer)
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 12 / 28
.
.
Ein Beispiel
Gegeben sei eine leere Warteschlange. Nach append(7);
append(12); append(3); append(17); erhalten wir:
head
12 3 17
7
tail
Und dreimal get ergibt dann 7, 12 und 3. Zur ¨uck bleibt die Warteschlange:
17
head tail
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 13 / 28
.
Ablauf der Append-Operation (I)
Schritt 1
head ptr
tail
NULL
7 12 3
17
7 12 3
head
tail
NULL
ptr Schritt 2
NULL
ptr NULL
17 Schritt 3
3 7
head
tail
12
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 14 / 28
.
.
Ablauf der Append-Operation (II)
7 12
head
tail
NULL
ptr
17 3
Schritt 4
tail
7 12 3
head NULL
17 Schritt 5
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 15 / 28
.
Ablauf der Get-Operation
Die Referenz head kopieren
Dann die im Node enthaltenen Informationen kopieren
Die Referenz head auf den Nachfolger des head-Knotens setzen Uberfl ¨ussiges Element mit¨ delete freigeben (in JAVA unn ¨otig) Die Verkettungsrichtung ist wichtig!
Neue Elemente werden am
”Schwanz“ eingekettet Alte Elemente werden am
”Kopf“ ausgekettet Annahme: Verkettungsrichtung w ¨are umgekehrt
append ist einfach (Schwanz neu setzen)
getw ¨are schwierig, da keine Referenz auf das Vorg ¨angerelement existiert (⇒ komplette Queue durchlaufen)
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 16 / 28
.
.
Laufzeiten & Anwendungen
F ¨ur die Zeitkomplexit ¨at von Operationen einer Warteschlange ergibt sich:
Operation Zeitkomplexit ¨at append O(1)
get O(1) is empty O(1)
Grund: F ¨ur jede Operation sind nur endlich viele elementare Anweisungen auszuf ¨uhren.
Anwendungen von Warteschlangen:
Warteschlangen von Prozessen in Betriebssystemen (vgl. kfifo.c im Linux Kern)
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 17 / 28
.
Lineare Listen
Bis jetzt: Einf ¨ugen und Entfernen von Elementen nur am Anfang oder Ende der Datenstruktur.
Ziel: Daten sollen geordnet (also sortiert) verwaltet werden und die Datenstruktur soll wieder dynamisch sein
(⇒ Ein- und Ausf ¨ugen an beliebigen Stellen der Datenstruktur) Die Schnittstelle einer linearen Liste besteht aus den Operationen:
insert(Obj data); (Element an der richtigen Stelle einf ¨ugen) Obj remove(Obj data); (Entferne das Element)
boolean is empty(); (Test ob Liste leer)
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 18 / 28
.
.
Die Remove-Operation
Angenommen wir haben die folgende sortierte Liste gegeben:
NULL
3 7 12 17
head
Es soll remove(12) simuliert werden:
.
.
.
1 Setze
”Vorg ¨anger“ auf head.
.
.
.
2 R ¨ucke den
”Vorg ¨anger“ so lange vor, bis er auf ein Objekt deutet, dessen next-Referenz auf das Objekt deutet, das die zu
entfernenden Daten enth ¨alt.
.
.
.
3 Sichere die Referenz auf den direkten Nachfolger des
”Vorg ¨angerobjekts“ in die tempor ¨are Referenz tmpRef.
.
.
.
4 Setze die next-Referenz des
”Vorg ¨angersobjekts“ auf die next-Referenz von tmpRef.
.
.
.
5 L ¨osche das durch tmpPtr referenzierte Objekt mit delete (in JAVA nicht notwendig).
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 19 / 28
.
Ablauf der Remove-Operation (I)
3 7 12
vorgnger
17
NULL head
Schritt 1
3 7 12
head
vorgnger
NULL 17
Schritt 2
12 7
3
vorgnger Schritt 3
head
tmp
17
NULL
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 20 / 28
.
.
Ablauf der Remove-Operation (II)
3
Schritt 4
head
tmp
17
NULL
7 12
NULL
3 7
Schritt 5
head
17
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 21 / 28
.
Die Insert-Operation
Es soll insert(9) simuliert werden:
.
.
.
1 Neuen Node erzeugen (mit new) und Daten in diesem speichern.
.
.
.
2 ”Vorg ¨anger“ auf den Anfang der Liste setzen.
.
.
.
3 ”Vorg ¨anger“ solange vorr ¨ucken, bis er auf das Objekt deutet, nach dem das neue Objekt einsortiert werden soll.
.
.
.
4 next-Referenz von tmpRef auf den Nachfolger des Vorg ¨angerobjekts zeigen lassen.
.
.
.
5 next-Referenz des Vorg ¨angerobjekts auf das im ersten Schritt erzeugte Objekt deuten lassen vorgaenger.next = tmpRef;.
Wir erhalten:
NULL
3 7 9 17
head
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 22 / 28
.
.
Ablauf der Insert-Operation (I)
3 17
NULL
Schritt 1
head
tmpRef
7 9
vorgaenger
3 7 9
head
Schritt 2
17
NULL tmpRef
tmpRef NULL 17
3 head
Schritt 3
7 9
vorgaenger
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 23 / 28
.
Ablauf der Insert-Operation (II)
vorgaenger
9 3
Schritt 4
head
tmpRef
NULL 17
7
3 7
9 head
Schritt 5
17
NULL
tmpRef
vorgaenger
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 24 / 28
.
.
Einige Bemerkungen
Die oben beschriebenen Operationen funktionieren nur auf dem
”inneren“ Teil der Liste.
Sind Kopf oder Schwanz betroffen, so m ¨ussen diese als Sonderf ¨alle ber ¨ucksichtigt werden. (⇒ fehleranf ¨allig & arbeitsintensiv)
Idee: F ¨uge ein
”kleinstes“ und ein
”gr ¨oßtes“ Dummyelement ein (mit ung ¨ultigen Daten).
NULL head
3 7 12 17
−1 9999
gültiger Datenbereich 1 .. 1000
Sentinel
Diese Dummyelemente nennt man auch Sentinels. (engl. W ¨achter)
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 25 / 28
.
Laufzeiten
F ¨ur die Zeitkomplexit ¨at der Operationen auf Listen mit n Elementen ergibt sich:
Operation Zeitkomplexit ¨at insert O(n)
remove O(n) is empty O(1)
Anwendungen von Listen:
Verwaltung von sortierten Dateien
Implementierungsgrundlage f ¨ur Stacks und Queues
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 26 / 28
.
.
Weitere lineare Datenstrukturen
Man kann eine lineare Liste auch in eine Ringliste umwandeln:
head
3 7 12 17
Anwendungen:
Zuteilung von Zeitscheiben in einem Betriebssystem
Sende- und Empfangsbuffer bei der Datenkommunikation
Nachteil linearer Listen: man kann sich nur in eine Richtung bewegen L ¨osung: Verkettungen in zwei Richtungen
NULL head
NULL
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 27 / 28
.
Mischen von statischen und dynamischen Ans ¨atzen
Aufgabe: Speichere und verwalte große Datens¨atze (z.B. vollst ¨andige Personaldatens ¨atze)
L ¨osung 1: Arbeiten mit (dynamischen) Arrays:
Vorteil: schneller Zugriff auf die einzelnen Komponenten
Nachteil: z.B. sind Sortieroperationen sehr aufw¨andig (kopieren) L ¨osung 2: Mischansatz:
Den eigentlichen Datensatz in einem dynamisch erzeugten Objekt ablegen
Einen Schl ¨ussel (z.B. Personalnummer) mit einer Referenz auf das Objekt in einem Array ablegen.
Vorteil: Schnelle Zugriffe auf die Daten und effizientes Sortieren Nachteil: Kompliziertere Implementierung
Steffen Reith Lineare dynamische Datenstrukturen 21. Mai 2007 28 / 28