Grundlagen der Algorithmen und Datenstrukturen Kapitel 3.3-3.5 Prof. Dr. Christian Scheideler Sequenzen Sequenz: s = <e0,…,en-1> Arten, auf Element zuzugreifen: • Feldrepräsentation: absoluter Zugriff über s[i] s[0]: e0 s[1] s[2] s[3] s[4] s[5] …. • Listenrepräsentation: relativer Zugriff über Nachfolger und/oder Vorgänger Nachf. Nachf. s e0 e2 e1 Vorg. Nachf. Vorg. Nachf. e3 Vorg. Vorg. …. Doppelt verkettete Liste Type Handle=Pointer to Item Class Item of Element e: Element next: Handle prev: Handle … e e Class List of Item h: Handle …weitere Variablen und Operationen… Invariante: (this: Zeiger auf aktuelles Element) next! prev = prev! next = this e … Doppelt verkettete Liste Einfache Verwaltung: durch „Dummy“-Element mit Inhalt ?: ? e1 e2 en … Anfangs: ? Doppelt verkettete Liste Zentrale Operation: splice • splice entfernt <a,…,b> aus Sequenz und fügt sie hinter einem t an. • Bedingung: <a,…,b> muss eine Teilsequenz sein, b ist nicht vor a, und t darf nicht in <a,…,b> sein. Splice (<e1,…,a´,a,…,b,b´,…,t,t‘…,en>, a, b, t) = <e1,…,a´,b´,…,t,a,…,b,t´,…,en> Doppelt verkettete Liste Procedure splice(a,b,t: Handle) // schneide <a,…,b> heraus a´ := a!prev b´ := b!next a´!next := b´ b´!prev := a´ // füge <a,…,b> hinter t ein t´ := t!next b!next := t´ a!prev := t t!next := a t´!prev := b a´ a b b´ b t´ … … … … t a … … … … Doppelt verkettete Liste h: Handle auf ?-Item in List ? e1 … en Function head(): Handle return h ? Function isEmpty: {0,1} return h.next = h Function first(): Handle return h.next Kann evtl. auf ?-Element zeigen! Function last(): Handle return h.prev Kann evtl. auf ?-Element zeigen! Doppelt verkettete Liste h: Handle to ?-Item of List ? e1 … en Procedure moveAfter(b,a: Handle) splice(b,b,a) // schiebe b nach a Procedure moveToFront(b: Handle) moveAfter(b,head()) // schiebe b nach vorne Procudure moveToBack(b: Handle) moveAfter(b,last()) // schiebe b nach hinten Doppelt verkettete Liste Löschen und Einfügen von Elementen: mittels extra Liste freeList h b … … Procedure remove(b:Handle) moveAfter(b, freeList.head()) … freeList Procedure popFront() remove(first()) Procedure popBack() remove(last()) … … freeList: gut für Laufzeit, da Speicherallokation teuer Doppelt verkettete Liste Function insertAfter(x: Element, a: Handle): Handle checkFreeList a´ := freeList.first() h a moveAfter(a´, a) … a´!e := x … … return a´ a´ x Function insertBefore(x: Element, b: Handle): Handle return insertAfter(e, pred(b)) Procedure pushFront(x: Element) insertAfter(x, head()) Procedure pushBack(x: Element) insertAfter(x, last()) Doppelt verkettete Liste Manipulation ganzer Listen: last h Procedure concat(L: List) // L darf nicht leer sein! splice(L.first(), L.last(), last()) Procedure makeEmpty() freeList.concat(this) … … L … … Doppelt verkettete Liste Suche nach einem Element: Trick: verwende „Dummy“-Element x e1 … en Function findNext(x:Element, from: Handle): Handle h.e := x while from! e <> x do from := from!next return from Einfach verkettete Liste Type SHandle=Pointer to SItem Class SItem of Element e: Element … next: Handle e e Class SList of SItem h: SHandle …weitere Variablen und Operationen… e … Einfach verkettete Liste Procedure splice(a´, b, t: SHandle) a´!next := b!next t!next := a´!next b!next := t!next a´ t a … b b´ t´ Stacks und Queues Grundlegende Datenstrukturen für Sequenzen: • Stack • FIFO-Queue: Stack Operationen: • pushBack: <e0,…,en>.pushBack(e) = <e0,…,en,e> • popBack: <e0,…,en>.popBack = <e0,…,en-1> • last: last(<e0,…,en>) = en Implementierungen auf vorherigen Folien. FIFO-Queue Operationen: • pushBack: <e0,…,en>.pushBack(e) = <e0,…,en,e> • popFront: <e0,…,en>.popFront = <e1,…,en> • first: first(<e0,…,en>) = e0 Implementierungen auf vorherigen Folien Beschränkte FIFO-Queue Class BoundedFIFO(n: IN) of Element b: Array[0..n] of Element h=0: IN // Index des ersten Elements t=0: IN // Index des ersten freien Eintrags Function isEmpty(): {0,1} return h=t Function first(): Element return b[h] Function size(): IN return (t-h+n+1) mod (n+1) b n h pop 0 1 t 2 3 . push . . Beschränkte FIFO-Queue Class BoundedFIFO(n: IN) of Element b: Array[0..n] of Element h=0: IN // Index des ersten Elements t=0: IN // Index des ersten freien Eintrags Procedure pushBack(x: Element) assert size<n h b[t] := x t := (t+1) mod (n+1) pop Procedure popFront() assert (not isEmpty()) h := (h+1) mode (n+1) b n 0 1 t 2 3 . push . . Fazit • Listen sind sehr flexibel, wenn es darum geht, Elemente in der Mitte einzufügen • Felder können in konstanter Zeit auf jedes Element zugreifen • Listen haben kein Reallokationsproblem bei unbeschränkten Größen ! beide Datenstrukturen einfach, aber oft nicht wirklich zufriedenstellend Beispiel: Sortierte Sequenz Problem: bewahre nach jeder Einfügung und Löschung eine sortierte Sequenz 1 3 10 14 19 3 5 10 14 19 1 3 5 10 19 insert(5) 1 remove(14) Beispiel: Sortierte Sequenz S: sortierte Sequenz Jedes Element e identifiziert über Key(e). Operationen: • <e0,…,en>.insert(e) = <e0,…,ei,e,ei+1,…,en> für das i mit Key(ei) < Key(e) < Key(ei+1) • <e0,…,en>.remove(k) = <e0,…,ei-1,ei+1,…,en> für das i mit Key(ei)=k • <e0,…,en>.find(k) = ei für das i mit Key(ei)=k Beispiel: Sortierte Sequenz • Realisierung als Liste: – insert, remove und find auf Sequenz der Länge n kosten im worst case (n) Zeit • Realisierung als Feld: – insert und remove kosten im worst case (n) Zeit – find kann so realisiert werden, dass es im worst case nur O(log n) Zeit benötigt (! binäre Suche!) Beispiel: Sortierte Sequenz Kann man insert und remove besser mit einem Feld realisieren? • folge Beispiel der Bibliothek! • verwende Hashtabellen (Kapitel 4) Beispiel: Sortierte Sequenz Bibliotheksprinzip: lass Lücken! Angewandt auf sortiertes Feld: 1 3 10 1 3 5 10 1 3 5 10 14 19 14 19 insert(5) remove(14) 19 Beispiel: Sortierte Sequenz Durch geschickte Verteilung der Lücken: amortierte Kosten für insert und remove (log2 n) 1 3 10 1 3 5 10 1 3 5 10 14 19 14 19 insert(5) remove(14) 19 Nächste Woche • Weiter mit Kapitel 4: Hashtabellen • Midterm am 29. Mai! • Anmeldung über Grundstudiumstool