2. "Höhere" Datenstrukturen Zur Wiederholung seien hier zunächst die elementaren und aus diesen zusammengesetzte Datenstrukturen erwähnt 2.1 Elementare Datenstrukturen 2.1.1 Boolean (Typbezeichnung boolean oder kurz bool) Wertemenge: IB = {TRUE, FALSE} . Funktionen: Eine mindestens funktional vollständige Menge boolescher Funktionen, z.B. die folgende: not: IB → IB, ∧, ∨, ⊕ : IB × IB → IB Die Semantik der Funktionen ist hier durch Wertetabellen der Aussagenlogik festgelegt. 2.1.2 Character (vorgegebener Zeichensatz, z.B. A = {a, b, c, …, z, 0, 1, …, ?, |, &, …}) Die Wertemenge wird als geordnet definiert. succ: A → A partielle Funktion pred: A → A partielle Funktion Gleichheit = : A × A → IB Funktionen: 2.1.3 Integer Funktionen: +, −, *, /, div, mod, = 2.1.4 Real M Wertemenge: = endliche Einschränkung der Menge der reellen Zahlen Funktionen: +, −, *, / ; spezielle Funktionen exp, sin, cos, …; <, = Funktionen in weichen im allgemeinen von den Funktionen in ab. Z. B.: wenn x, y ∈ und x + y ∉ , dann Ersatz durch eine Approximation (Rundung). M M M Die bekannten Axiome sind daher i. a. falsch! 2.1.5 Aufzählungstyp (enumeration type) Wertemenge: Endliche geordnete Menge (x1, …, xn) von Symbolen, xi ∈ A *, A ein Alphabet (Character) Funktionen: succ, pred (partiell definiert); =, < 2.1.6 Unterbereichstypen Benutzerdefinierte Typen sind oft Einschränkungen von Standardtypen (subtype). Beispiele: type eins_bis_hundert = l..100 Werktag = (Mo..Fr) als Unterbereichstyp von Integer; als Unterbereichstyp von Wochentag. 2.1.7 Zeiger (Pointer) Einem Datentyp wird mittels ref (Bezeichnung nach ALGOL 68) ein Zeiger zugeordnet. Ein Datum vom Typ ref t ist eine Speicheradresse zu einem Datum vom Typ t. Man unterscheidet: • Typ-unabhängige Zeiger, maschinennah, jede Adresse zuweisbar, Typ: pointer • Typ-abhängige Zeiger, ref t zu jedem t. Für die Festlegung von t unterscheidet man zwei Fälle: • t beliebig: ermöglicht z.B. Typen real, ref real, ref ref real, ..., ref n, n > 0 (z.B. in ALGOL 68; schwer zu programmieren) • t einschränken auf nicht-ref-Typen. 2.2 Zusammengesetzte Datenstrukturen 2.2.1 String (Zeichenketten über einem Alphabet A) String of char, String of boolean, ... © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-1 Operationen zur Konstruktion ε() : string ι ([char]) : string °([string], [string]) : string : erzeugt einen leeren String : erzeugt einen String aus einem Zeichen : Konkatenation von Strings Zugriffsoperationen access ([string], [nat]) : char : Zugriff auf ein einzelnes Zeichen length ([string]) : nat : Zugriff auf Länge des Strings Test Operationen = ([string], [string]) : bool < ([string], [string]) : bool 2.2.2 Felder (arrays) Ein- oder mehrdimensionale Speicherung gleichartiger Daten und Zugriff über Indizes. 1-dimensionale Felder: Typbezeichnung: array Typ der Indizes: index Typ der Elemente: elem Operationen zur Konstruktion: create(): array (erzeugt ein Feld bei vorgegebener Indexmenge, aber noch ohne Einträge) assign([array], [index], [elem]): array (Eintrag eines Elementes) Zugriffsoperation: access([array], [index]): elem; Übliche Schreibweisen: (Zugriff auf ein Element) a[i]: = e statt a := assign(a,i,e) a[i] statt access(a,i) Mehrdimensionale Felder: Indexmenge ist ein Cartesisches Produkt mehrerer Komponenten, z.B. I = I1 × I2 × I3 Beispiel: array[1..100,0..30,-20..20] of elem Dies kann identifiziert werden mit array[1..100] of array[0..30] of array[-20..20] of elem 2.2.3 Verbund (record) Gegeben seien k Typen m1, …, mk und k Namen für Selektoren S1,. .., Sk . Die daraus gebildete Struktur eines Verbundes wird mit record(S1: m1; …; Sk: mk) bezeichnet. Jeder konkrete Verbund enthält (bis zu) k Komponenten der Typen m1, …, mk , auf welche mit den Selektoren S1, ..., Sk zugegriffen wird. Programmiersprachliche Notation (PASCAL): r: record S1: m1; ... Sk: mk; end; Zugriff auf die i-te Komponente: r.Si Mathematisch betrachtet entspricht der Verbund einem Cartesischen Produkt. 2.2.4 Speicherstrukturen Stack, Queue, Deque Diese Speicherstrukuren dienen zum Abspeichern und Lesen von Elementen gleichen Typs mit speziellen Zugriffsbedingungen. LIFO (Last-In-First-Out)-Prinzip (Stack oder Kellerspeicher) FIFO (First-In-First-Out)-Prinzip: charakterisiert die Queue oder Warteschlange © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-2 LIFO/FIFO-Kombination double-ended-queue oder kurz Deque :und kann wahlweise wie ein Stack oder eine Queue benutzt werden. Für jede dieser Strukturen führen wir eine Operation create ohne Parameter ein, welche eine leere Struktur der jeweiligen Art erzeugt, sowie eine Testfunktion empty. Für den Datentyp stack, dessen Elemente von einem Typ elem sind, haben wir die folgenden Operationen: create(): stack push ([elem], [stack]): stack pop ([stack]): stack Beachte: pop liefert hier nicht das oberste Stackelement, sondern den um 1 Element reduzierten Stack! top ([stack]): elem empty ([stack]): bool full([stack]): bool Hierzu gehören die folgenden Axiome: 1. top(push(e, s)) = e 2. pop(push(e, s)) = s 3. empty (create) = TRUE 4. empty(push(e, s)) = FALSE. Für die Datenstrukturen queue und deque wird entsprechend verfahren. 2.2.5 Listen Listen sind eigentlich eine Verallgemeinerung von Strings, wobei statt char ein allgemeiner Typ elem zugelassen wird. Wir formatieren die Listen aber so, dass auch die Speicherstrukturen Stack, Queue und Deque darin sichtbar werden: create( ): list appendr([list], [elem]): list appendl([list], [elem]): list concat([list], [list]): list headr([list]): elem headl([list]): elem tailr([list]): list taill([list]): list length([list]): nat access([list], [nat]): elem empty([list]): bool equal([list], [list]): bool Die Semantik wird direkt mit Hilfe der Repräsentation der Listen durch Tupel erklärt: 〈e1, ..., en〉, n ≥ 0 , mit Elementen ei vom Typ elem: create = () appendr(〈e1, ..., en〉, e) = 〈e1, ..., en, e〉 appendl(〈e1, ..., en〉, e) = 〈e, e1, ..., en〉 concat(〈e1, ..., en〉, 〈e1', ..., em' 〉) = 〈e1, ..., en, e1', ..., em' 〉 headr(〈e1, ..., en〉) = en headl(〈e1, ..., en〉) = e1 tailr(〈e1, ..., en〉) = 〈e1, ..., en−1〉 taill(〈e1, ..., en〉) = 〈e2, ..., en〉 access(〈e1, ..., en〉), i) = ei (l < i < n) length(〈e1, ..., en〉) = n empty(〈e1, ..., en〉) = (n = 0 TRUE, FALSE) equal(〈e1, ..., en〉, 〈e1', ..., em' 〉) = ((n = m ∧ ei = e'i für alle i ∈ {1, ..., n}) TRUE, FALSE) 2.2.6 Mengen Der Datentyp set of elem wird benutzt zur Beschreibung von Mengen {e1, ..., en} mit e1, ..., en ∈ E, wobei E eine Menge von Elementen vom Typ elem ist. Darauf sind folgende Operationen definiert: create( ): set append([set], [elem]): set ∩ ([set], [set]): set ∪([set], [set]): set card([set]): nat ∈ : ([elem], [set]): bool ⊆ : ([set], [set]): bool = : ([set], [set]): bool Die Semantik ergibt sich aus den bekannten mengentheoretischen Operationen. © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-3 2.3 Dateien In vielen Anwendungen, insbesondere bei Datenbanken, sind große Mengen von Sätzen (Records) gleicher Struktur im Sekundärspeicher (z.B. Festplatte) zu speichern. Datei (File) = Mengen von Sätzen gleicher Struktur. Typische Operationen auf Dateien sind: • Einfügen eines Satzes • Löschen eines Satzes • Modifizieren eines Satzes • Auffinden eines Satzes mit einem bestimmten Wert in einem bestimmten Feld, oder einer bestimmten Wertekombination auf einer bestimmten Menge von Recordfeldern. ("lookup Operation") Der Zugriff auf die Sätze einer Datei erfolgt häufig über einen Schlüssel. Dieser muss die Sätze eindeutig identifizieren. 2.3.1 Sequentielle Files Sätze werden hintereinander (sequentiell) angeordnet. Beispiel (Pascal): type person = record p-nummer: longint; ... – p-nummer ist Schlüssel var personendatei : file of person; Es seien nun n Datensätze in beliebiger Reihenfolge gespeichert. − Aufsuchen des i-ten Satzes: mit seek-Funktion (1 Zugriffsoperation) − Aufsuchen eines Satzes mit gegebenem Schlüssel (z.B. p-nummer = 101) erfordert im Mittel n/2 Satzzugriffe. − Löschoperationen sind ähnlich aufwendig. Lücken nach Löschoperationen − Einfügen eines neuen Satzes am File-Ende: konstanter Zeitaufwand. Wenn die Datensätze z.B. nach aufsteigenden Schlüsselwerten in der Datei eingetragen sind: − Aufsuchen der Einfügestelle (≈ log n Schritte) − Einfügen: verschieben aller nachfolgenden Sätze (im Mittel n/2 Zugriffe) − Löschoperationen sind ähnlich aufwendig. Lücken nach Löschoperationen 2 − Ausgabe nach Schlüsseln geordnet: sehr aufwendig O(n ) − Ausgabe nach Schlüsseln geordnet: einfach O(n) 2.3.2 Organisation durch lineare Listen Sätze werden mit Zeigern verkettet. - ungeordnetes Eintragen der Sätze: aufwendiges Suchen O(n) Einfügen neuer Sätze: einfach O(1) Löschoperationen: einfach (jedoch zuerst Suchoperation erforderlich) O(1) 2 Ausgabe nach Schlüsseln geordnet: sehr aufwendig O(n ) - Eintragen nach Schlüsseln geordnet: aufwendiges Suchen O(n) Einfügen neuer Sätze: aufwendig O(n) Löschoperationen einfach (jedoch zuerst Suchoperation erforderlich) O(1) Ausgabe nach Schlüsseln geordnet: einfach O(n) 2.3.3 Indexsequentieller Zugriff Die Datei ist in Blöcken organisiert. Jeder Record besitzt einen eindeutigen Schlüssel. Jeder Block kann mehrere Records aufnehmen. Blöcke sind nicht notwendig voll. Zusätzlich gibt es einen Indexfile (sequentiell organisiert). Im Indexfile sind Record-Schlüssel zusammen mit Verweisen auf Blocknummern eingetragen. © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-4 Aachen Aachen Augsburg Info zu Aachen Info zu Augsburg Berlin Bochum Clausthal Info zu Berlin Info zu Bremen Info zu Clausthal Block 1 Berlin Darmstadt Zittau Darmstadt Info zu Darmstadt Dortmund Info zu Dortmund Block 2 Block 3 nur Schlüssel (alphabetisch geordnet) Schlüssel weitere Information Operationen: − Suchen: 1. auf Indexfile (binär, log m bei m Einträgen) 2. im Block (linear durchsuchen) − Löschen: Eintrag im Block löschen. Wenn der erste Eintrag im Block gelöscht wird: Eintrag im Indexfile ändern Wenn der Block leer ist, dann kann er „freigegeben" werden. Eintrag im Indexfile löschen − Einfügen: Im Block, sofern noch Platz ist. Andernfalls neuen Block anlegen. Schlüssel im Indexfile an der richtigen Stelle eintragen. Im folgenden drei Datenstrukturen (Hashing, Bäume, B-Bäume) haben ein besseres zeitliches Verhalten bei den Standardoperationen zum Ziel. 2.3.4 Hashing (gestreute Speicherung) Prinzip: Hash-Tabelle steht für die Aufnahme von m ∈ IN Sätzen zur Verfügung. Die Position, in der der Satz gespeichert wird, wird aus dem Schlüssel berechnet; Hash-Funktion Sei S die Menge aller möglichen Schlüsselwerte; Sei h: S → {0, ..., m − 1) eine Hash-Funktion Vorgehen: der erste Satz (Schlüssel s1) wird in Position h(s1) eingetragen der zweite in h(s2), usw. Dabei können Kollisionen auftreten: Schlüssel si und sj mit si ≠ sj und h(si) = h(sj). Mögliche Verfahren im Kollisionsfall: (1) re-hashing: statt einer Hash-Funktion werden m verschiedene Hash-Funktionen gewählt Bei Kollision wird die nächste Hash-Funktion genommen Beispiel: gegeben h0 = h : S → {0, ..., m − 1} hi(s) := (h(s) + i) mod m, i = 1, ..., m − 1 (2) Überlaufliste: Bei Kollision wird der neue Satz in eine Liste eingetragen (3) m Überlauflisten: jede Zeile der Hash-Tabelle besitzt einen Pointer zu einer eigenen Verhalten beim Aufsuchen und Löschen: von der Kollisionsbehandlung abhängig - Re-hashing: maximal O(m) Zugriffe auf die Hash-Tabelle erforderlich - Überlauflisten: max. n Schritte Forderungen an Hash-Funktionen: sollen möglichst gut "streuen" ("gestreute Speicherung"), damit eine gleichmäßige Belegung der Hash-Tabelle mit möglichst wenig Kollisionen erreicht wird Bei guter Streuung sehr schnell (nur 1 Tabellen-Zugriff) Nachteil: Sortiertes Ausgeben der Sätze nicht möglich 2.3.5 Binäre Bäume Jeder Knoten nimmt l Record auf und besitzt außerdem zwei Pointer (auf den linken und den rechten Nachfolgeknoten). © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-5 Beispiel: Bochum Augsburg Aachen Clausthal nil Berlin nil nil nil nil Dortmund nil Darmstadt nil nil Sortiertes Einfügen erfolgt "In-Order": links die kleineren, rechts die größeren Schlüssel. Suchen eines Satzes mit gegebenem Schlüssel: Zeitaufwand ist proportional zur Höhe des Baumes. Löschen eines Satzes: Zuerst aufsuchen des Satzes. Zeitaufwand für das Löschen selbst ist konstant. Einfügen eines Satzes: Zuerst Aufsuchen eines Blattes, an das der neue Satz angehängt wird. Einfügen selbst braucht konstanten Zeitaufwand. Wenn ein binärer Baum n Knoten hat, dann gilt für dessen Höhe h: log2 n ≤ h ≤ n Wenn h ≈ log2 n dann haben alle Blätter ungefähr die gleiche Tiefe ("ausgeglichener Baum"). Wenn h ≈ n, dann liegt ein "entarteter Baum" vor. Ob ein Baum entartet ist oder nicht, hängt von der Reihenfolge ab, in der die Sätze eingetragen werden. Definition (ausgeglichener Baum). Sei im Knoten i der Zeiger zum linken Nachfolger mit NL(i) und zum rechten Nachfolger mit NR(i) bezeichnet. Die linke und rechte Tiefe des Knotens i ist rekursiv definiert als 0 falls NL(i) = nil 0 falls NR(i) = nil TL(i) := TR(i) := max{TL(j), TR(j)} + 1 sonst max{TL(k), TR(k)} + 1 sonst Dabei bedeuten j und k den linken und rechten Nachfolgerknoten von i (falls vorhanden). Die Balance von i ist definiert als B(i) := abs(TR(i) − TL(i)). Der Baum heißt ausgeglichen, falls B(i) ≤ 1 gilt. Adelson-Algorithmus: reorganisiert den Baum, sobald er nicht ausgeglichen ist. Beispiel: TL(c) = 1, TR(c) = 3 ⇒ B(c) = 2; c d Reorganisation a m m c d r a f r f Nach jedem Einfüge- bzw. Löschschritt wird ggf. reorganisiert. 2.3.6 Geordnete Bäume einer festen Ordnung k Jeder Knoten hat m ≤ k Nachfolger Jedem Nachfolger ist eindeutig ein Index aus {1, ..., k} zugeordnet (injektiv) 1., 2., ..., k-ter Nachfolger; dabei können auch einige fehlen Jeder Knoten - nimmt bis zu k − 1 Records auf, die nach Schlüssel geordnet sind − hat bis zu k Pointer, die zu Nachfolger Knoten zeigen. S1 S1 S2 ... Sk-1 ... ... n Höhe des h Baumes bei n Knoten: logk(k−1) ≤ h ≤ n n h = logk(k−1) ... ausgeglichener Baum; alle Blätter ungefähr gleiche Tiefe h ≈ n ... entarteter Baum Operationen © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-6 Suchen: Zeitaufwand O(h) (linear in h) Einfügen: O(h) Löschen: O(h) Ausgabe nach Schlüsseln geordnet: O(n) 2.3.7 B+-Bäume B ... steht für "Bayer" (nicht für "binär!) B+-Bäume erlauben die Definition von Index-Hierarchien. Seien d ≥ 2 und e ≥ 1 natürliche Zahlen Eigenschaften: jeder Weg von der Wurzel zu einem Blatt hat dieselbe Länge B+-Bäume können wachsen oder schrumpfen jeder Knoten ("Block") enthält Platz für 2d − 2 Schlüssel und 2d − 1 ≥ 3 Pointer zu Nachfolgeknoten. die Blockgrösse ist so gewählt, dass mit nur einem Plattenzugriff ein ganzer Block gelesen bzw. geschrieben werden kann in jedem Knoten (ausser dem Wurzelknoten) sind mindestens d Pointer eingetragen die vollständigen Sätze (nicht nur deren Schlüssel) sind in den Blatt-Knoten gespeichert (in jedem Blatt-Knoten: e ≤ Anzahl_gespeicherte_Schlüssel ≤ 2e − 1) jeder Knoten (mit Ausnahme der Blätter) enthält genau 1 Pointer mehr als Schlüssel Die Menge der Blatt-Knoten stellen den "Hauptfile" dar: dort sind die vollständigen Sätze eingetragen. Die im Baum darüberliegenden Blöcke beschreiben die "Indexstruktur" Beispiel: B+-Baum mit d = 2, e = 2 B2 1 4 B5 9 16 9 B6 25 36 B1 25 144 B3 64 100 49 B7 64 B4 81 100 B8 B9 121 196 144 169 B10 196 225 256 B11 Für einen B+-Baum mit n Records (Schlüssel) gilt: Anzahl der Blätter: ≤ n/e Weglänge 1 (Wurzelknoten): ≥ Pointer Weglänge 2: ≥ 2d Pointer ... ≥ 2d l−1 Pointer Weglänge l: n Für die Anzahl der Blätter gilt daher: 2d l−1 ≤ Anzahl Blätter ≤ e ; d.h. ein Baum der Höhe l enthält mindestens n n ≥ 2ed l−1 Records. Bei n im Hauptfile gespeicherten Records sind somit höchstens l ≤ 1 + logd(2e) Blockzugriffe erforderlich. Beispiel: n = 106, e = 5, d = 50 ⇒ l = 1 + log50 105 ≈ 6 Zeitaufwand für Operationen: n Aufsuchen eines Satzes: 1 + logd(2e) Blockzugriffe Einfügen: ° Wenn der betreffende Block B im Hauptfile weniger als 2e − 1 Records enthält: den neuen Satz eintragen. Andernfalls: neuen Block B' erzeugen, und die Sätze von B zusammen mit dem neuen Satz auf B und B' gleichmässig verteilen (jeder bekommt dann e Sätze). Im Vaterblock von B Zeiger auf B' und Schlüssel des ersten Records von B' eintragen. ° Wenn Vaterblock voll ist (d.h. der Eintrag ist nicht möglich), dann Vaterblock splitten ähnlich wie vorhin: B in B und B' splitten. Im Vaterblock der Vaterblocks müssen dann ein Schlüssel und ein Pointer eingetragen werden. ° Das Splitten kann sich bis zur Wurzel (rekursiv) fortsetzen (siehe Beispiel). © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-7 Löschen: Im Prinzip invers zur Einfügeoperation. ° Wenn ein Block B im Hauptfile nach dem Löschen e − 1 Records hat, dann einen Geschwisterblock B' aufsuchen und die Sätze in einem Block zusammenlegen (falls möglich), andernfalls gleichmässig auf B und B' aufteilen. Zeitaufwand beim Einfügen und Löschen: Da sich die Struktur des Baumes längs eines Pfades vom Blatt zur n Wurzel ändern kann, ist der Zeitaufwand proportional zur Weglänge, also zu 1 + logd(2e). Beispiel: Einfügen eines Schlüssels 32 in obigen B+-Baum, und anschliessendes Löschen von Satz mit Schlüssel 64. − Einfügen von Schlüssel 32: 64 25 144 36 9 1 9 4 25 16 32 36 100 49 64 196 81 100 144 121 169 196 225 256 − Löschen von Schlüssel 64: 25 9 1 4 9 81 36 16 25 32 36 144 49 81 100 121 144 196 169 196 225 256 Eigenschaften (seien d und e natürliche Zahlen, d ≥ 2, und e ≥ 1) - innere Knoten speichern nur Schlüssel und Pointer zu Nachfolgeknoten 2d − 2 Schlüssel, 2d − 1 Pointer - in jedem inneren Knoten sind mindestens d pointer eingetragen (außer im Wurzelknoten) - Blattknoten enthalten die vollständigen Sätze - in jedem Blattknoten sind mindestens e Sätze eingetragen - Höhe des Baumes kann sich beim Einfügen/Löschen verändern - die Knotengröße (bestimmt durch e bzw. d) sollte so gewählt sein, dass bei jedem Plattenzugriff ein kompletter Knoten in den Speicher geholt wird © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-8 Löschen und Einfügen im Detail: neuen Schlüssel 32 einfügen B1 B2 25 144 64 100 B3 9 B4 196 36 1 16 9 4 B5 25 B6 36 49 64 B7 Knoten splitten 25 81 100 B8 32 36 121 144 B9 196 169 B10 225 256 225 256 B11 49 B1 25 144 64 B2 Knoten splitten 1 36 64 B6 81 100 B8 25 32 36 196 100 16 9 4 B5 B4 100 64 9 121 144 B9 196 169 B10 B11 49 64 Knoten splitten neuen Wurzelknoten einführen B2 1 9 144 25 144 36 100 16 9 4 B5 25 64 B6 81 100 B8 25 32 36 B4 121 196 144 B9 169 B10 196 225 256 B11 49 64 resultierender Baum: B2 1 4 B5 9 9 144 36 100 16 64 B6 B8 25 © 2002 K. Ecker 25 32 36 81 B4 100 B9 121 196 144 169 B10 196 225 256 B11 49 Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-9 64 Schlüssel 64 löschen B2 1 9 144 36 100 16 9 4 B5 25 64 B6 B4 81 100 B8 25 32 36 196 121 144 B9 196 169 B10 225 256 225 256 B11 49 64 B2 1 9 144 36 100 16 9 4 B5 25 64 100 81 B6 81 25 32 36 B4 100 49 196 121 144 196 169 B10 121 B11 Knoten kominieren 64 25 B2 144 B4 100 36 9 196 144 1 16 9 4 B5 25 32 36 49 81 100 Knoten kominieren 196 144 121 B6 169 B10 196 225 256 225 256 B11 64 resultierender Baum 25 144 25 B2 1 4 B5 9 144 36 9 B6 Knoten kominieren 81 16 25 32 36 49 81 100 121 196 144 169 B10 196 B11 2.4 Objekte Ziele der Objektorientierung: Objekte haben Eigenschaften (beschrieben durch ein oder mehrere Attribute), haben Struktur (innerer Aufbau), und haben Methoden zu deren Manipulation. Attribute können selbst wieder Objekte sein. Datenstrukturen lassen sich mit Hilfe sogenannter Typenkonstruktoren aufbauen. Die Methoden sind Programme, die auf deren Daten operieren. © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-10 Typenkonstruktoren: erlauben den Aufbau komplexer Datenstrukturen. . • elementare (unstrukturerte) Typen ("Atome"): Integer, Boolean, Character; elementare Aufzählungstypen z.B. wie in Pascal, die keine für die konkrete Anwendung relevante "innere" Struktur besitzen (z.B. Farben rot, grün, etc.). • Standardtypen wie Real, String, ..., record und array-Typen, wenn sie semantisch als Einheit angesehen werden sollen. • Aggregation (Tupelkonstruktor): Aus gegebenen Typen T1 , ..., Tk wird ein neuer Typ T erzeugt, der T1, ..., Tk als Komponententypen enthält. • Gruppierung (Mengenkonstruktor): Aus einem gegebenen Typ wir ein neuer Typ erzeugt, dessen Instanzen Mengen von Instanzen des Ausgangstyps sind. • Listenkonstruktor: Aus einem gegebenen Typ wird ein neuer Typ erzeugt, dessen Instanzen Listen (Folgen) von Instanzen des Ausgangstyps sind. Einkapselung: legt fest, welche Daten und Methoden für den Benutzer überhaupt sichtbar sind. Die Sichtbarkeitsdefinition erfolgt über eine Schnittstelle. Objekte: werden durch Datenstrukturen mit Attributen und Methoden charakterisiert. Die Attributwerte eines konkreten Objekts stellen seinen "Zustand" dar. Klassen: Menge von Objekten mit gleichen Attributen und Methoden. Operationen (Methoden) auf Objekten: • Erzeugen eines Objekts; nach dem Erzeugen existiert eine Referenz (Zeiger) auf den vom Objekt belegten Speicherplatz. Die Attributwerte sind noch nicht initialisiert. • Löschen eines Objekts • Zuweisung, Kopie, Identität, Gleichheit, usw. Beispiel Bücher: Bücher str str ISBN Titel Autoren Versionen Stichworte Version Stichwort str str Autor Aggregation Verlag int int Auflage Jahr Gruppierung str Standard Datentyp STRING ---- Ende Kapitel 2 ---- © 2002 K. Ecker Angewandte und praktische Informatik, Kap. 2: "Höhere" Datenstrukturen 2-11