Algorithmen und Datenstrukturen SS09 Foliensatz 4 Michael Brinkmeier Technische Universität Ilmenau Institut für Theoretische Informatik Sommersemester 2009 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 1 / 50 Grundlegende Datentypen Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 2 / 50 Atomare Datentypen Die meisten höheren Programmiersprachen, kennen verschiedene atomare1 Datentypen. Für uns sind das im Wesentlichen die folgenden: boolean – Wahrheitswert, zwei mögliche Werte: true/false, 0/1 integer – ganze Zahlen, Z real – Reelle Zahlen, R char – ASCII oder Unicode Zeichen pointer – Zeiger auf Speicherbereiche Auf realen Rechnern sind die Typen in der Regel mit Größen- oder Genauigkeitsbesschränkungen versehen ( z.B. short/int/long, float/double ). 1 griech.: atomos – unteilbar Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 3 / 50 Zusammengesetzte Datentypen Mittels verschiedener Operationen können weitere Datentypen konstruiert werden. Zu ihnen gehören: Verbünde – struct; Kombinationen von mehreren Daten Felder oder Arrays – int[5]; Listen fester Länge, in denen jeder Eintrag denselben Typ hat Diese Arten von Datentypen und der Umgang mit ihnen wird als bekannt vorausgesetzt. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 4 / 50 Grundlegende Datenstrukturen Im Folgenden wollen wir uns mit einigen grundlegenden Datenstrukturen beschäftigen: Stacks Listen Queues Dynamische Mengen Bäumen Hashtabellen Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 5 / 50 Die informale Beschreibung Die natürliche Sprache ist mit Sicherheit die einfachste Methode einen Datentyp zu beschreiben. Beispiel (Stack in natürlicher Sprache) Ein Stack (oder Stapel, Keller, LIFO – last-in-first-out) ist wie ein Stapel von Gegenständen organisiert. Man kann immer nur einen neuen Gegenstand auf den Stapel drauf legen (push), den obersten Gegenstand vom Stapel betrachten (top) oder den obersten Gegenstand entfernen (pop). Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 6 / 50 Die informale Beschreibung Alternativ kann man versuchen die Datenstruktur grafisch oder mit Hilfe von Beispielen zu beschreiben. Beispiel (Beschreibung eines Stacks mittels einer Graphik) Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 7 / 50 Die informelle Beschreibung von Datentypen Die informale Beschreibung erweist sich im Allgemeinen als zu unpräzise. Eine Verbesserung stellt die informelle Beschreibung dar, bei der der Datentyp mittels einer konkreten Implementation in einer beliebigen Programmiersprache beschrieben wird. Vorteile: einfach und direkt Nachteile: verschleiert die wesentliche Funktionalität (eine Operation wird über den auszuführenden Quelltext definiert) erschwert Korrektheitsbeweise die gegebene Implementierung ist nicht universell nutzbar (Programmiersprache ist fixiert) fixiert bestimmte technische Entscheidungen (Eine andere Implementation könnte für die gegebene Aufgabe deutlich geeigneter sein) Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 8 / 50 Abstrakte Datentypen Die Verwendung von Abstrakten Datentypen (ADT) erlaubt die Definition des Datentyps von seiner Implementierung zu trennen. Ein Abstrakter Datentyp besteht im Wesentlichen aus zwei Komponenten: einer Signatur und einer Semantik. Für beide Teile spielt die Implementierung keine Rolle. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 9 / 50 Signaturen Die Signatur eines ADT entspricht im Wesentlichen dem klassischen C-Header, bzw. den Deklaratoren. Sie beschreibt, welche Operationen im Zusammenhang mit dem ADT ausgeführt werden können. Definition (Signatur) Eine Signatur Σ = (S, O, domain, codomain) besteht aus einer Menge S von Sorten, die im Zusammenhang mit der ADT benötigt werden, einer Menge O von Operationen, zwei Abbildungen domain: O → Seq(S) und codomain: O → S wobei Seq(S) = {(S1 , . . . , Sn ) | n ∈ N, S1 , . . . , Sn ∈ D} die Menge aller endlichen Tupel über S ist. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 10 / 50 Signaturen Ist Σ = (S, O, domain, codomain) eine Signatur, dann kann man eine Operation op ∈ O als Abbildung von ihrem Definitionsbereich (Domain, Domäne) domain(op) in ihren Wertebereich (Codomain) codomain(op) interpretieren. Notation Operationen beschreibt man häufig in der Form op : domain(op) → codomain(op) Wenn domain(op) = (I1 , . . . , In ) und codomain(op) = O schreibt man auch op : I1 × · · · × In → O. Die Forderung, dass jede Operation genau einen Ausgabetyp hat, ist auf technische Gründe zurück zu führen. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 11 / 50 Die Signatur eines Stacks Beispiel (Die Signatur eines Stacks) Sorten: Elements Stacks Boolean Operationen: empty : () → Stacks push : Stacks × Elements → Stacks pop : Stacks → Stacks top : Stacks → Elements isempty : Stacks → Boolean Bemerkung Der Definitionsbereich () von ist explizit erlaubt. Man spricht dann von Konstanten oder auch von Konstruktoren. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 12 / 50 Signaturen Bemerkung Wenn eine Operation den Inhalt eines Objektes verändert, muss das Objekt das Ergebnis der Operation sein! Bei den Signaturen handelt es sich um eine funktionale (applikative) Herangehesweise. Beispiel push: Stacks × Elements → Stacks pop : Stacks → Stacks verändern beide den Stack. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 13 / 50 Algebren Eine Signatur Σ = (S, O, domain, codomain) ist lediglich eine Hülle, die die Syntax der Datenstruktur beschreibt. Es fehlt noch der Inhalt. Definition (Algebra) Sei Σ = (S, O, domain, codomain) eine Signatur. Eine Σ-Algebra A = (T , F ) besteht aus einer Familie T = {TS | S ∈ S} von Mengen und einer Familie F = fop | op ∈ O und f : Tdomain(op) → Tcodomain(op) von Abbildungen. Dabei ist T(S1 ,...,Sn ) = TS1 × · · · × TSn . Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 14 / 50 Algebren Anders gesagt: Eine Σ-Algebra A = (T , F ) mit Σ = (S, T , domain, codomain) weist jeder Sorte S ∈ S eine Menge TS von Objekten dieser Sorte zu und jeder Operation op : I1 × · · · × In → O eine Abbildung fop : TI1 × · · · × TIn → TO . Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 15 / 50 Eine Algebra für Stacks Beispiel (Eine Algebra für Stacks) Elements = (nicht-leere) Menge D Boolean = {true, false} Stacks = Seq(D) Das leere Tupel () stellt den leeren Stack dar. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 16 / 50 Eine Algebra für Stacks Beispiel (Eine Algebra für Stacks – Fortsetzung) empty() := () push((a1 , . . . , an ), x) := (a1 , . . . , an , x) ( ⊥ steht für einen Feh((a1 , . . . , an−1 ), an ) falls n ≥ 1 ler oder einen undefipop((a1 , . . . , an )) := ⊥ fallsWert. n=0 nierten ( (a1 , . . . , an−1 ) falls n ≥ 1 pop((a1 , . . . , an )) := ⊥ falls n = 0 ( an falls n ≥ 1 top((a1 , . . . , an )) := ⊥ falls n = 0 ( false falls n ≥ 1 isempty((a1 , . . . , an )) := true falls n = 0 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 17 / 50 Axiome Um die Semantik einer Signatur festzulegen genügt es in der Regel nicht eine Algebra anzugeben. Häufig müssen zusätzlich bestimmte Gleichungen oder Axiome erfüllt werden. Beispiel (Die Axiomatik eines Stacks) ∀s ∈ Stacks, ∀x ∈ Elements: top(push(s, x)) = pop(push(s, x)) = x s isempty(empty()) = true isempty(push(s, x)) =false top(empty()) = ⊥ pop(empty()) = ⊥ Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 18 / 50 Die Algebra und die Axiome Beispiel (Die Axiome und unsere Stack-Algebra) top (push((a1 , . . . , an ), x)) = top ((a1 , . . . , an , x)) = x pop (push((a1 , . . . , an ), x)) = pop ((a1 , . . . , an , x)) = (a1 , . . . , an ) isempty(empty()) = isempty(()) = true isempty(push((a1 , . . . , an ), x)) = isempty((a1 , . . . , an , x)) = false Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 19 / 50 Die Modellalgebra Hat man eine Signatur Σ und dazugehörige Axiome gegeben, so spricht man von einer algebraischen Spezifikation. Aus einer solchen algebraischen Spezifikation läßt sich eine Modellalgebra (oder auch mathematisches Modell) konstruieren (Stichwort: Quotiententermalgebra, AuP). Diese Modellalgebra hat einige sehr wichtige Eigenschaften, die bei dem Beweis der Korrekheit einer Implementierung helfen können. Unter Umständen lässt sich diese Modellalgebra auf elegante und einfach Weise beschreiben. Beispiel Die beschriebene Algebra für Stacks ist eine Modellalgebra für die gegebene algebraische Spezifikation. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 20 / 50 Algebraische Spezifikation eines ADT Vorteile: Universelle und vollständige Beschreibung Die axiomatische Darstellung ermöglicht automatisiere Beweise von Eigenschaften der Datenstruktur Nachteile: Das Finden eines geeigneten und vollständigen Axiomensystems ist in der Regel nicht einfach. Umsetzung in eine Implementation erfordert in der Regel einen weiteren Zwischenschritt über die Modellalgebra. Diese explizit zu beschreiben ist in der Regel ebenfalls sehr schwer. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 21 / 50 Implementierungen von Abstrakten Datentypen Das Konzept von Abstrakten Datentypen lässt sich in gewisser Weise in der Objektorientierung wiederfinden. Im Wesentlichen entspricht die Signatur der Definition der Klassen (z.B. in C++ und Java) und Interfaces. Abstrakte Klassen entsprechen direkt einer Signatur. Eine Implementierung entspricht einer Algebra. Sogar die abstrakte Definition von Signaturen mit nicht näher spezifizierten Sorten – wie z.B. die Sorte Elements eines Stacks – findet sich in modernen objektorientierten Sprachen in Templates wieder. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 22 / 50 Implementierungen eines Stacks Zur Realisierung eines Stacks über eine Menge D von Elementen, ergeben sich im wesentlichen zwei mögliche Implementierungen: Die Listenimplementierung Die Arrayimplementierung Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 23 / 50 Die Listenimplementierung von Stacks Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 24 / 50 Die Listenimplementierung eines Stacks Nullzeiger Listen haben Sie schon in AuP kennengelernt. S: a1 a2 an ··· S: emptyLI : Erzeuge eine leere Liste. pushLI (s, x): Füge am Anfang der Liste einen neues Element ein. S: x a1 a2 an ··· Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 25 / 50 Die Listenimplementierung eines Stacks topLI (s): Gebe den Inhalt des ersten Listenknotens zurück, falls vorhanden a1 S: a2 an ··· popLI (s): Entferne das erste Element der Liste. S: a1 a2 a3 ··· an isemptyLI (s): Gibt true zurück, falls die Liste leer ist, sonst false. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 26 / 50 Korrektheit der Listenimplementierung Da eine Modellalgebra für Stacks bekannt ist, kann die Korrektheit einer Implementierung auf einfache Art bewiesen werden. Dabei nutzt man das folgende Resultat: Sei A = (T , F ) eine Modellalgebra von Σ = (S, O, domain, codomain) und I = (T I , F I ) eine Implementierung (interpretiert als Algebra). I die in I. TS und fop seien die Mengen und Abbildungen in A und TSI und fop I ist eine korrekte Implementierung von Σ oder A, wenn für jede Sorte S ∈ S eine Bijektion ϕS : TS → TSI existiert und für jede Operation op : I1 × · · · × In → O Folgendes gilt: I (ϕI1 (x1 ), . . . , ϕIn (xn )) . ϕO fop (x1 , . . . , xn ) = fop Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 27 / 50 Korrektheit der Listenimplementierung Anders formuliert: Wir benötigen für jede Sorte S eine Bijektion ϕS : TS → TSI zwischen den Elementen der Sorte in der Modellalgebra und den Objekten der Sorte in der Implementation. Weiter müssen diese Bijektionen mit den Operationen kompatibel sein, d.h. im folgenden Diagramm ist es egal welchen der beiden Wege wir von der linken oberen Ecke zur rechten unteren Ecke nehmen. Beide führen zum selben Ergebnis. fop I1 × · · · × In O ϕO ϕI1 × · · · × ϕIn I1I × · · · × InI I fop Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 28 / 50 OI Korrektheit der Listenimplementierung Wie sieht nun die Bijektion zwischen Seq(D) und den Listen aus? Notation a1 S: a2 ··· an Das oben dargestellte Array notieren wir zur Abkürzung in der Form [a1 → a2 → · · · → an ] Damit definieren wir ϕ((a1 , . . . , an )) := [an → an−1 → · · · → a2 → a1 ] . (Beachten Sie, dass die Reihenfolge umgekehrt wurde). Für Elements und Boolean gehen wir einfach von den Identitäten aus. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 29 / 50 Korrektheit der Listenimplementierung ((a1 , . . . , an ), x) (a1 , . . . , an , x) push Seq(D) × D Seq(D) ϕ List < D > × D ϕ pushLI ([an → · · · → a1 ], x) [x → an → · · · → a1 ] Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau List < D > Seite 30 / 50 Korrektheit der Listenimplementierung (a1 , . . . , an ) (a1 , . . . , an−1 ) pop Seq(D) Seq(D) ϕ ϕ List < D > popLI [an → · · · → a1 ] List < D > [an−1 → · · · → a1 ] Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 31 / 50 Korrektheit der Listenimplementierung Auf ähnliche Weise kann man die übrigen Operationen auf Kompatibilität mit ϕ überprüfen. Satz Die beschriebene Listenimplementierung ist eine korrekte Implementierung des abstrakten Datentyps Stack. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 32 / 50 Zeitaufwand der Listenimplementierung Behauptung Jede Operation der Listenimplementierung hat Kosten O(1). Die Behauptung ist offensichlich korrekt. Allerdings erfordert push das Anlegen eines neuen Listeneintrages. Diese Operation (malloc in C, new in C++ und Java) ist auf realen Maschinen in der Regel deutlich teurer als der Zugriff auf einen Listeneintrag. Noch ein Problem Auf realen Maschinen kann push zu einem Speicherüberlauf führen. Im Beweis sind wir stillschweigend von einem unendlich großem Speicher ausgegangen. Die Korrektheit gilt also nur bis auf Speicherüberläufe. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 33 / 50 Die Arrayimplementierung von Stacks Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 34 / 50 Die Arrayimplementierung von Stacks In der Arrayimplementierung besteht ein Stack s aus zwei Komponenten: einem Array A[1 . . . m] der festen Größe m und einer Integervariablen p, genannt Pegel. p: n s: 1 A : a1 a2 ··· n an ∗ ··· m ∗ ∗ Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 35 / 50 Die Arrayimplementierung von Stacks empty(): A = new Elements[1 . . . m] p = 0. push(s, x): if p == m then return ⊥ else p = p + 1 ; A[p] = x pop(s): if p == 0 then return ⊥ else p = p − 1 ; return A[p + 1] top(s): if p == 0 then return ⊥ else return A[p] isempty(s): if p == 0 then return true else return false Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 36 / 50 Korrektheit der Arrayimplementierung Diesmal gehen wir einen anderen Weg für den Korrektheitsbeweis. Wir betrachten eine Folge (op0 = empty, op1 , . . . , opN ) von Operationen auf demselben Stack mit op1 , . . . , opN 6= empty. Im mathematischen Modell erzeugt diese Operationsfolge eine Folge (s0 , s1 , . . . , sN ) von Tupeln mit s0 = () und eine Folge (z0 , z1 , . . . , zN ) von Ausgaben aus Elements ∪ {true, false} ∪ {−, ⊥}, wobei − die Ausgabe von empty und push ist und ⊥, falls ein Fehler auftritt (pop oder top auf leeren Stack). pop und top liefern das entsprechende Element als Ausgabe. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 37 / 50 Korrektheit der Arrayimplementierung Behauptung Die Arrayimplementierung erzeugt genau dieselbe Ausgabefolge wie das mathematische Modell, solange weder im mathematischen Modell ein Fehler auftritt (top ode pop auf leeren Stack), noch die Höhe des Stacks si die Arraygröße m überschreitet. Beweis Man beweist per Induktion über i = 0, 1, . . . , N, dass nach Ausführen von opi die folgende Invariante gilt, sofern kein Fehler aufgetreten ist: p enthält die Länge ni von si und si = (A[1], . . . , A[ni ]). D.h. das Array A[1 . . . ni ] stellt genau si dar. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 38 / 50 ... Korrektheit der Arrayimplementierung Beweis (Fortsetzung) Induktionsanfang (i = 0) : Nach op0 = empty() hat p den Inhalt 0 und somit ist das Array A[1 . . . p] leer, also gleich s0 = (). Induktionsvoraussetzung: Die Behauptung gilt für i − 1. Induktionsschritt: Wir müssen verschiedene Fälle unterscheiden: Falls si−1 =⊥ – d.h. im mathematischen Modell ist vorher ein Fehler aufgetreten – ist nichts zu zeigen, da dieser Fall ausgeschlossen wurde. Falls in den Schritten 1, . . . , i − 1 in der Implementierung ein Fehler aufgetreten ist, ist ebenfalls nichts zu zeigen. Wir können also si −1 = (a1 , . . . , ani −1 ) annehmen und p hat den Inhalt ni −1 und aj = A[j] für 1 ≤ j ≤ ni −1 . ... Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 39 / 50 Korrektheit der Arrayimplementierung Beweis (Fortsetzung) Falls opi = push(s, x), ist si = (a1 . . . , ani −1 , x). Wenn ni −1 < m, wird von der Prozedur push(s, x) das Objekt x an die Stelle A[ni −1 + 1] gesetzt und p auf den Wert ni −1 + 1 erhöht. Das impliziert die Induktionsbehauptung in diesem Fall. Wenn ni −1 = m ist, tritt in der Implementierung ein Fehler auf und die Behauptung ist trivialerweise erfüllt. Die anderen Operationen können analog behandelt werden (Übungsaufgabe). Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 40 / 50 Die Laufzeit der Arrayimplementierung Behauptung Bei der Arrayimplementierung hat jede Operation die Kosten O(1). Die Behauptung ist wahr, falls bei empty() das Array lediglich angelegt, nicht aber initialisiert wird. Im letzten Fall, wäre die Laufzeit für empty gerade O(m). Da der Inhalt in A[p + 1 . . . m] aber unerheblich ist, beeinflusst diese Einschränkung die Korrektheit jedoch nicht. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 41 / 50 Arrayimplementierung ohne Platzprobleme Frage Ist es möglich, die Begrenzung auf ein Array fester Länge zu beheben? Antwort Ja! Mittels der Verdoppelungsstrategie! Sobald während push(s, x) ein Überlauf eintritt – d.h. das m + 1-te Element würde hinzugefügt werden – verdoppeln wir die Größe des Arrays. Genauer: Legen ein neues Array B der Größe 2m an. Kopieren das alte Array A in die ersten m Felder des neuen Arrays B. Gebe A frei. Setze A = B, d.h. setze die Referenz um. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 42 / 50 Die Verdoppelungsstrategie p: m s: 1 A: a1 a2 1 B : a1 a2 ··· m am ··· m am ∗ ··· 2m ∗ ∗ Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 43 / 50 Kostenanalyse der Verdoppelungsstrategie Frage Wieviele Verdoppelungen können auftreten? k sei die Anzahl der push-Opertationen in unserer Folge und j die Anzahl der Verdoppelungen. Damit enthält der Stack stets ≤ k Elemente. Also gilt vor der letzten Verdoppelung m0 · 2j−1 < k denn sonst, müsste nicht verdoppelt werden, und somit j − 1 < log k = log k − log m0 . m0 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 44 / 50 Kostenanalyse der Verdoppelungsstrategie Sei L = ⌈log k − log m0 ⌉. Dann lassen sich die Gesamtkosten für die Verdoppelungen nach oben abschätzen: ! L L−1 X X O m0 · 2i −1 = O m0 · 2i = O m0 · 2 L i =1 i =0 = O m0 · 2log k m0 = O(k) k = O m0 · m0 Gesamtkosten mit k push-Operationen N · O(1) + O(k) = O(N) Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 45 / 50 Kostenanalyse der Verdoppelungsstrategie Satz Wenn ein Stack mit Arrays und der Verdoppelungsstrategie implementiert wird, sind die Gesamtkosten für N Operationen in O(N). Der gesamte Platzbedarf ist O(k), wenn k die maximale je erreichte Stackgröße ist. Selbst wenn unbenutzte Arrays nie freigegeben werden, tritt ein Speicherüberlauf erst dann ein, wenn die Zahl der Stackeinträge zu einem bestimmten Zeitpunkt mehr als 14 des zur Verfügung stehenden Speichers beansprucht (Übungsaufgabe). Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 46 / 50 Amortisierte Analyse Eine Analyse der Kosten, wie wir sie gerade durchgeführt haben, nennt man Amortisierte Analyse. Idee hinter der amortisierten Analyse Eine Datenstruktur wird häufig nicht für eine Operation, sondern für eine Reihe von insgesamt N Operationen genutzt. Betrachtet man die Kosten der einzelnen Operationen, so ergeben sich unter Umständen sehr hohe Worst-Case Kosten, wie z.B. die Kosten von push bei einem Stack mit Verdoppelungsstrategie. Betrachtet man jedoch die gesamte Folge, so könnte es sein, das diese relativ hohen Kosten nur in bestimmten Situationen eintreten (z.B. wenn besonders viele billige push Operationen seit der letzten teuren durchgeführt wurden). Dadurch ergeben sich insgesamt niedrigere Kosten, als bei der Verwendung der Worst-Case Laufzeit. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 47 / 50 Amortisierte Analyse Amortisierte Kosten Benötigt man im schlechtesten Fall T (N) Zeit für N Operationen auf einer (N) . Datenstruktur, so benötigt jede Operation die amortisierten Kosten T N Wichtig! Amortisierte Kosten müssen klar von Average-Case Kosten unterschieden werden! Bei letzteren geht eine Wahrscheinlichkeitsverteilung ein, und man betrachtet die erwartete Laufzeit. Bei den amortisiertern Kosten handelt es sich um eine tatsächliche obere Schranke, d.h. N beliebige Operationen auf der Datenstruktur benötigen höchstens T (N) Zeit. Die amortisierten Kosten für jede einzelne Operation sind dann die entstandenen durchschnittlichen Kosten für jede Operation. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 48 / 50 Vergleich von Listen- und Arrayimplementierung Arrayimplementierung: O(1) Kosten pro Operation, amortisiert – d.h. im Mittel über alle Operationen. Sequentieller, indexbasierter Zugriff auf den Speicher (cachefreundlich) Höchstens die Hälfte des allozierten Speichers ist ungenutzt Listenimplementierung: O(1) Kosten pro Operation im schlechtesten Fall Hohe Allokationskosten, da jedes Listenelement einzeln angelegt wird Zusätzlicher Platz für Zeiger benötigt Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 49 / 50