Kapitel 5: Von Datenstrukturen zu Abstrakten Datentypen

Slides:



Advertisements
Ähnliche Präsentationen
Objektorientierte Programmierung
Advertisements

der Universität Oldenburg
Klassen - Verkettete Liste -
Zusammenfassung der Vorwoche
PKJ 2005/1 Stefan Dissmann Vorwoche - Klasse public class Studierende { private String name, vorname, studiengang; private int matNr, semester; private.
Kapitel 5. Stacks und Queues
10. Grundlagen imperativer Programmiersprachen
Synonyme: Stapel, Keller, LIFO-Liste usw.
Finale Semantik und beobachtbares Verhalten
der Universität Oldenburg
Gliederung Motivation / Grundlagen Sortierverfahren
Java: Objektorientierte Programmierung
Java: Dynamische Datentypen
Indirekte Adressierung
FH-Hof Indirekte Adressierung Richard Göbel. FH-Hof Einfache Speicherung von Daten Eine "einfache" Deklaration definiert direkt eine Speicherplatz für.
Java: Grundlagen der Objektorientierung
Dynamischer Speicher. In einer Funktion wird z.B. mit der Deklaration int i; Speicher auf dem sogenannten Stack reserviert. Wenn die Funktion verlassen.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (06 - Anwendungen von Stapeln und Schlangen) Prof. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Tobias Lauer.
Anwendungen von Stapeln und Schlangen
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (05 – Elementare Datenstrukturen) Prof. Th. Ottmann.
Informatik II, SS 2008 Algorithmen und Datenstrukturen Vorlesung 6 Prof. Dr. Thomas Ottmann Algorithmen & Datenstrukturen, Institut für Informatik Fakultät.
Universität Dortmund, Lehrstuhl Informatik 1 EINI II Einführung in die Informatik für Naturwissenschaftler und Ingenieure.
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Kapitel 10 Claudio Moraga; Gisbert Dittrich FBI Unido
Datentyp  Zusammenfassung von Mengen von "Werten" mit auf
Beispiele für Ausdrucksalgebren
Eine (Gleichungs-)Spezifikation ist ein Paar SPEC = (, E),
Einführung Wat jibt´s denn? Mit Computa kenn´ ick mir aus! Guten Tag,
Institut für Kartographie und Geoinformation Prof. Dr. Lutz Plümer Diskrete Mathematik I Vorlesung Listen-
Programmieren mit JAVA
Vererbung Spezialisierung von Klassen in JAVA möglich durch
PRJ 2007/1 Stefan Dissmann Motivation Problem: gleiche Datenstrukturen werden für verschiedene Objekte gebraucht: z.B. Listen von Studierenden, Kunden,
PKJ 2005/1 Stefan Dissmann Ausblick Es fehlen noch: Möglichkeiten zum Strukturieren größerer Programme Umgang mit variabler Zahl von Elementen Umgang mit.
PKJ 2005/1 Stefan Dissmann Rückblick auf 2005 Was zuletzt in 2005 vorgestellt wurde: Klassen mit Attributen, Methoden und Konstruktoren Referenzen auf.
PKJ 2005/1 Stefan Dissmann Zusammenfassung Bisher im Kurs erarbeitete Konzepte(1): Umgang mit einfachen Datentypen Umgang mit Feldern Umgang mit Referenzen.
PKJ 2005/1 Stefan Dissmann Zusammenfassung der Vorwoche Variable stehen für (einen) Wert, der sich im Programmablauf ändern kann. Variablen besitzen einen.
PKJ 2005/1 Stefan Dissmann Klassenhierarchie Person Kunde Goldkunde Lieferant Object.
PKJ 2005/1 Stefan Dissmann Zusammenfassung Vorwoche Methoden sind mit einem Namen versehene Programmabschnitte besitzen Rückgabetyp, Namen, Parameterliste.
DVG Klassen und Objekte
Weiteres Programm Studium des Breitendurchlaufs Hierzu
Kapitel 2: Datenstrukturen
Stacks Referat im Fach Basisinformationstechnologien von Venelina Koleva.
Einfach verkettete Listen (OOP)
PRJ 2007/1 Stefan Dissmann Verkettete datenstruktur: Liste Problem: Liste, die eine beliebige Zahl von Elementen verwaltet Operationen: Erzeugen, Anfügen,
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Einführung in die Programmierung
Javakurs FSS 2012 Lehrstuhl Stuckenschmidt
Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fakultät.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fakultät.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
2.4 Rekursion Klassifikation und Beispiele
Variablenkonzept Klassisch, in Java Basistyp
Einfach und doppelt verkettete Listen in JAVA by Jens Weibler
Kapitel 5: Von Datenstrukturen zu Abstrakten Datentypen
Kapitel 6: Suchbäume und weitere Sortierverfahren
Programmieren in C Grundlagen C 2
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Mag. Thomas Hilpold, Universität Linz, Institut für Wirtschaftsinformatik – Software Engineering 1 Algorithmen und Datenstrukturen 1 SS 2002 Mag.Thomas.
Kapitel 5Strukturen Information aus der realen Welt werden in einem informationsverarbeitenden System als Daten abgelegt. Diese stellen also eine (vereinfachte)
Java-Kurs Übung Besprechung der Hausaufgabe
Diskrete Mathe Diskrete Mathematik I Listen Vorlesung 4.
Tutorium Software-Engineering SS14 Florian Manghofer.
Pointer. * und &  Bei der Definition int var1; ○ // „normale“ Variable int *var2; ○ // Zeiger auf einen Integer int *var2 = NULL; ○ // … incl. Initialisierung.
Schlange und Stapel.
 Präsentation transkript:

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

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)

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

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.

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.

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;

Speicherfreigabe: In Java: garbage collector. In Pascal: kein garbage collector. Stattdessen: explizit mittels dispose, z.B.: dispose(p^.Vater); dispose(p^.Mutter);

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;

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;

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

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;

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;

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.

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.

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

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.

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) ...

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).

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.

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.

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)

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.

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).

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!

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

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

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; }

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);   }

Doppelt verkettete Listen Zur effizienteren Implementierung von last und concat: doppelt verkettete Listen:

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

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

Stacks: LIFO Die Grundoperationen auf einem Stack entsprechen den bereits definierten Listenoperationen: top  first push  insertFirstEl pop  butFirst

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

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); }

Anwendungen von Stacks Unterstützung von Kontrollstrukturen, z.B. Auswertung algebraischer Ausdrücke, Verwaltung geschachtelter Prozeduraufrufe, speziell bei rekursiven Prozeduren.

Queue (Warteschlange): FIFO Die Grundoperationen auf einer Queue: front  first enqueue  hänge ein Element hinten an dequeue  butFirst

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