Lineare dynamische Datenstrukturen Einleitung

Werbung
Lineare dynamische Datenstrukturen
Steffen Reith
[email protected]
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öße benutzt
Vorteil: Einfache Handhabung und Deklaration
Nachteile: Unflexibel, da eine feste Grenze existiert und evtl. sehr
viel Speicher unnötig verbraucht wird
Um diese Nachteile zu beseitigen benötigen wir
Datenstrukturen, die dynamisch wachsen (und) schrumpfen
können.
Steffen Reith
Lineare dynamische Datenstrukturen
21. Mai 2007
2 / 28
Datenstrukturen und Wiederverwertbarkeit
Ziel: Unsere Programme sollen wiederverwertbar sein (Verbesserung
der Softwarequalität)
Programmierer sollen unsere Datenstrukturen leicht benutzen
können
Die Implementierung soll jederzeit änderbar sein (ohne dass der
Benutzer diese bemerkt)
Abstraktion von unnötige 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ügbare 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 über 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ächste Ausleseoperation liefert das vorletzte Element, etc.
Anderer Name für 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). Übrig bleibt:
7
Mit Feldern können wir sicherlich leicht einen Stapel fester Größe
implementieren. Aber:
Wie machen wir das dynamisch?
Steffen Reith
Lineare dynamische Datenstrukturen
21. Mai 2007
6 / 28
Interne Darstellung der Daten
Wir benötigen eine Hilfsdatenstruktur:
Nutz−
daten
struct Node {
T data; // Nutzdaten
struct Node *next; // Referenz
}
in C/C++
next
Referenz (Zeiger) auf
nächstes Objekt
Objekte dieses Hilfsdatentyps können 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
Schritt 1
NULL
17
3
12
7
17
3
12
7
head
ptr
Schritt 2
NULL
head
5
ptr
Schritt 3
NULL
5
ptr
17
3
12
7
head
Schritt 4
NULL
5
17
3
12
7
head
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ätzen.
Ziel: Die Implementierung sollte unabhängig 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ür die Zeitkomplexität von Operationen auf einen Stapel ergibt sich:
Operation
is empty
top
push
pop
Zeitkomplexität
O(1)
O(1)
O(1)
O(1)
Grund: Für jede Operation sind nur endlich viele elementare
Anweisungen auszuführen.
Anwendungen von Stapelspeichern:
Speicherung lokaler Variablen von Unterprogrammen
Speichern von Rücksprungadressen
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ügt
Alte Elemente werden vorne aus der Warteschlange entfernt
Die Schnittstelle einer Warteschlange besteht aus den Operationen:
append(Obj data); (Hänge 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:
7
3
12
17
head
tail
Und dreimal get ergibt dann 7, 12 und 3. Zurück bleibt die
Warteschlange:
17
head
Steffen Reith
tail
Lineare dynamische Datenstrukturen
21. Mai 2007
13 / 28
Ablauf der Append-Operation (I)
tail
Schritt 1
NULL
7
12
3
head
ptr
tail
Schritt 2
NULL
7
12
3
17
ptr
head
tail
Schritt 3
NULL
7
12
3
head
Steffen Reith
17
ptr
Lineare dynamische Datenstrukturen
NULL
21. Mai 2007
14 / 28
Ablauf der Append-Operation (II)
tail
Schritt 4
7
12
3
17
head
NULL
ptr
tail
Schritt 5
7
12
3
17
head
Steffen Reith
NULL
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
Überflüssiges Element mit delete freigeben (in JAVA unnötig)
Die Verkettungsrichtung ist wichtig!
Neue Elemente werden am Schwanz“ eingekettet
”
Alte Elemente werden am Kopf“ ausgekettet
”
Annahme: Verkettungsrichtung wäre umgekehrt
append ist einfach (Schwanz neu setzen)
get wäre schwierig, da keine Referenz auf das Vorgängerelement
existiert (⇒ komplette Queue durchlaufen)
Steffen Reith
Lineare dynamische Datenstrukturen
21. Mai 2007
16 / 28
Laufzeiten & Anwendungen
Für die Zeitkomplexität von Operationen einer Warteschlange ergibt
sich:
Operation
append
get
is empty
Zeitkomplexität
O(1)
O(1)
O(1)
Grund: Für jede Operation sind nur endlich viele elementare
Anweisungen auszuführen.
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ügen 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ügen an beliebigen Stellen der Datenstruktur)
Die Schnittstelle einer linearen Liste besteht aus den Operationen:
insert(Obj data); (Element an der richtigen Stelle einfügen)
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:
3
7
12
17
head
NULL
Es soll remove(12) simuliert werden:
1
Setze Vorgänger“ auf head.
”
2
Rücke den Vorgänger“ so lange vor, bis er auf ein Objekt deutet,
”
dessen next-Referenz auf das Objekt deutet, das die zu
entfernenden Daten enthält.
3
Sichere die Referenz auf den direkten Nachfolger des
Vorgängerobjekts“ in die temporäre Referenz tmpRef.
”
4
Setze die next-Referenz des Vorgängersobjekts“ auf die
”
next-Referenz von tmpRef.
5
Lösche 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)
Schritt 1
3
7
12
17
head
NULL
vorgnger
Schritt 2
3
7
12
17
head
NULL
vorgnger
Schritt 3
3
7
12
head
NULL
vorgnger
Steffen Reith
17
tmp
Lineare dynamische Datenstrukturen
21. Mai 2007
20 / 28
Ablauf der Remove-Operation (II)
Schritt 4
3
7
12
17
head
NULL
tmp
Schritt 5
3
7
17
head
NULL
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änger“ auf den Anfang der Liste setzen.
”
Vorgänger“ solange vorrücken, bis er auf das Objekt deutet, nach
”
dem das neue Objekt einsortiert werden soll.
3
4
next-Referenz von tmpRef auf den Nachfolger des
Vorgängerobjekts zeigen lassen.
5
next-Referenz des Vorgängerobjekts auf das im ersten Schritt
erzeugte Objekt deuten lassen vorgaenger.next = tmpRef;.
Wir erhalten:
3
7
9
head
Steffen Reith
17
NULL
Lineare dynamische Datenstrukturen
21. Mai 2007
22 / 28
Ablauf der Insert-Operation (I)
Schritt 1
3
7
17
9
head
NULL
tmpRef
Schritt 2
3
7
17
9
head
NULL
vorgaenger
tmpRef
Schritt 3
3
7
17
9
head
NULL
tmpRef
vorgaenger
Steffen Reith
Lineare dynamische Datenstrukturen
21. Mai 2007
23 / 28
21. Mai 2007
24 / 28
Ablauf der Insert-Operation (II)
Schritt 4
3
7
17
head
NULL
vorgaenger
tmpRef
9
3
7
Schritt 5
head
17
NULL
vorgaenger
tmpRef
Steffen Reith
9
Lineare dynamische Datenstrukturen
Einige Bemerkungen
Die oben beschriebenen Operationen funktionieren nur auf dem
inneren“ Teil der Liste.
”
Sind Kopf oder Schwanz betroffen, so müssen diese als Sonderfälle
berücksichtigt werden. (⇒ fehleranfällig & arbeitsintensiv)
Idee: Füge ein kleinstes“ und ein größtes“ Dummyelement ein (mit
”
”
ungültigen Daten).
gültiger Datenbereich 1 .. 1000
−1
3
7
12
17
head
9999
NULL
Sentinel
Diese Dummyelemente nennt man auch Sentinels. (engl. Wächter)
Steffen Reith
Lineare dynamische Datenstrukturen
21. Mai 2007
25 / 28
Laufzeiten
Für die Zeitkomplexität der Operationen auf Listen mit n Elementen
ergibt sich:
Operation
insert
remove
is empty
Zeitkomplexität
O(n)
O(n)
O(1)
Anwendungen von Listen:
Verwaltung von sortierten Dateien
Implementierungsgrundlage für 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:
3
7
12
17
head
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ösung: Verkettungen in zwei Richtungen
NULL
NULL
head
Steffen Reith
Lineare dynamische Datenstrukturen
21. Mai 2007
27 / 28
Mischen von statischen und dynamischen Ansätzen
Aufgabe: Speichere und verwalte große Datensätze (z.B. vollständige
Personaldatensätze)
Lösung 1: Arbeiten mit (dynamischen) Arrays:
Vorteil: schneller Zugriff auf die einzelnen Komponenten
Nachteil: z.B. sind Sortieroperationen sehr aufwändig (kopieren)
Lösung 2: Mischansatz:
Den eigentlichen Datensatz in einem dynamisch erzeugten Objekt
ablegen
Einen Schlüssel (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
Herunterladen