Präsentation herunterladen
Die Präsentation wird geladen. Bitte warten
Veröffentlicht von:Amalrich Kern Geändert vor über 10 Jahren
1
Kapitel 5: Von Datenstrukturen zu Abstrakten Datentypen
5.1 Zur Erinnerung: Datenstrukturen in Pascal Listenverarbeitung in Pascal 5.2 Abstrakte Datentypen und Objektorientierung Brüche als ADT Implementierung - objektorientiert Brüche als ADT Implementierung - funktional 5.3 Geordnete lineare Zusammenfassungen: Listen bzw. Folgen Cons-Zelle als Basisklasse Statisch-funktionale Listenimplementierung Listenimplementierung mit Objektmethoden 5.4 Stacks und Queues Zustandsorientierte Stack-Implementierung 5.5 Find and Merge 5.5.1 Implementierungen 5.5.2 Implementierung mit Bäumen 5.6 Priority Queues
2
Eigenschaften von Typ-Konstruktoren in Pascal: (nicht in Java)
Array (Feld) Record (Verbund) Set (Menge) Vereinbarung S: Array [N .. N + k-1] of T S: Record F1: T1;... ; Fn: Tn end S: Set of T Selektor S[i] S.Fi Zugriff Index Feldname Enthalten-Test Kardinalität #(T) k #(T1) • ... • #(Tn) 2 #(T)
3
Für in der Tabelle aufgeführte Datentypen:
jeweils ein fester Speicherbedarf. Rekursive Datenstrukturen: Speicherbedarf kann sich ändern! Muss dynamisch zugewiesen (und freigegeben werden). Rekursive Datenstrukturen in Pascal: realisiert durch Zeigertypen
4
Rekursive Datenstrukturen als Zeiger-Typen in Pascal:
Typdefinition: type S = ^T (Zeiger auf Elemente vom Typ T) nil : ausgezeichneter Wert, steht für eine leere (terminierende) Belegung eines beliebigen Zeigertyps. Dereferenzierung: Ist z eine Zeigervariable, so ist z^ der Inhalt der durch die Variable repräsentierten Speicheradresse. Der aktuelle Wertebereich des Typs T ist die Menge aller bisherigen Adressen von T-Variablen ergänzt um nil.
5
Rekursive Datenstrukturen als Zeiger-Typen in Pascal:
Variablendeklaration: var p: T Initialisierung einer T-Variablen: new(p) new(p) erzeugt eine neue (unbenannte) Variable vom Typ T und stellt den benötigten Speicher bereit.
6
Beispiel 1: Stammbaum Speicher-Allokation und Instantiierung:
new(p); p^.Name := "Franz"; p^.Vater := nil; p^.Mutter := nil; new(q); q^.Name := "Georg"; q^.Vater := nil; q^.Mutter := nil; p^.Vater := q; q^.Name := "Maria"; p^.Mutter := q; Grundelement: type Person = RECORD Name: String; Vater, Mutter:^Person END; Deklaration und (leere) Initialisierung: var p,q: ^Person; p := nil; q := nil;
7
Speicherfreigabe: In Java: garbage collector.
In Pascal: kein garbage collector. Stattdessen: explizit mittels dispose, z.B.: dispose(p^.Vater); dispose(p^.Mutter);
8
Beispiel 2: Algebraische Ausdrücke
Deklaration: type TermRef = ^Term; type ExprRef = ^OpExpr; type OpExpr = RECORD Op: Operator; Opd1, Opd2: TermRef END; type Term = case Atomic: Boolean of true: (Atom: Symbol); false: (SubExpr: ExprRef) Wieso nicht einfach ... type ExprRef = ^Expr; type Expr = RECORD Op: Operator; Opd1, Opd2: ExprRef END;
9
Beispiel 3: Listen-Verarbeitung in Pascal:
program list1; type NString = String[20]; Pos = integer; List = ^El; El = Record Content: NString; Id: Pos; Succ: List end;
10
var L1,L2: List; LastPos: integer; Com: String; function isempty(L: List) : boolean; begin isempty := (L=nil) end; procedure newlist(var L: List); new(L); LastPos := 0; L := nil
11
procedure cons(var L: List;
Name: NString); var X: List; begin new(X); X^.Content := Name; X^.Id := LastPos+1; X^.Succ := L; L := X; LastPos := LastPos+1; end; procedure lcons(var L: List; Name: NString); var X,Y: List; begin new(X); X^.Content := Name; X^.Id := LastPos+1; X^.Succ := nil; if isempty(L) then L := X else begin new(Y); Y := L; while NOT ( Y^.Succ = nil ) do Y := Y^.Succ; Y^.Succ := X end; LastPos := LastPos+1;
12
procedure delete(var L: List; Posit: Pos);
var X,Y: List; begin Y := L; if isempty(Y) then (* empty *) else if isempty(Y^.Succ) AND (Y^.Id = Posit) then L := nil else while NOT ( ( Y^.Id = Posit ) OR ( Y^.Succ^.Succ = nil ) ) do Y := Y^.Succ; if Y^.Id = Posit then X:= Y^.Succ; Y^ := X^ end else if (Y^.Succ^.Succ = nil) then if Y^.Succ^.Id = Posit then begin X := Y^.Succ; Y^.Succ := nil; dispose(X) end else (* empty *) end;
13
5.2 Abstrakte Datentypen und Objektorientierung
Abstrakter Datentyp (ADT): Implementierungsunabhängige Spezifikation von Datenstrukturen. (analog zur implementierungsunabhängigen Beschreibung von Algorithmen) Zwei Methoden der ADT-Spezifikation: die algebraische und die axiomatische. Sie haben gemeinsam: die Angabe der Signatur.
14
Signatur legt fest: Sorten (Objektmengen), Operationen,
inbesondere, was für Objekte Eingabe und Ausgabe der Operationen sind. Die Signatur definiert die Syntax und Typstruktur einer Datenstruktur.
15
Beispiel: Menge ganzer Zahlen (IntSet)
Signatur: algebra (bzw. adt) IntSet sorts IntSet, int, boolean ops emptySet: IntSet insertEl: int x IntSet IntSet deleteEl: int x IntSet IntSet member: int x IntSet boolean isEmpty: IntSet boolean
16
Operationale Semantik: Algebraische Spezifikation
gibt als Semantik Algebren an, also Mengen (Semantik der Sorten) mit Funktionen (Semantik der Operationen). sets IntSet = {S | S Teilmenge von Z, S endlich} functions emptySet() := {} insertEl(x,S) := {x} S deleteEl(x,S) := S \ {x} member(x,S) := true falls x in S, false sonst isEmpty(S) := ( S={} ) end IntSet.
17
Operationale Semantik: Axiomatische Methode
spezifiziert die Semantik der Operationen über Axiome (als Postulate): axioms isEmpty(emptySet()) = true isEmpty(insertEl(x,S)) = false (für alle x, S) insertEl(x,insertEl(x,S)) = insertEl(x,S) (dito) member(x,insertEl(x,S)) = true (dito) member(x,deleteEl(x,S)) = false (dito) insertEl(x,deleteEl(x,S)) = insertEl(x,S) (dito) member(x,insertEl(y,S)) = true (für x <> y, alle S) ...
18
Axiomatische Methode Vorteile:
Man muss nur soviel festlegen, wie nötig (gibt Freiheit bei der Implementierung). Beachte: Zu einem axiomatisch spezifizierten Datentyp kann es mehrere verschiedene Algebren geben, die alle Axiome erfüllen (polymorpher Datentyp). Präzise Sprache: ermöglicht evtl. formale Verifikation der Spezifikation. Nachteile Bei größeren Anwendungen: sehr viele Axiome. Spezifikation anhand von Axiomen oft schwer zu verstehen. Charakterisierung einer gewünschten Datenstruktur durch Axiome oft schwer (Widerspruchsfreiheit und Vollständigkeit der Axiome).
19
Abbildung von ADT-Spezifikationen in Programmiersprachen:
Kapselung: In einer ADT-Spezifikation werden zugleich Datentypen und Operationen spezifiziert Operationen sind damit an den Typ gebunden. Daher möglich: Überladung: ein und derselbe Operator kann je nach Typ unterschiedlich implementiert sein. Diese Aspekte finden sich unmittelbar in objektorientierten Programmiersprachen.
20
Beispiel: Brüche als abstrakte Datentypen Algebraische Spezifikation:
algebra Fract sorts Fract, int ops initFract: int x (int \ {0}) Fract normFract: Fract Fract addFract, multFract, ...: Fract x Fract Fract sets Fract = {F=(z,n) | z aus Z, n aus Z \ {0}} functions initFract(x,y) := (x,y) normFract(F) := ... end Fract.
21
Implementierung von Brüchen in Java:
Alternativen: Statisch-funktional: z.B. public static FractionB add(FractionB f1, FractionB f2) {…} Objektorientiert: z.B. public FractionA add(FractionA f2) {…} Der Bruch f1 wird hier implizit verwendet (explizit mittels this)
22
5.3 Geordnete lineare Zusammen- fassungen: Listen bzw. Folgen
Listen: endliche Folgen. Unterschied zu Mengen: Es gibt eine Reihenfolge der Elemente. Ein Element kann auch mehrfach vorkommen. Nebenbei: es gibt auch noch Multisets. Bei ihnen gibt es keine Reihenfolge, aber ein Element kann mehrfach vorkommen.
23
Implementierung von Listen mittels:
statischer Speicherstrukturen: Array Vorteil: - Zugriff auf einzelne Elemente in Zeit O(1). Nachteile: - Listengröße durch Arraygröße beschränkt. - Speicherbedarf: bedingt durch Arraygröße, nicht die tatsächliche (meistens kleinere!) Größe der Liste dynamischer Speicherstrukturen: Zeigerstrukturen. Vorteile: - Beliebig große Listen möglich, Größenänderung während des Programmablaufs kein Problem. - Speicherbedarf: nur der wirklich von der Liste benötigte Platz ((n)). Nachteil: - Zugriff auf einzelne Elemente im Schnitt in Zeit (n).
24
Aufwandsvergleich für Listen als Array bzw
Aufwandsvergleich für Listen als Array bzw. einfach verkettete Zeigerstruktur: Operation Array Einfach verkettete Liste Vorn anfügen O(n) O(1) Hinten anfügen Konkatenation mit |Liste1|=k, |Liste2|=m O(m) O(k) Element-Suche Im Array vorn anfügen kostet O(n). Grund: man muss alles verschieben!
25
Eine Signatur für Listen
algebra List sorts List, El, boolean ops emptyList: List first: List El rest (bzw. butFirst): List List cons (bzw. insertFirstEl): El x List List null (bzw. isEmpty): List Boolean Die folgenden Operationen kann auf die oben angegebenen Operationen zurückgeführt werden: member: El x List boolean concat (bzw. appendList): List x List List
26
Algebraische Spezifikation der Semantik:
sets list = { (a1,…,an) | n 0, ai aus El} functions emptyList = nil first(a1…an) = a1, falls n 1, undefiniert, falls n=0 rest(a1…an) = (a2…an), falls n 1, cons(b, a1…an) = (b a1…an) null(a1…an) = (n=0) concat(a1…an, b1…bm) = (a1,…,an b1…bm) member(b, a1…an) = true, falls es ein i gibt mit ai=b, false sonst
27
Einfach verkettete Listen in Java:
Zuerst: Definition der Klasse einer Cons-Zelle (bestehend aus einem Wert und einem Zeiger auf eine Cons-Zelle). class PCell { private Object elem; private PCell succ; // Konstruktor: PCell(Object c) { elem = c; succ = null; } // Selektor- und Modifikator-Methoden: public Object getEl() { return this.elem; } public void setEl(Object c) { this.elem = c; } public PCell getSucc() { return this.succ; } public void setSucc(PCell next) { this.succ = next; }
28
Implementation Dann: Implementation gemäß der Signatur. Das geht
statisch-funktional public static LiLiS insertFirstEl(Object El, LiLiS L) { PCell h = new PCell(El); if (! isEmpty(L) ) h.setSucc(L.head); return new LiLiS(h); } objektorientiert public LiLiO insertFirstEl(Object El) { PCell h = new PCell(El); if (! this.isEmpty() ) h.setSucc(this.head); return new LiLiO(h); }
29
Doppelt verkettete Listen
Zur effizienteren Implementierung von last und concat: doppelt verkettete Listen:
30
InsertFirstEL in DoLi public static DoLiS insertFirstEl(Object El, DoLiS L) { DoLiS R; DCell h,cf; R = new DoLiS(L); h = new DCell(El); cf = R.head.getSucc(); R.head.setSucc(h); h.setPred(R.head); h.setSucc(cf); if (cf!=R) cf.setPred(h); else R.head.setPred(h); return R; } h cf=a1 R
31
5.4 Stacks und Queues Stacks (Keller) und Queues (Warteschlangen):
Datenstrukturen, die zur dynamischen, reihenfolgeabhängigen Verwaltung beliebiger Elemente dienen. Stacks: LIFO-Prinzip ("last in - first out") Queues: FIFO-Prinzip ("first in - first out") Beide: Spezialfälle von Listen
32
Stacks: LIFO Die Grundoperationen auf einem Stack entsprechen den bereits definierten Listenoperationen: top first push insertFirstEl pop butFirst
33
Stacks: axiomatische ADT-Spezifikation:
adt Stack sorts Stack, El, boolean ops emptyStack: Stack top: Stack El pop: Stack Stack push: El x Stack Stack isEmpty: Stack Boolean axioms isEmpty(emptyStack()) = true isEmpty(push(x,S)) = false pop(emptyStack()) error top(emptyStack()) error pop(push(x,S)) = S top(push(x,S)) = x not isEmpty(S) => push(top(S),pop(S)) = ??? end
34
Implementierung von Stacks:
Man kann Implementierungen von Listen verwenden. Meistens: objektorientiert (Objekte mit Zustandsänderungen). Z.B. pop: ohne Rückgabe, vgl. Skript. // Basis-Methoden: public Object top() { return this.head.getEl(); } public Object pop() { Object t = this.top(); this.head = this.head.getSucc(); return t; public void push(Object El) { PCell h1 = new PCell(El); h1.setSucc(this.head); this.head = h1; } public boolean isEmpty() { return (this == null || this.head==null); }
35
Anwendungen von Stacks
Unterstützung von Kontrollstrukturen, z.B. Auswertung algebraischer Ausdrücke, Verwaltung geschachtelter Prozeduraufrufe, speziell bei rekursiven Prozeduren.
36
Queue (Warteschlange): FIFO
Die Grundoperationen auf einer Queue: front first enqueue hänge ein Element hinten an dequeue butFirst
37
Queues: Axiomatische ADT-Spezifikation
adt Queue sorts Queue, El, boolean ops emptyQueue: Queue front: Queue El dequeue: Queue Queue enqueue: El x Queue Queue isEmpty: Queue Boolean axioms isEmpty(emptyQueue()) = true isEmpty(enqueue(x,Q)) = false isEmpty(Q) => front(enqueue(x,Q)) = x isEmpty(Q) => dequeue(enqueue(x,Q)) = Q not isEmpty(Q) => front(enqueue(x,Q)) = ??? not isEmpty(Q) => dequeue(enqueue(x,Q)) = ??? end
Ähnliche Präsentationen
© 2024 SlidePlayer.org Inc.
All rights reserved.