0.0.1 Rot-Schwarz Bäume Rot-Schwarz Bäume sind binäre Suchbäume, deren Knoten entweder “rot” oder “schwarz” gefärbt sind, d.h. sie werden unterschiedlich gekennzeichnet. Wie bei anderen Binärbäumen befinden sich die Daten in den Blättern. Dazu haben Rot-Schwarz Bäume folgende Eigenschaften: • jedes Blatt ist schwarz, • rote Knoten haben zwei schwarze Kinder, • jeder Weg von der Wurzel bis zu einem Blatt hat die gleiche Anzahl schwarzer Knoten. Daraus folgt, dass Rot-Schwarz Bäume logarithmische Höhe haben. Damit RotSchwarz Bäume beim Einfügen eines Elementes diese Eigenschaften bewahren, werden dabei wenn erforderlich Rotationen durchgeführt (so wie bei AVL-Bäumen). Beispiel. Ein Rot-Schwarz Baum kann beispielhaft folgendermaßen aussehen: Bemerkung. Wenn man rote Knoten mit ihren schwarzen Vätern verschmilzt (siehe Kreise auf der Abbildung), wird ein Rot-Schwarz Baum zu einem (2,4)Baum. Dies ist ausserdem auch der Fall bei Bruderbäumen : wenn man Vaterknoten mit ihren Einzelkindern verschmilzt, bekommt man einen AVL-Baum. 0.0.2 Wörterbuchproblem für Wörter bzw. Strings Gegeben ist ein endliches Alphabet Σ, wobei |Σ| = k. Das Universum ist die Menge der Wörter in Σ∗ . 1 Das Problem ist das gleiche wie beim normalen Wörterbuchproblem, bezogen auf dieses Universum. Man möchte also Elemente suchen, einfügen und streichen können. Anwendungen Dieses Problem ist für folgende Anwendungen relevant: • Für Suchmaschinen im Internet, da man nach bestimmten Strings in Internetseiten sucht. • In der Bioinformatik, da bei der Genomanalyse Teilstrings bei der Suche nach einem “größten gemeinsamen Superstring” manipuliert werden. • Für Datenkompression, weil es in einer Sprache etwa 60000 Wörter gibt, mit jeweils im Mittel 5 Zeichen, also insgesamt 300000 Zeichen. Aber log(300000) ' 18, also 3 Bytes, aber man benutzt im Mittel 5 Bytes. Es bleiben also 2 Bytes zum Komprimieren übrig. Datenstruktur Die benutzte Datenstruktur für das Problem ist der “Trie” (vom englischen retrieval), auch “digitaler Suchbaum” genannt. Es ist ein Suchbaum, in dem jeder Knoten einen Buchstaben enthält. Jedes seiner Kinder enthält den nächsten Buchstaben, der in einem der Wörter des Alphabets vorkommen. So wird ein Wort also durch einen Weg von der Wurzel zu einem Knoten dargestellt. Man muss also Knoten, die das Ende eines Wortes darstellen, speziell markieren. Bemerkenswert ist dabei, dass die Wurzel des Baumes keine Information enthält, da die Wörter ja nicht alle mit dem gleichen Buchstabe anfangen. Andernfalls müsste man einen Baum pro Anfangsbuchstabe haben. Beispiel. Sei das Alphabet Σ = {der, die, das, einer, eine, eines}. Erstellen wir den dazugehörenden Trie: Laufzeit der Operationen Suchen, Einfügen und Streichen eines Wortes W der Länge |W | dauert O(|W |). |W | ist nämlich die Länge des Weges, der vom ersten Buchstabe bis zum letzten Buchstabe führt, da in jedem Knoten nur ein Buchstabe gespeichert wird. Verbesserung Man kann dies etwas verbessern, indem man Folgen von Knoten, die nur ein Kind haben, einfach in einem Knoten zusammenschmilzt, wie beim folgenden Beispiel: Knotenorganisation In der Regel besteht jeder Knoten aus einem Feld oder einer verketteten Liste von Verweisen auf die Kind-Knoten. Um schnell feststellen zu können, ob ein Knoten ein Kind hat, das einen bestimmten Buchstaben enthält, kann man für jeden Knoten einen Bit-array der Größe des Alphabets speichern. 2 d a s e e u i r e i n e n s Abbildung 1: Beispiel : trie eine s r Abbildung 2: Verbesserung zum trie 0.1 Das Vereinige-Finde-Problem (auch “Union-Find” oder “Disjoint Sets”) Abstrakter Datentyp Der abstrakte Datentyp, der dieses Problem darstellt, ist eine feste endliche Menge S (o.B.d.A. ist S = {1,...,n}). Sei S eine Partition von dieser Menge S, mit S = {S1 , ..., Sk } Sk (d.h. i=1 Si = S und ∀i, j mit i 6= j: Si ∩ Sj 6= ∅). Jedes Si wird durch einen Repräsentanten - einem seiner Elemente - dargestellt. Operationen Bezüglich dieser Menge sollen folgende Operationen möglich sein: • VEREINIGE(Si , Sj ) mit Si , Sj zwei Repräsentanten. Diese Operation verschmilzt zwei Mengen der Partition S zu einer einzigen. Die Partition wird also sozusagen “vergröbert”. Formal kann man es folgenderweise darstellen : S := S \ {Si , Sj } ∪ {Si ∪ Sj }). • FINDE(a) mit a ∈ S. 3 Diese Operation liefert den Repräsentanten von der Menge Si aus S, die a enthält. Anwendungen Dieses Problem hat folgende Anwendungen : • Sei G = (V, E) ein ungerichteter Graph. Die Zusammenhangskomponente von G zu finden, ist ein Vereinige-Finde Problem : Sei S die Menge der Knoten und S zunächst eine Menge von n einelementiger Mengen. Man durchläuft die Menge E der Kanten ; für jede Kante : e = (u, v). Falls Si =FINDE(n)6= FINDE(v)=Sj , dann VEREINIGE(Si ,Sj ). Zum Schluß enthält S die Zusammenhangskomponente von G. • Bei der Suche nach minimal spannende Bäume (siehe später im Skript). • Bei der Bildverarbeitung, wenn man “Segmentierung” machen möchte, also das Einteilen des Bildes in mehrere ähnliche Zonen (der Farbe nach). • In der Sprache Fortran gibt es den Befehl EQUIVALENCE(x,y), der zwei Variablen x und y gleichstellt. Beim Kompilieren gibt es dann ein solches Vereinige-Finde Problem, wenn mehrere EQUIVALENCE-Befehle im Code vorkommen. (z.B. EQUIVALENCE(x,y) und EQUIVALENCE(y,z) ). Datenstruktur Die verwendete Datenstruktur für das Problem ist ein “Wald”, also eine Menge von Bäumen. Für jede Menge Si steht ein Baum, dessen Knoten die Elemente von Si enthalten. Verweise sind Kind-Vater-Verweise (und nicht Vater-Kind-Verweise wie z.B. bei Binärbäumen). Dabei ist der Repräsentant der Menge Si , die Wurzel des Baumes. 9 (1) 3 2 6 8 7 1 4 5 Abbildung 3: Beispiel : Wald Laufzeit von Vereinige-Finde Operationen Analysieren wir kurz die Laufzeit dieser Operationen : • VEREINIGE(Si ,Sj ): Diese Operation macht die Wurzel eines der beiden Bäume zum Kind der anderen Wurzel. Das dauert nur O(1) Zeit, weil es nur eine Zeigermodifikation ist. 4 Beispiel. siehe Pfeil (1) auf Abbildung 3. • FINDE(a): Diese Operation erfolgt folgenderweise : Man erhält zunächst einen Verweis auf die Stelle des Waldes, wo sich a befindet. Von dort folgt man den Vaterverweisen bis zur Wurzel. Der Verweis auf die Wurzel wird dann geliefert, da diese den Repräsentanten enthält. Die Laufzeit dieser Operation ist also O(h) wobei h die Höhe des Baumes ist, der a enthält, da wir den ganzen Baum von unten nach oben durchlaufen. Im schlechtesten Fall ist das Θ(n), wenn der Wald einen einzigen Baum enthält, und dieser in Form einer Liste ist (2). (1) (2) Abbildung 4: Alternative (1) wird angestrebt Dies kann aber verbessert werden, indem bei der VEREINIGE-Operation der Baum mit geringerer Höhe an die Wurzel des anderen gehängt wird (Wenn beide Höhen gleich sind ist es egal). Dazu braucht man ein zusätzliches Feld “Höhe”. Wenn i ein Repräsentant ist, ist Höhe[i] die Höhe des entsprechenden Baumes. Behauptung 0.1.1. Höhenbalancierung Falls die Startsituation so ist, dass jedes Element der Partition nur ein Element entält ( Si = {i}, i=1,...,n), dann kann eine Folge von VEREINIGE-FINDE Operationen ausgeführt werden, so dass die Höhe eines entstandenen Baumes mit k Knoten nie größer ist als dlog(k)e. Satz 0.1.2. Mit Höhenausgleich gilt also: • VEREINIGE-Operationen erfolgen in O(1) Zeit (dazu ist die Höheninformation der Wurzel mit einem Feld in konstanter Zeit aktualisierbar) • FINDE-Operationen erfolgen in O(log(n)) Zeit. 5