3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Heapsort 3.2 Algorithmen auf Listen und Bäumen Bemerkung: Heapsort verfeinert die Idee des Sortierens durch Auswahl: • Minimum bzw. Maximum wird nicht durch lineare Suche gefunden, • sondern mit logarithmischem Aufwand durch Verwendung einer besonderen Datenstruktur, dem sogenannten Heap. Algorithmische Idee: • Der Begriff „Heap“ist in der Informatik überladen. • 1. Schritt: Erstelle den Heap zur Eingabeliste. • 2. Schritt: I Entferne Maximumelement aus Heap (konstanter Aufwand) und hänge es an die Ausgabeliste. I Stelle Heap-Bedingung wieder her (logarithmischer Aufwand). I Fahre mit Schritt 2 fort bis der Heap leer. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren • Ziele des Vorgehens: I Beispiel für komplexe, abstrakte Datenstruktur I Zusammenhang der algorithmischen Idee und der Datenstruktur. Auch der Speicher für zur Laufzeit angelegte Variablen wird im Englischen „heap“genannt. 357 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Begriffsklärung: (zu Bäumen) 358 3.2 Algorithmen auf Listen und Bäumen Begriffsklärung: (zu Bäumen) (2) Ein Binärbaum der Höhe h heißt fast vollständig, wenn • jedes Blatt die Tiefe h − 1 oder h − 2 hat, Wir betrachten im Folgenden Binärbäume, die • jeder Knoten mit einer Tiefe kleiner h − 2 zwei nicht-leere • entweder leer sind oder Unterbäume hat, • für die Knoten K des Niveaus h − 2 gilt: • aus einem markierten Knoten mit zwei Unterbäumen bestehen. Ein Blatt ist dann ein Knoten mit zwei leeren Unterbäumen. 1. Hat K 2 nicht-leere Unterbäume, dann auch alle linken Nachbarn von K . 2. Ist K ein Blatt, dann sind auch alle rechten Nachbarn von K Blätter. 3. Es gibt maximal ein K mit genau einem nicht-leeren Unterbaum und der ist links. Ein Binärbaum heißt strikt, wenn jeder Knoten ein Blatt ist oder zwei nicht-leere Unterbäume besitzt. Ein Binärbaum der Höhe h heißt vollständig, wenn er strikt ist und alle Blätter die Tiefe h − 1 haben. ©Arnd Poetzsch-Heffter TU Kaiserslautern Ein Baum der Größe n heißt indiziert, wenn man seine Knoten mittels der Indizes 0, . . . , n − 1 ansprechen kann. 359 ©Arnd Poetzsch-Heffter TU Kaiserslautern 360 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Bemerkung: 3.2 Algorithmen auf Listen und Bäumen Modulschnittstelle für fast vollständige, markierte, indizierte Binärbäume: Im Folgenden gehen wir bei indizierten Bäumen immer davon aus, dass die Reihenfolge der Indices einem Breitendurchlauf folgt (siehe Beispiel). module FvBintree (FVBintree ,create ,size ,get ,swap , removeLast ,hasLeft ,hasRight ,left ,right ) where Beispiel: (Fast vollst., indizierter und markierter Binärbaum) create :: [ Dataset ] -> FVBintree -- Erzeugt fast vollstaendigen Binaerbaum , wobei die -- Schluessel der Listenelemente zu Markierungen werden size :: FVBintree -> Int -- Anzahl der Knoten des Baums get :: FVBintree -> Int -> Dataset -- Liefert Markierung am Knoten mit Index i, -- 0 <= i < size b ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 361 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Modulschnittstelle für fast vollständige, markierte, indizierte Binärbäume: (2) 362 3.2 Algorithmen auf Listen und Bäumen Modulschnittstelle für fast vollständige, markierte, indizierte Binärbäume: (3) swap :: FVBintree -> Int -> Int -> FVBintree -- Vertauscht Markierungen der Knoten mit Index i und j -- Modifiziert fbt; 0 <= i,j < size b removeLast :: FVBintree -> FVBintree -- Entfernt letzten Knoten left :: FVBintree -> Int -> Int -- Liefert Index des linken Kinds von Knoten mit Index i -- 0 <= i < size b && hasLeft i hasLeft :: FVBintree -> Int -> Bool -- Knoten mit Index i hat linkes Kind -- 0 <= i < size b right :: FVBintree -> Int -> Int -- Liefert Index des rechten Kinds von Knoten mit Index i -- 0 <= i < size b && hasRight i hasRight :: FVBintree -> Int -> Bool -- Knoten mit Index i hat rechtes Kind -- 0 <= i < size b ©Arnd Poetzsch-Heffter TU Kaiserslautern 363 ©Arnd Poetzsch-Heffter TU Kaiserslautern 364 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Vorgehen 3.2 Algorithmen auf Listen und Bäumen Bemerkung: • Wir benutzen die Datenstruktur ohne die Implementierung zu kennen; man sagt die Datenstruktur ist für den Nutzer abstrakt und spricht von abstrakter Datenstruktur. • Wir gehen im Folgenden davon aus, dass wir eine Implementierung von FvBintree haben (steht zum Testen bereit) und realisieren heapsort damit; d.h. ohne die • Die Signatur beschreibt die Schnittstelle der abstrakten Struktur/Implementierung zu kennen. Datenstruktur. Die Benutzung von Schnittstellen abstrakter Datenstrukturen ist ein zentraler Bestandteil der SW-Entwicklung. • Im Zusammenhang mit der objektorientierten Programmierung werden wir dann eine sehr effiziente Implementierung für fast vollständige Binärbäume kennen lernen. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren • Eine abstrakte Datenstruktur kann unterschiedliche Implementierungen haben. Implementierungen können ausgetauscht werden, ohne dass der Nutzer seine Programme ändern muss! 365 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Begriffsklärung: (Heap) 366 3.2 Algorithmen auf Listen und Bäumen Beispiel: (Heap) Heap der Größe 8: Ein markierter, fast vollständiger, indizierter Binärbaum mit n Knoten heißt ein Heap der Größe n, wenn die folgende Heap-Eigenschaft erfüllt ist: Ist M ein Knoten und N ein Kind von M mit Markierungen kM und kN , dann gilt: kM ≥ kN Bei einem Heap sind die Knoten entsprechend einem Breitendurchlauf indiziert (siehe Beispiel unten). ©Arnd Poetzsch-Heffter TU Kaiserslautern 367 ©Arnd Poetzsch-Heffter TU Kaiserslautern 368 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Bemerkung: 3.2 Algorithmen auf Listen und Bäumen Herstellen der Heap-Eigenschaft: Sei ein markierter, fast vollständiger Binärbaum gegeben, der die Heap-Eigenschaft nur an der Wurzel verletzt. Die Heap-Eigenschaft garantiert, dass der Schlüssel eines Knotens M größer gleich aller Schlüssel in den Unterbäumen von M ist. Die Heap-Eigenschaft kann hergestellt werden, indem man den Wurzelknoten M rekursiv in dem Unterbaum mit dem größeren Schlüssel versickern lässt: • Gibt es kein Kind, ist nichts zu tun. Insbesondere steht in der Wurzel ein Element mit einem maximalen Schlüssel. • Gibt es genau ein Kind N, dann ist dies links und kinderlos: Ist kM < kN , vertausche die Markierungen. • Gibt es zwei Kinder und ist N das Kind mit dem größeren Schlüssel: Ist kM < kN , vertausche die Markierungen und fahre rekursiv mit dem Unterbaum zu N fort. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 369 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern TU Kaiserslautern 3. Funktionales Programmieren Beispiel: (Versickern lassen) ©Arnd Poetzsch-Heffter ©Arnd Poetzsch-Heffter 370 3.2 Algorithmen auf Listen und Bäumen Beispiel: (Versickern lassen) (2) 371 ©Arnd Poetzsch-Heffter TU Kaiserslautern 372 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Beispiel: (Versickern lassen) (3) 3.2 Algorithmen auf Listen und Bäumen Funktion heapify formuliert auf Basis von FVBINREE: heapify :: FVBintree -> Int -> FVBintree -- Stelle Heap - Eigenschaft im Knoten ix her -- Annahme : die Kinder von ix erfuellen die -- Heap - Eigenschaft heapify b ix = let ds = get b ix in if hasLeft b ix && not ( hasRight b ix) then let lx = left b ix in if get b lx `leq` ds then b else swap b ix lx -- else ... ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 373 3.2 Algorithmen auf Listen und Bäumen 374 3.2 Algorithmen auf Listen und Bäumen Konkretisierung des Heapsort-Algorithmus: rechten Unterbaum 1. Schritt: left b ix right b ix if get b lx `leq` get b rx then rx else lx in if get b largerKid `leq` ds then b else heapify (swap b ix largerKid ) largerKid else b ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren Funktion heapify formuliert auf Basis von FVBINREE: (2) else -- hat linken und -- oder ist Blatt if hasRight b ix then let lx = rx = largerKid = ©Arnd Poetzsch-Heffter TU Kaiserslautern 375 • Erzeuge Binärbaum-Repräsentation aus Eingabefolge. • Stelle Heap-Eigenschaft her, indem heapify ausgehend von den Blättern für jeden Knoten aufgerufen wird. Es reicht, nur Knoten mit Kindern zu berücksichtigen. ©Arnd Poetzsch-Heffter TU Kaiserslautern 376 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Konkretisierung des Heapsort-Algorithmus: (2) Heapsort: Abstrakte Version 2. Schritt: Wir betrachten zunächst Heapsort auf Basis des abstrakten Datentyps für markierte, fast vollständige, indizierte Binärbaume: • Schreibe den Wurzel-Datensatz in die Ausgabe. • Schreibe den Datensatz des letzten Elementes in den heapifyAll :: FVBintree -> FVBintree hpfyEmb :: FVBintree -> Int -> FVBintree -- Hilfsfunktionen fuer den ersten Schritt Wurzelknoten (swap) • Entferne das letzte Element. • Stelle die Heap-Eigenschaft wieder her. heapifyAll b = hpfyEmb b (( size b) `div` 2) • Fahre mit Schritt 2 fort, solange die Größe > 0. hpfyEmb b 0 Lemma: In einem fast vollständigen Binärbaum der Größe n sind die Knoten mit den Indizes (ndiv 2) bis n − 1 Blätter. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 377 then b else heapify b 0 hpfyEmb b ix = hpfyEmb ( heapify b ix) (ix -1) if size b == 0 TU Kaiserslautern 3. Funktionales Programmieren 378 3.2 Algorithmen auf Listen und Bäumen Bemerkungen: -- heapsort : sortiert gegebene Liste heapsort :: [ Dataset ] -> [ Dataset ] • Wie wir in Kapitel 4 zeigen, profitiert Heapsort davon, dass sich heapsort xl = reverse ( sortheap ( heapifyAll ( create xl))) sortheap :: FVBintree -> [ Dataset ] sortheap hp = if size hp == 0 then [] else let maxds = get hp 0 hp1 = swap hp 0 (size hp - 1) hp2 = removeLast hp1 hp3 = heapify hp2 0 in maxds : ( sortheap hp3) TU Kaiserslautern = ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen Heapsort: Abstrakte Version (2) ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen fast vollständige, markierte, indizierte Binärbäume sehr effizient mit Feldern realisieren lassen. • Zu einem algorithmischen Problem (hier Sortieren) gibt es im Allg. viele Lösungen. • Algorithmische Lösungen unterscheiden sich in: I der Laufzeiteffizienz (messbar) I der Speichereffizienz (messbar) I der „Komplexität“ der Verfahrensidee (im Allg. nicht messbar). 379 ©Arnd Poetzsch-Heffter TU Kaiserslautern 380 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Bemerkungen: (2) 3.2 Algorithmen auf Listen und Bäumen Unterabschnitt 3.2.2 In den folgenden Kapiteln werden wir demonstrieren, • wie einige der obigen Algorithmen in anderen Suchen Programmierparadigmen formuliert werden können; • wie der Effizienz/Komplexitätsbegriff präzisiert werden kann. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 381 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Suchen 382 3.2 Algorithmen auf Listen und Bäumen Schnittstell zum Suchen Wir betrachten die folgende Schnittstelle: module Dictionary (Dict ,emptyDict ,get ,put , remove ) where Die Verwaltung von Datensätzen basiert auf drei grundlegenden Operationen: • Einfügen eines Datensatzes in eine Menge von Datensätzen; • Suchen eines Datensatzes mit Schlüssel k ; type Dict = STree -- type des Dictionarys emptyDict :: Dict -- leeres Dictionary get :: Dict -> Int -> (Bool , String ) -- Nachschauen des Eintrags zu Schluessel i • Löschen eines Datensatzes mit Schlüssel k . Bemerkung: Weitere oft gewünschte Operationen sind: Sortierte Ausgabe, Suchen aller Datensätze mit bestimmten Eigenschaften, Bearbeiten von Daten ohne eindeutige Schlüssel, etc. put :: Dict -> Int -> String -> Dict -- Einfuegen des Eintrags (i,s), -- Ueberschreibt ggf. alten Eintrag zu i remove :: Dict -> Int -> Dict -- Loeschen des Eintrags zu Schluessel i ©Arnd Poetzsch-Heffter TU Kaiserslautern 383 ©Arnd Poetzsch-Heffter TU Kaiserslautern 384 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Bemerkung: 3.2 Algorithmen auf Listen und Bäumen Begriffsklärung: (Binärer Suchbaum) In der Literatur zur funktionalen Programmierung wir „get“ oft „lookup“ oder „search“, „put“ oft „insert“ und „remove“ oft „delete“genannt. Ein markierter Binärbaum B ist ein natürlicher binärer Suchbaum (kurz: binärer Suchbaum), wenn die Suchbaum-Eigenschaft gilt, d.h. wenn für jeden Knoten K in B gilt: Um den Zusammenhang zu OO-Schnittstellen augenfälliger zu machen, benutzen wir die dort üblichen Namen. Ziel ist es, Datenstrukturen zu finden, bei denen der Aufwand für obige Operationen gering ist. Wir betrachten hier die folgenden Dictionary-Realisierungen: • Alle Schlüssel im linken Unterbaum von K sind echt kleiner als der Schlüssel von K . • Alle Schlüssel im rechten Unterbaum von K sind echt größer als der Schlüssel von K . • lineare Datenstrukturen (Übung) • (natürliche) binäre Suchbäume (Vorlesung) ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 385 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Bemerkung: 386 3.2 Algorithmen auf Listen und Bäumen Datenstruktur für Suchbäume: Wir stellen Dictionaries als Binärbäume mit Markierungen vom Typ Dataset dar: • „Natürlich“ bezieht sich auf das Entstehen der Bäume in Abhängigkeit von der Reihenfolge der Einfüge-Operationen (Abgrenzung zu balancierten Bäumen). data STree = Node Dataset STree STree Empty deriving (Eq , Show) • In einem binären Suchbaum gibt es zu einem Schlüssel maximal einen Knoten mit entsprechender Markierung. | emptyDict = Empty Die Konstante emptyDict repräsentiert das leere Dictionary. ©Arnd Poetzsch-Heffter TU Kaiserslautern 387 ©Arnd Poetzsch-Heffter TU Kaiserslautern 388 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Invariante für Suchbäume: 3.2 Algorithmen auf Listen und Bäumen Suchen eines Eintrags: Binärbäume, die als Dictionary verwendet werden, müssen die Suchbaum-Eigenschaft erfüllen. Alle Funktionen, die Dictionaries als Parameter bekommen, gehen davon aus, dass die Suchbaum-Eigenschaft für die Parameter gilt. Wenn kein Eintrag zum Schlüssel existiert, liefere (False,""); sonst liefere (True,s), wobei s der String zum Schlüssel ist: Die Funktionen müssen garantieren, dass die Eigenschaft auch für Ergebnisse gilt. get get | | | Man sagt: Die Suchbaum-Eigenschaft ist eine Datenstrukturinvariante von Dictionaries. Empty k = (False ,"") (Node (km ,s) l r) k k < km = get l k km < k = get r k otherwise = (True ,s) Wir guarantieren die Datenstrukturinvariante u.a. dadurch, dass wir Nutzern des Moduls Dictionary keinen Zugriff auf die Konstruktoren geben. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 389 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Einfügen eines Eintrags: 390 3.2 Algorithmen auf Listen und Bäumen Beispiel: Algorithmisches Vorgehen: Einfügen von 33: • Neue Knoten werden immer als Blätter eingefügt. • Die Position des Blattes wird durch den Schlüssel des neuen Eintrags festgelegt. • Beim Aufbau eines Baumes ergibt der erste Eintrag die Wurzel. • Ein Knoten wird I in den linken Unterbaum der Wurzel eingefügt, wenn sein Schlüssel kleiner ist als der Schlüssel der Wurzel; I in den rechten, wenn er größer ist. Dieses Verfahren wird rekursiv fortgesetzt, bis die Einfügeposition bestimmt ist. ©Arnd Poetzsch-Heffter TU Kaiserslautern 391 ©Arnd Poetzsch-Heffter TU Kaiserslautern 392 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Implementierung von put: 3.2 Algorithmen auf Listen und Bäumen Bemerkungen: • Die Reihenfolge des Einfügens bestimmt das Aussehen des Die algorithmische Idee lässt sich direkt umsetzen. binären Suchbaums: Beachte aber, dass das Dictionary nicht verändert wird, sondern ein neues erzeugt und abgeliefert wird: Reihenfolgen: 2;3;1 put Empty k s = Node (k,s) put (Node (km ,sm) l r) k s | k == km = Node (k,s) l | k < km = Node (km ,sm) | otherwise = Node (km ,sm) ©Arnd Poetzsch-Heffter 1;2;3 Empty Empty r (put l k s) r l (put r k s) TU Kaiserslautern 3. Funktionales Programmieren 1;3;2 393 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Bemerkungen: (2) 394 3.2 Algorithmen auf Listen und Bäumen Löschen: Löschen ist die schwierigste Operation, da • Es gibt sehr viele Möglichkeiten, aus einer vorgegebenen • ggf. innere Knoten entfernt werden und dabei Schlüsselmenge einen binären Suchbaum zu erzeugen. • die Suchbaum-Eigenschaft erhalten werden muss. • Bei sortierter Einfügereihenfolge entartet der binäre Suchbaum zur linearen Liste. Algorithmisches Vorgehen: • Der Algorithmus zum Einfügen ist schnell, insbesondere weil • Die Position eines zu löschenden Knotens K mit Schlüssel X wird keine Ausgleichs- oder Reorganisationsoperationen vorgenommen werden müssen. nach dem gleichen Verfahren wie beim Suchen eines Knotens bestimmt. • Dann sind drei Fälle zu unterscheiden: ©Arnd Poetzsch-Heffter TU Kaiserslautern 395 ©Arnd Poetzsch-Heffter TU Kaiserslautern 396 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen Löschen: (2) Löschen: (3) 1. Fall: K ist ein Blatt. 2. Fall: K mit Schlüssel X hat genau einen Unterbaum. Lösche K : K wird im Eltern-Knoten durch sein Kind ersetzt und gelöscht: Entsprechend, wenn Knoten mit Schlüssel X in rechtem Unterbaum. Die anderen links-rechts-Varianten entsprechend. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 397 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Löschen: (4) 398 3.2 Algorithmen auf Listen und Bäumen Löschen: (5) 3. Fall: K mit Schlüssel X hat genau zwei Unterbäume. Problem: Wo werden die beiden Unterbäume nach dem Löschen von K eingehängt? Hier gibt es 2 symmetrische Lösungsvarianten: • Ermittle den Knoten KR mit dem kleinsten Schlüssel im rechten Unterbaum, Schlüssel von KR sei XR. • Speichere XR und die Daten von KR in K . • Lösche KR gemäß Vorgehen zu Fall 1 bzw. 2, möglich da für KR einer der Fälle zutrifft. (andere Variante: größten Schlüssel im rechten UB) ©Arnd Poetzsch-Heffter TU Kaiserslautern 399 ©Arnd Poetzsch-Heffter TU Kaiserslautern 400 3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen 3. Funktionales Programmieren Umsetzung in Haskell Umsetzung in Haskell (2) removemin :: STree -> (Dataset , STree) -- Parameter : nichtleerer binaerer Suchbaum b. -- Liefert Eintrag d mit kleinstem Schluessel in b -- und Baum nach Loeschen von d in b • Die Fälle 1 und 2 lassen sich direkt behandeln. • Für Fall 3 realisiere Hilfsfunktion removemin, die I nichtleeren binären Suchbaum b als Parameter nimmt; I ein Paar (mnm, br ) als Ergebnis liefert, wobei I I ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen removemin (Node d Empty r) = (d,r) removemin (Node d l r) = let (mnm ,ll) = removemin l in (mnm , Node d ll r) mnm der kleinste Datensatz in b ist und br der Baum ist, der sich durch Löschen von mnm aus b ergibt. TU Kaiserslautern 3. Funktionales Programmieren 401 ©Arnd Poetzsch-Heffter 3.2 Algorithmen auf Listen und Bäumen TU Kaiserslautern 3. Funktionales Programmieren Umsetzung in Haskell (3) 402 3.2 Algorithmen auf Listen und Bäumen Diskussion: Der Aufwand für die Grundoperationen Einfügen, Suchen und Löschen eines Knotens ist proportional zur Tiefe des Knotens, bei dem die Operation aus- geführt wird. Ist h die Höhe des Suchbaumes, ist der Aufwand der Grundoperationen im ungünstigsten Fall also O(h), wobei remove Empty k = Empty remove (Node (km ,s) l r) k | k < km = Node (km ,s) ( remove l k) r | km < k = Node (km ,s) l ( remove r k) | l == Empty = r | r == Empty = l | otherwise = -- k == km && l /= Empty /= r let (mnm ,rr) = removemin r in Node mnm l rr log(N + 1) ≤ h ≤ N für Knotenanzahl N. Folgerung: Bei degenerierten natürlichen Suchbäumen kann linearer Aufwand für alle Grundoperationen entstehen. Im Mittel verhalten sich Suchbäume aber wesentlich besser. Zusätzlich versucht man durch gezielte Reorganisation eine gute Balancierung zu erreichen (siehe Kapitel 5). ©Arnd Poetzsch-Heffter TU Kaiserslautern 403 ©Arnd Poetzsch-Heffter TU Kaiserslautern 404