Sortierte Folgen SS12

Aus ProgrammingWiki
< AuK
Archivierte Version vom 2012-04-22T18:11:35+00:00. Aktuelle Version

Inhaltsverzeichnis

Bäume

Termminologie

Binäre Bäume


Binäre Suchbäume

Einfügen eines Elementes

Beim Einfügen eines Elementes macht man sich die Eigenschaft zunutze, dass der Schlüssel des linken Sohns jeweils kleiner oder gleich dem Schlussel ist, den der Knoten selbst enthält. Der rechte Kindknoten dagegen enthält einen echt größeren Schlüssel. Fügt man also einen Schlüssel in den Baum ein so beginnt man beim Vergleich mit der Wurzel. Ist der Einzufügende Schlüssel größer als der Schlüssel der Wurzel, so setzt man den Vergleich im rechten Teilbaum des aktuellen Knotens fort. Andernfalls also wenn der Schlüssel kleiner oder gleich dem Schlüssel der Wurzel ist, setzt man den Vergleich im linken Teilbaum des aktuellen Knotens fort. Trifft man bei diesem Verfahren auf einen Knoten dessen Wertigkeit null entspricht, so wird an dieser Stelle der neue Schlüssel eingefügt. Für den Programmablauf lässt sich also folgendes allgemein formulieren:

  1. Abfrage ob der einzufügende Schlüssel größer oder gleich dem Schlüssel von N ist
  2. Abfrage ob der sich daraus ergebende Kindknoten dem Wert null entspricht
  3. wenn ja, dann erzeuge neuen Knoten mit dem einzufügenden Schlüssel
  4. sonst beginne mit Schritt 1 wobei N nun N.links bzw n.rechts ist

Für die Methode zum Einfügen in einen BST ergibt sich zum einen eine rekursive sowie eine iterative Umsetzungsmöglichkeit, die im folgenden durch Pseudocode beschrieben werden.

Rekursive Umsetzung

InsertRek( Schlüssel K, Knoten N )
if Schlüssel < N.Schlüssel do
    if N.links is null do
        N.links = neuer Knoten
        N.links.Schlüssel = K
        N.links.Vater = N
    else do 
        InsertRek( K, N.links )
else
    if N.rechts is null
        N.rechts = neuer Knoten
        N.rechts.Schlüssel = K
        N.rechts.Vater = N
    else do 
        InsertRek( K, N.rechts ) 

Iterative Umsetzung

InsertIt( Schlüssel K )
Knoten p = Wurzel
Knoten o = null
while p not null
    o = p
    if K < p.schlüssel or K is p.schlüssel
        p = p.links
    else if K > p.schlüssel
        p = p.rechts
    else return
p = neuer Knoten
p.Schlüssel = K
p.Vater = o
if Wurzel is null
    Wurzel = p
else if K < o.schlüssel or K is o.schlüssel
    o.links = p
else o.rechts = p

Finden eines Elementes

Beim suchen eines Elementes wird die spezielle Anordnung der Elemente in einem Baum genutzt. Ist der Baum leer, so ist der Schlüssel nicht enthalten. Ist das zu suchende Element größer als der Schlüssel der Wurzel bzw. des Knotens des aktuellen Teilbaumes so vergleicht man dessen rechten Kindknoten, ist er gleich so wurde das gesuchte Element gefunden ansonsten vergleicht man die Wurzel bzw. den linken Kindknoten des aktuellen Knotens. Beinhaltet der binäre Suchbaum ein oder mehrere Duplikate, so wird immer nur der Schlüssel gefunden, der den gerringsten Weg zu Wurzel des Baumes besitzt, also der Schlüssel mit der geringsten Tiefe. Folgende Schritte sind also beim Einfügen durchzuführen:

  • Abfrage ob der Baum leer ist
  • Vergleich ob der gesuchte Schlüssel der Schlüssel des Wurzelelementes ist
  • Wenn Schlüssel kleiner oder gleich ist, dann suche im linken Teilbaum weiter sonst rechts

Rekursive Implementierung

searchRek( Schlüssel k, Knoten n )
if n is null
    return null
if k is n.schlüssel
    return n 
if k < n.schlüssel
    return searchRek( k, n.links )
else return searchRek( k, n.rechts )	

Iterative Implementierung

searchIt( Schlüssel k, Knoten n )
Knoten nI = n
while nI not null and nI.schlüssel not k
    if k < nI.schlüssel
        nI = nI.links
    else nI = nI.rechts
return nI

Finden des Minimums

Um den minimalsten Schlüssel im Baum zu finden wandert man einfach von der Wurzel beginnend immer am linken Teilbaum entlang, bis der nächst folgende Knoten den Wert null enthält.

Iterative Implementierung

Knoten getMinIt( Knoten wurzel )
if wurzel is null
    return null
while wurzel.links not null
    wurzel = wurzel.links
return wurzel

Rekursive Implementierung

Knoten getMinRek( Knoten wurzel )
if wurzel is null
    return null
else if wurzel.links is null 
    return wurzel
else return getMinRek( wurzel.links )

Finden des Maximums

Um den maximalen Schlüssel im Baum zu finden wandert man einfach von der Wurzel beginnend immer am rechten Teilbaum entlang, bis der nächst folgende Knoten den Wert null enthält.

Iterative Implementierung

Knoten getMaxIt( Knoten wurzel )
if wurzel is null
    return null
while wurzel.rechts not null
    wurzel = wurzel.rechts
return wurzel

Rekursive Implementierung

Knoten getMaxRek( Knoten wurzel )
if wurzel is null
    return null
else if wurzel.rechts is null 
    return wurzel
else return getMaxRek( wurzel.rechts )

Finden des Nachfolgers

Sortierte Ausgabe des Baumes

Löschen eines Elementes

Mit einem Sohn

Mit zwei Söhnen

2-3-4-Bäume

Eigenschaften

  1. Knoten enthalten bis zu 3 Schlüssel
  2. 3-Knoten mit 3 ausgehenden Verkettungen sind zugelassen
  3. 4-Knoten mit 4 ausgehenden Verkettungen sind zugelassen
  4. Binärbaum Eigenschaften vorhanden (2-Knoten)

Suchen

Beim Suchen in 2-3-4-Bäumen verwendet man die Top-Down-Methode. Dabei folgt man ausgehend von dem kleinsten Element des Wurzelknotens der entsprechenden Verkettungen bis zum gesuchten Schlüssel. Im ersten Schritt wird dabei der gesuchte Schlüssel mit dem aktiven Element verglichen. Liefert der Vergleich eine wahre Aussage ist schon an dieser Stelle die Suche beendet. Ist dies nicht der Fall wird geprüft ob der gesuchte Schlüssel kleiner als das aktive Element im aktuellen Knoten ist. Ist dieses Kriterium wahr geht man die Verkettung vor dem aktiven Element weiter entlang und setzt das kleinste Element des Kindknotens als neues aktives Element und beginnt wieder im ersten Schritt. Im Fall, dass das Kriterium nicht erfüllt ist wird im Wurzelknoten das nächstgrößere Element aktiviert und beginnt mit diesem wieder bei Schritt eins. Sollte kein nächstes Element im aktuellen Knoten vorhanden sein wird der Verkettung nach dem zuletzt aktiven Elements zum kleinsten Element des Kindknotens gefolgt und der Erste Schritt wieder ausgeführt.

Einfügen

Günstiger Fall

Beim Einfügen wird ein Knoten solange aufgefüllt, bis er 3 Elemente enthält. Soll noch ein viertes Element aufgenommen werden ist dies nur durch ein Spalten des Knotens möglich. Dabei wird der überfüllte Knoten in 3 Teile gesplittet:

  • Knoten mit zwei Elementen
  • Knoten mit einem Element
  • Mittleres Element

Das mittlere Element rutscht bei der Spaltung in den Elternknoten und stellt somit die Verkettung der zwei neuen Knoten dar.


Ungünstiger Fall

Muss der überfüllte Knoten wie im Günstigen Falle gespalten werden und der Elternknoten ist schon mit 3 Elementen belegt, wird das Spalten in Richtung Wurzel solange nach oben gereicht bis man entweder auf kein 4-Knoten mehr trifft oder auf die Wurzel stößt. Bei der Wurzel wird jedoch nach gleicher Aufteilungsregel eine neue Wurzel erzeugt, wenn diese ebenfalls schon voll besetzt ist.

O im schlimmsten Fall: log(n)

Split-Operation

Eine alternative Möglichkeit um dem ungünstigen Fall sofort vorzubeugen ist die Split-Operation. Dabei wird anders als bei den oben beschriebenen Methoden, während ein Element in den Baum eingefügt wird beim Durchlauf zusätzlich nach 4-Knoten gesucht, die daraufhin ausgepalten werden. Somit wird die Split-Operation im schlimmsten Fall einmal durchgeführt.

Löschen

Das Löschen eines Elements aus einem Knoten lässt sich immer auf das Löschen eines Elements in einem Blatt zurückführen. Dafür ist es zunächst nötig die Position des zu löschenden Elements zu wissen. Neben dieser Position i wird außerdem das Element benötigt, welches sich an der äußersten rechten Stelle befindet um beide Elemente zu tauschen. Nun kann das Element aus dem Blatt gelöscht werden, wobei drei Fälle zu unterscheiden sind:

  • mehr als ein Element im Blatt - einfaches Entfernen
  • ein Element im Blatt und Nachbarknoten (gleicher Vorgänger) mindestens zwei Elemente - leihen eines Elements aus dem Nachbarknoten - Schlüssel in Vorgängerknoten verschieben - Vorgängerelement des zu löschenden Elements rückt an dessen Position und ersetz dieses
  • nur ein Element im Blatt und Nachbarknoten - Element entfernen - Vorgängerelement mit Nachbarelement zusammengefügt
  • vorhergehender Fall und Vorgängerknoten nur ein Element - gleihe Operation auf höherer Ebene

A-V-L-Bäume

Eigenschaften

  1. balancierte Knoten
  2. Höhenunterschied = 1
  3. Rotation um mittleres Element

Suchen

Einfügen

Löschen


Rot-Schwarz-Bäume

Komponenten der Knoten

  • Farbe
  • Schlüssel
  • Adresse des Eleternknotens
  • Adresse des linken Kindknotens
  • Adresse des rechten Kindknotens

Eigenschaften

  1. Jeder Konten ist entweder rot oder schwarz
  2. Die Wurzel ist schwarz
  3. Jedes Blatt( null/ nil ) ist schwarz
  4. Die Kind-Knoten eines Roten Knotens sind jeweils schwarz
  5. Alle Pfade die von einem bestimmten Knoten ausgehen und in einem Blatt dieses Teilbaums enden, haben die gleiche Anzahl an schwarzen Knoten


Rotationen

Durch die Einfüge- und Löschoperationen kann es dazu kommen, das eine oder mehrere Eigenschaften des Rot-Schwarzbaumes verletzt werden. Um die Zeigerstruktur des Baumes zu "reparieren" bedient man sich der Rotation.

Linksrotation

Rechtsrotation

Einfügen

Fall 1

Fall 2

Fall 3

Fall 4

Fall 5

Löschen

Fall 1

Fall 2

Fall 3

Fall 4

Fall 5