Was bisher geschah ADT Menge mit Operationen: I Suche nach einem Element I Einfügen eines Elementes I Löschen eines Elementes Realisierung durch verschiedene Datenstrukturen: I lineare Datenstrukturen: Array, Liste, Hashtabelle I hierarchische Datenstrukturen: Suchbäume, balancierte Suchbäume Datenstrukturen mit Zugriff nur auf ein spezielles Element: Stack, Queue mit 1. Ansehen (top, first) 2. Entfernen (pop, dequeue) 3. Einfügen einen Elementes (push, enqueue) 182 Priority-Queues (Vorrang-Warteschlange) Idee: I Modellierung von Prioritäten, z.B. Dringlichkeit I Datenstruktur zur Verwaltung von Paaren (Schlüssel, Element) I Schlüssel repräsentieren Priorität I oft Rangfolge: kleiner Schlüssel = hohe Priorität I Zugriff nur auf ein Element mit minimalem Schlüssel (maximale Priorität) 183 Beispiele I Notfallaufnahme Prioritäten: lebensbedrohliche (1), schwere (2), leichte (3) Verletzungen I Verkauf von EM-Karten Prioritäten: Sportler (1-1000), Sponsoren (1000-2000), Sportlerfamilien (2000-3000), Prominenz (3000-4000), . . . I Betriebssysteme: Scheduling von Prozessen I kürzeste Wege in Graphen (Dijkstra) I Expansionsreihenfolge der Knoten in Spielbäumen 184 ADT Priority-Queue Menge von Schlüsseln (Prioritäten) mit einer totalen Ordnung Sorten: Bool, Element, PQ (kurz für PQhElementi) Signatur: isEmpty : PQ → emptyPQ : add : PQ × Element → getmin : PQ → deletemin : PQ → Bool PQ PQ Element PQ Axiome (Ausschnitt): (überall ∀p, q ∈ PQ ∀e, f ∈ Element :) isEmpty(emptyPQ) = t, isEmpty(add(p, e)) = f, (isEmpty(p) ∨ e ≤ getmin(p)) ↔ getmin(add(p, e)) = e, (isEmpty(p) ∨ e ≤ deletemin(p)) ↔ getmin(add(p, e)) = p, Φ= e > getmin(p) → getmin(add(p, e)) = getmin(p), e > getmin(p) → deletemin(add(p, e)) = add(detetemin(p), e) (+ Axiome der Booleschen Algebra) nur für Spezifikationen: K (p) = Menge aller Schlüssel in p 185 Wiederholung: Stack, Queue Datenstruktur (DS) mit get / remove nur für spezielles Element: Sorten: Bool, Element, DS (kurz für DShElementi) Signatur: isEmpty : DS → emptyDS : add : DS × Element → get0 : DS → DS → remove0 : Bool DS DS Element DS Spezialfälle: Stack mit anderen Namen für Operationen: add 7→ push, remove’ 7→ pop, get’ 7→ top spezielles = „neuestes“ enthaltenes Element Queue mit anderen Namen für Operationen: add 7→ enqueue, remove’ 7→ dequeue, get’ 7→ front spezielles = „ältestes“ enthaltenes Element Priority-Queue mit anderen Namen für Operationen: add 7→ add, remove’ 7→ deletemin, get’ 7→ getmin spezielles = minimales enthaltenes Element 186 Stack und Queue als Priority-Queues Queue Priorität jedes neu in die PQ eingefügten Elementes kleiner (Schlüssel größer) als Maximum aller schon in der PQ enthaltenen Elemente (Einfügen aufsteigend geordneter Schlüsselfolge) Stack Priorität jedes neu in die PQ eingefügten Elementes größer (Schlüssel kleiner) als Maximum aller schon in der PQ enthaltenen Elemente (Einfügen absteigend geordneter Schlüsselfolge) 187 Sortieren mit Priority-Queue Wiederholung: Spezifikation des Sortier-Problemes V: Eingabe (x1 , x2 , . . . , xn ) mit ∀i ∈ {1, . . . , n} : xi ∈ N N: Ausgabe (y1 , y2 , . . . , yn ) ist 1. y1 < y2 ≤ · · · < yn+1 (aufsteigend geordnet) und 2. eine Permutation (Umordnung) der Eingabe (x1 , x2 , . . . , xn ) Algorithmus : PQ-Sort Eingabe : x = (x1 , . . . , xn ) Ausgabe : y = (y1 , . . . , yn ) p ← emptyPQ für jedes i ← i, . . . , n : p ← add(p, xi ) für jedes i ← i, . . . , n : yi ← getmin(p) p ← deletemin(p) Laufzeit: n-mal add + n-mal (getmin + deletemin) abhängig von Realisierung der PQ 188 PQ – Realisierung durch Folgen unsortierte Folge: I add: (als erstes / letztes Element) O(1) bis O(n) (Liste, Array) I getmin, deletemin: (Minimum-Suche) O(n) I PQ-Sortierverfahren: O(n2 ) sortierte Folge: I add: (aufsteigend) sortiertes Einfügen in O(n) I getmin: (erstes / letztes Element) O(1) I deletemin: (erstes / letztes Element) O(1) bis O(n) (Liste, Array) I PQ-Sortierverfahren: O(n2 ) 189 PQ – Realisierung durch Bäume Binäre Suchbäume: Laufzeiten: Tiefe des Baumes I add: sortiertes Einfügen in O(log(n)) bis O(n) I getmin, deletemin: (linker äußerer Knoten) O(log(n)) bis O(n) I PQ-Sortierverfahren: O(n log(n)) bis O(n2 ) Suchbäume mit geeigneter Balance-Eigenschaft (z.B. AVL-Bäume, B-Bäume, Rot-Schwarz-Bäume): Laufzeiten: Tiefe des Baumes I add: sortiertes Einfügen in O(log n) I getmin, deletemin: O(log n) I PQ-Sortierverfahren: O(n log(n)) 190 Binäre Heaps Ein Binärbaum t erfüllt die Heap-Eigenschaft gdw. t erfüllt beide folgende Eigenschaften Struktureigenschaft (Balance): t ist ein balancierter Binärbaum in spezieller Heap-Form: Blatt-Schicht „von links vollständig“ belegt Ordnungseigenschaft: t ist Heap-geordnet, d.h. in jedem Knoten Branch(x, Branch(y , l, r ), Branch(z, s, t)) in t gilt: x ≤ y und x ≤ z Beispiele (Tafel) Auf jedem Pfad eines Heap-geordneten Baumes sind die Schlüssel sortiert (aufsteigend von Wurzel zu Blatt) 191 Binärer Heap – add Spezifikation add V: Eingabe binärer Heap t, Schlüssel k N: Ausgabe binärer Heap s mit K (s) = K (t) ∪ {k } Idee zum Einfügen eines Schlüssels k in t: 1. Neuen Knoten mit Schlüssel k auf der ersten freien Position im Heap t (Blattebene) einfügen (ggf. neue Blattebene beginnen) Heap-Ordnung ist danach evtl. verletzt 2. rekursiv entlang des Pfades vom neu eingefügten Knotens zur Wurzel: solange Schlüssel des Kindes größer als Schlüssel des Vaters: Schlüssel austauschen (bubble) Warum erhält diese Operation die Heap-Eigenschaft? Laufzeit: O(log n) (Tiefe des Baumes) 192 Binärer Heap – getmin Spezifikation getmin V: Eingabe (nichtleerer) binären Heap t N: Ausgabe Schlüssel k = min(K (t)) (d.h. ∀k 0 ∈ K (t) : k < k 0 ) Idee: Rückgabe des Schlüssels in der Wurzel des Heap k ← getmin(Branch(k , l, r )) Laufzeit: O(1) 193 Binärer Heap – deletemin Spezifikation deletemin V: Eingabe (nichtleerer) Heap t N: Ausgabe binärer Heap s mit K (s) = K (t) \ {min(K (t))} Idee: Löschen des Wurzelknotens im Heap t in zwei Schritten 1. Kopie des Schlüssels des „letzen“ Blattes in die Wurzel, Löschen des letzten Blattes Heap-Eigenschaft ist danach evtl. verletzt 2. rekursiv beginnend in der Wurzel, solange Schlüssel des Vaters kleiner als Schlüssel eines Kindes: Austausch der Schlüssel von Vater und dem Kind mit dem kleinstem Schlüssel Warum erhält diese Operation die Heap-Eigenschaft? Laufzeit: O(log n) (Tiefe des Baumes) 194 Heapsort (PQ-Sort mit PQ-Realisierung als binärer Heap) Spezifikation Sortieren (aufsteigend): V: Eingabe (x1 , x2 , . . . , xn ) mit ∀i ∈ {1, . . . , n} : xi ∈ N N: Ausgabe (y1 , y2 , . . . , yn ) ist 1. y1 ≤ y2 ≤ · · · ≤ yn+1 (aufsteigend geordnet) und 2. eine Permutation (Umordnung) der Eingabe (x1 , x2 , . . . , xn ) Algorithmus : Heapsort Eingabe : x = (x1 , . . . , xn ) Ausgabe : y = (y1 , . . . , yn ) p ← ⊥ (leerer binärer Heap) für jedes i ← i, . . . , n : p ← add(p, xi ) für jedes i ← i, . . . , n : yi ← getmin(p) p ← deletemin(p) Gesamtlaufzeit: O(n log n) 195 Repräsetation binärer Heaps als Arrays Funktion t 7→ level-order(t) ist eine Bijektion zwischen binären Heaps und Arrays (Umkehrfunktion?) Repräsentation eines binären Heaps t als Array (x1 , . . . , xn ) = level-order(t) Nachbarn des Knotens xi in x I Kinder: x2i , x2i+1 (in O(1)) I Vater: xbi/2c (in O(1)) Heapsort-Implementierung häufig mit Array-Repräsentation (in-situ) 196 Durchquerung von Bäumen mit Priority-Queue Wiederholung: allgemeiner Durchquerungs-Algorithmus 1. La = {s} (Wurzel s des zu durchquerenden Baumes) 2. solange La 6= ∅ wiederhole: Auswahl und Entfernen eines Knotens u aus La Einfügen aller Kinder von u in La Verwaltung von La als Stack: Tiefensuche Verwaltung von La als Queue: Breitensuche Verwaltung von La als Priority-Queue: Auswahl des Knotens aus La : getmin (deletemin) Einfügen der Kinder in La : add (mit geeigneten Prioritäten) häufige Anwendung: Priorität = Bewertung der Knoten (Heuristik) heuristische Suchverfahren, z.B. in Spielbäumen, Labyrinth-Suche (mehr dazu in LV Wissensverarbeitung) 197