AVL-Baum

Datenstruktur in der Informatik
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 27. November 2012 um 13:44 Uhr durch 217.91.18.137 (Diskussion) (Änderung 110998278 von YMS wurde rückgängig gemacht.). Sie kann sich erheblich von der aktuellen Version unterscheiden.

Der AVL-Baum ist eine Datenstruktur in der Informatik, und zwar ein balancierter binärer Suchbaum, bei dem für jeden Knoten die Höhe der beiden Teilbäume höchstens um 1 differiert.[Ref 1] Diese Bedingung verhindert – bei einem moderaten Aufwand –, dass der Baum zu sehr aus der Balance gerät, und garantiert bei einer Belegung mit n Schlüsseln eine Höhe in O(log n). In O(log n) ist dann auch die maximale Anzahl der Schritte, um An- oder Abwesenheit eines Schlüssels festzustellen, sowie der maximale Aufwand beim Einfügen, Löschen und Traversieren.

Abb. 1: AVL-Baum mit Balance-Werten (grün)
AVL-Baum
Komplexität
Platz O(n)
Operation im Mittel Worst case
Suchen O(log n) O(log n)
Querschritt O(1) O(log n)
Min, Max O(log n) O(log n)
Einfügen O(1) O(log n)
Löschen O(1) O(log n)
n  Knoten im Baum
  ohne Navigation
Tab. 1: Platz- und Zeit-Komplexitäten

Der Name AVL leitet sich ab von den Erfindern Adelson-Velski und Landis, die diese Datenstruktur 1962 entwickelten.[Ref 2] Der AVL-Baum ist damit die älteste Datenstruktur für balancierte Bäume.

Balance

Als Balance-Wert eines Knotens[Anm 1]   in einem Binärbaum bezeichnet man die Höhendifferenz

 ,

wobei   die Höhe des Baums  , und   der linke,   der rechte Teilbaum von   ist.[Anm 2]

Ein binärer Suchbaum ist genau dann ein AVL-Baum, wenn stets für alle seine Knoten   das „AVL-Kriterium“

 

eingehalten wird – es also Vorkehrungen gibt, dass sich die Höhen von 2 Bruder-Teilbäumen um höchstens 1 unterscheiden.

Die Höhe   eines AVL-Baums   mit   Knoten ist beschränkt durch[Ref 3]

 [Anm 3].

Die obere Schranke kommt vom Fibonacci-Baum, der bei gegebener Höhe einen AVL-Baum mit kleinster Knotenanzahl darstellt, also am schlechtesten balanciert ist. Strebt die Anzahl   seiner Knoten gegen unendlich, so verhält sich seine mittlere Pfadlänge   bei Einheits-Zugriffswahrscheinlichkeiten über alle Knoten asymptotisch wie  [Anm 4].

Ähnlich wie es bei einem unbalancierten binären Suchbaum eine hohe Wahrscheinlichkeit gibt, dass er bei zufälligen Einfügungen eine gewisse Balanciertheit erlangt, ist auch beim AVL-Baum die Wahrscheinlichkeit sehr hoch, dass er besser balanciert herauskommt als seine am schlechtesten balancierte Ausprägung, der Fibonacci-Baum. Wie die Rechnung zeigt, kommt selbst letzterer beim Suchen im Mittel schon auf eine Effizienz, die nur um 4 % von der idealen eines vollständig (höhen-)balancierten Binärbaums abweicht. Wird über alle AVL-Bäume gemittelt, beträgt der Unterschied zum Ideal weniger als 2 %.

Operationen

Zum AVL-Baum gehören die drei Grundoperationen: Suchen, sowie Einfügen und Löschen eines Elements. Die Navigationsoperationen, d. s. die verschiedenen Suchoperationen, das Traversieren, Aufsuchen erstes oder letztes Element u. ä., lassen den Baum unverändert und funktionieren im Prinzip auf jedem binären Suchbaum. Sie liefern alle die Beschreibung einer Position im Baum, die hier – in Anlehnung an die DatenbankenCursor genannt sei. Bei anderen, z. B. den modifizierenden, Operationen ist der Cursor die Eingabe und spezifiziert den Einfügepunkt oder den zu löschenden Knoten.

Mit der Eigenschaft, dass alle genannten Operationen im schlechtesten Fall (worst case) die Komplexität O(log n) haben, gehört der AVL-Baum zu den höhen-balancierten binären Suchbäumen.

Suchen

Das Suchen eines Elements ist beim AVL-Baum die wichtigste unter den Navigations-Operationen. Die Höhen-Balancierung versucht, auf diese Operation hin zu optimieren. Sie ermöglicht einen sog. direkten Zugriff (i. Ggs. zum sequenziellen Zugriff der in-order-Traversierung). Sie wird i. d. R. als vorausgehende Operation sowohl beim Einfügen als auch beim Löschen eingesetzt.

Der binäre Suchbaum ist jederzeit so sortiert, dass links von jedem Knoten nur Knoten mit kleineren (oder gleichen) Schlüsseln, und rechts nur Knoten mit größeren (oder gleichen) Schlüsseln gespeichert sind. (Damit das funktioniert, muss die vom Benutzer zur Verfügung gestellte Vergleichsfunktion eine Totalordnung (genauer: eine totale Quasiordnung) etablieren. Implizit steckt in ihr auch die Spezifikation des „Schlüssels“.) Man findet also einen Schlüssel, indem man von der Wurzel des Baums ebenenweise nach unten wandert, und dabei jeweils nach links verzweigt, falls der gesuchte Schlüssel kleiner als der des aktuellen Knotens ist, oder rechts, wenn er größer ist. Stimmt ein Schlüssel überein, hat man ihn gefunden; erreicht man ein (Halb-)Blatt (d. h. einen Knoten mit Ausgangsgrad 0 oder 1), dessen Schlüssel nicht übereinstimmt und dem in der geforderten Richtung ein Nachfolger fehlt, ist garantiert, dass der Schlüssel im Baum nicht existiert (s. Suchen duplikatfrei).

Sowohl duplikatfreie als auch Bäume mit Duplikaten (u. U. gleichen Schlüsseln) lassen sich implementieren. (Das ist weniger eine Frage der Vergleichsfunktion als der Anwendungssituation.) Bei letzteren ist eine Suchoperation angebracht, die stets bis zu den (Halb-)Blättern hinab sucht und ermöglicht, ein Element gezielt links vom linkesten (am weitesten links liegenden) bzw. rechts vom rechtesten Duplikat einzufügen. Eine solche Suchoperation ist insbesondere dann interessant, wenn im Suchbaum nicht nur gesucht und gefunden werden soll, sondern auch sequenziell navigiert wird. Ferner erlaubt sie z. B., eine (möglicherweise unbekannte) Vorsortierung in einem Bereich zu retten, wo eine Einordnung nach dem aktuellen Sortiervergleichsschlüssel nur Duplikate produziert. Man nennt ein solches Verhalten „stabil“ (s. Stabilität (Sortierverfahren) mit erklärenden Beispielen). Zur Implementierung bei binären Suchbäumen s. Suchen mit Duplikaten.

Bei allen Varianten ist die Komplexität sowohl im Mittel wie auch im schlechtesten Fall O(log n). Das Sortieren einer Masse von n Elementen durch wiederholtes Suchen und Einfügen kostet O(n log n), also nicht mehr als mit den besten Sortierverfahren.

Traversieren (Iterieren, Enumerieren)

Die in-order-Traversierung (Querung), d. h. das Navigieren sowohl nach links als auch nach rechts zum nächsten Nachbarn in der gegebenen (Sortier-)Folge, ist besonders nützlich, wenn sie als Einzel-Operation vorliegt. So kann der Anwender eine Schleife in gewohnter Manier aufsetzen: Start mit der Suchfunktion (oder dem ersten Element), Iteration mit der Traversierfunktion.

Sie verhält sich im Mittel wie O(1) und im schlechtesten Fall wie O(log n). Eine Traversierung über den ganzen Baum kostet O(n).[Anm 5]

Aufsuchen erstes oder letztes Element

Verwandt hiermit ist die Positionierung auf das kleinste oder größte Element im Baum, welche sowohl im Mittel wie auch im schlechtesten Fall O(log n) kostet.

Ein Intervallbegriff (sowohl eigentlich wie uneigentlich) über der total geordneten Menge der Elemente im Baum lässt sich realisieren wie auch eine Art „unscharfe“ Suche, nämlich nach „größer“ oder „kleiner“ (s. binärer Suchbaum (Proximitäts-Suche)), – ebenfalls mit Aufwand O(log n).

Einfügen (Eintragen)

Es sei angenommen, dass die Navigation zum „Einfügepunkt“ bereits erledigt ist. Ein Einfügepunkt ist ganz links oder ganz rechts oder zwischen 2 Knoten, kann also auf jeden Fall durch einen Knoten zusammen mit einer Richtung (links oder rechts) spezifiziert werden. Von 2 Nachbarknoten ist immer einer ein (Halb-)Blatt; dieser Knoten sei – zusammen mit der entsprechenden Richtung – als der unmittelbare Einfügepunkt bezeichnet. Ein solches Paar (Knoten, Richtung) stellt einen Cursor dar; so wird er auch von einer nicht erfolgreichen Suchoperation geliefert.

Der Knoten mit dem neuen Schlüssel wird als Blatt am Einfügepunkt eingehängt. Beim Rücksprung aus der rekursiven Einfüge-Funktion wird geprüft, ob es genügt, den Balance-Wert anzupassen, oder ob der Teilbaum rebalanciert werden muss. Wird der Balance-Wert zu  , dann kommen wir von einem Ast, der vorher niedriger war, und die Höhe des betrachteten Teilbaums ändert sich nicht. Wird der Balance-Wert zu   (er muss vorher   gewesen sein), erhöht sich die Höhe des Teilbaums um 1. Die aufgerufene Funktion teilt dies der aufrufenden mit, die es in die Überprüfung der nächsthöheren Ebene einbezieht. Wird auf einer Ebene der Balance-Wert zu  , muss der Teilbaum rebalanciert werden. Nach einer Rebalancierung hat der Teilbaum an dieser Stelle die gleiche Höhe wie vor der Einfügeoperation, so dass die AVL-Balance des ganzen Baums auch schon wieder in Ordnung ist.

Wird die Wahrscheinlichkeit für die Notwendigkeit, die Balance auf der nächsthöheren Ebene überprüfen zu müssen, als <1 angenommen, dann verhält sich das Einfügen (ohne das Finden der Einfügeposition) im Mittel wie O(1)[Anm 6] und im schlechtesten Fall wie O(log n).

Unter der Voraussetzung, dass für die richtige Reihenfolge gesorgt ist, können also m vorsortierte Schlüssel mit Aufwand O(m) und ohne Suchvorgang[Anm 7] eingefügt werden.

Löschen (Austragen, Entfernen)

Die Navigation zum zu löschenden Knoten kann z. B. mittels einer Suche erfolgen. Wie beim Binärbaum gezeigt, sind beim Löschen mehr Fälle zu unterscheiden als beim Einfügen. Recht übersichtlich sind die Fälle, wo der zu löschende Knoten ein Blatt oder Halb-Blatt ist. Hat er aber zwei Söhne, dann müssen die beiden frei werdenden Teilbäume neu aufgehängt werden. Dazu wählt man einen der in-order-Nachbarn, also entweder den rechtesten Knoten des linken Teilbaums oder den linkesten des rechten Teilbaums (von den beiden Teilbäumen kann man den evtl. höheren bevorzugen), um die beiden Teilbäume daran wieder aufzuhängen. Der hierfür erforderliche Abstieg geht maximal über O(log n) Stufen und im Mittel über genau eine. Der Knoten nimmt den Platz des gelöschten Knotens ein und muss dabei natürlich selbst aus seinem Herkunftsteilbaum entfernt werden – das ist einfach, da er ein (Halb-)Blatt ist (s. Binärbaum).

Im nun folgenden Rücksprung werden die Balance-Werte überprüft, ggf. adjustiert und nötigenfalls Rebalancierungen durchgeführt. Die rekursiv aufgerufene Funktion teilt ihrem Aufrufer mit, ob sich die Höhe verringert hat, so dass dies in die Überprüfung der nächsthöheren Ebene einbezogen werden kann. Wird der Balance-Wert eines Knotens zu   (er war vorher  ), dann ändert sich seine Höhe nicht, und die Überprüfung ist abgeschlossen. Wenn ein Balance-Wert zu   wird, hat sich die Höhe des Teilbaums um 1 verringert und die Überprüfung muss weitergehen. Wird ein Balance-Wert zu  , muss der Teilbaum rebalanciert werden. Nach einer Rebalancierung mit Doppelrotation hat der neue Teilbaum eine um 1 niedrigere Höhe als vor der Löschoperation. Bei einer Einfachrotation kommt es aber auch vor, dass die gleiche Höhe wie vor dem Löschen herauskommt, womit dann der ganze Baum auch schon wieder in AVL-Form ist. Der Begründer dieser Theorie ist Peter Griffin.

Wird die Wahrscheinlichkeit für die Notwendigkeit, die Balance auf der nächsthöheren Ebene überprüfen zu müssen, als <1 angenommen, dann verhält sich das Löschen (ohne das Aufsuchen des zu löschenden Knotens) im Mittel wie O(1)[Anm 8] und im schlechtesten Fall wie O(log n).

Das Entfernen eines Intervalls enthaltend m (≪ n) Schlüssel kann mit einem einzigen Suchvorgang auskommen[Anm 7] und kommt so auf einen Aufwand von O(m+log n).

Rebalancierung

Wenn bei einer Einfügung bzw. Löschung die AVL-Balance zerstört wird, d. h. ein Höhenunterschied von mehr als   zwischen zwei Bruder-Teilbäumen entsteht, muss (innerhalb der aufgerufenen Operation) eine Rebalancierung durchgeführt werden. Hilfsmittel hierfür sind sog. Rotationen. Zur Abkürzung sei die Wurzel des höheren der beiden Teilbäume (in den Abb.en 2 und 3 jeweils Z) als der „+2-Knoten der Rebalancierung“ bezeichnet.

Zwei Fälle kommen vor: Einfachrotation und Doppelrotation. Eine Einfachrotation leistet die Rebalancierung, wenn der innere Sohn des +2-Knotens Z, d. i. der Sohn mit einer Sohnesrichtung, die der von Z entgegengesetzt ist, (t23 in der Abb. 2 bzw. Y in der Abb. 3) nicht höher ist als sein Bruder, der äußere Sohn. Der andere Fall, bei dem der innere Sohn höher ist, wird von einer Doppelrotation abgedeckt.

 
Abb. 2: Einfachrotation Links(X)

Rotation in Binärbäumen

Eine einzelne Rotation wird durch die Rotationsrichtung Links oder Rechts (im Bsp. Links) und (jeweils mit Bezug auf die Abb. 2) durch die Wurzel X des betroffenen Teilbaums spezifiziert. Das entspricht der Anhebung eines Knotens (hier: Z). Es geht um folgende Aktionen:

unten: Einhängen den Teilbaum zwischen Z und seinem Vater X (im Bsp. den linken Sohn t23 von Z) als rechten Sohn bei X. Dieser Teilbaum kann auch fehlen (h=0 in der Abb. 2).
Wippe: Einhängen X als linken Sohn bei Z.
oben:
Feststellen des Vaters und der Sohnesrichtung von X. Einhängen Z bei seinem Großvater als Sohn dieser Richtung.
 
Balkenwippe mit Kindern

Alle „Bewegungen“ sind nur „vertikal“ und ändern die vorhandene in-order-Reihenfolge nicht, was auch das senkrechte Raster und die <-Zeichen am oberen Rand der Abb.en verdeutlichen sollen.
Fazit: Eine Rotation „wippt“ eine Kante (hier: XZ) des Baums in ihre andere Orientierung (ZX). Damit verbunden sind notwendige Anpassungen an den beiden Endpunkten.

Eine Rotation benötigt nur eine konstante Anzahl von Verknüpfungsänderungen an einer konstanten Anzahl von Knoten. Die diesen Verknüpfungen entsprechenden Kanten sind in den Abb.en doppelt dick gezeichnet.

Einfachrotation

Die nebenstehende Abb. 2 zeigt eine Einfachrotation. In der oberen Hälfte ist ein Höhenunterschied von   bei den beiden Teilbäumen von Knoten X dargestellt. Der innere Sohn t23 des +2-Knotens Z ist nicht höher als sein Bruder t4.
Dies kann nach einem Einfügen in den Teilbaum t4 oder nach einem Löschen aus dem Teilbaum t1 auftreten. Der in der Abb. 2 blass gehaltene Fall, dass t23 gleich hoch ist wie t4, kommt nur beim Löschen vor.

Die Rebalancierung (gezeigt in der unteren Hälfte der Abb.) gelingt durch eine Einfachrotation nach links. Sie ist bereits oben als einzelne Rotation beschrieben. Die anzupassenden 3 Verknüpfungen sind in der Abb. verstärkt gezeichnet.

Die Höhe des neuen Teilbaums Z ist bei einer Einfügung gleich der von X vor der Operation (Einfügung), während sie bei einer Löschung um   vermindert oder unverändert ist, je nachdem ob Z rechtslastig war oder nicht.

 
Abb. 3: Doppelrotation RechtsLinks(Z, X) = Rechts(Z) dann Links(X)

Doppelrotation

Die in der Abb. 3 gezeigte Situation unterscheidet sich von der in Abb. 2 darin, dass der mittlere Teilbaum (mit Wurzel Y), d. i. der innere Sohn des +2-Knotens Z, höher ist als sein Bruder t4.
Das kann nach einer Einfügung in einen der Teilbäume t2 oder t3 oder nach einer Löschung aus dem Teilbaum t1 passieren. Der in der Abb. 3 in Standardfarbe gehaltene Fall, bei dem t2 gleich hoch ist wie t3, kommt nur beim Löschen vor, während die 2 höhenungleichen Fälle (genau ein Teilbaum blass) sowohl beim Einfügen wie beim Löschen vorkommen. Der vierte Fall (zweimal blass) bedarf keiner Rebalancierung.

Die Doppelrotation, deren Ergebnis im unteren Drittel der Abb. 3 gezeigt ist, ist eine Rechtsrotation durch Z gefolgt von einer Linksrotation durch X. Sie entspricht der zweimaligen Anhebung des höheren (und inneren) Sohnes Y des +2-Knotens Z.

Die anzupassenden 5 Verknüpfungen sind in der Abb. verstärkt gezeichnet.

Die Höhe des neuen Teilbaums Y ist bei einer Einfügung gleich wie die von X vor der Operation (Einfügung), während sie sich bei einer Löschung um   vermindert.

Beispiel

Der AVL-Baum in der Abb. 1 wird

  • nach der Löschung des Knotens G durch 2 Einfachrotationen Rechts(F), später Links(J)
  • nach der Einfügung eines Knotens T durch die Doppelrotation RechtsLinks(VP)

rebalanciert.

Implementierung

Zusätzlich zum Bedarf des binären Suchbaums muss in einem Knoten der Balance-Wert mit seinen 3 Möglichkeiten untergebracht werden: macht 2 Bits.[Anm 9]

Nach jedem Einfügen oder Löschen eines Knotens wird in allen seinen Vorfahren der Balance-Wert überprüft, berichtigt und ggf. auf AVL-Niveau gebracht. Erreicht man eine Ebene, bei der sich die Höhe des betroffenen Teilbaums nicht mehr ändert, ist die Operation abgeschlossen. Die Anpassungs- und Reparatur-Welle hat pro Ebene einen Dämpfungsfaktor von   (gemäß der Annahme, dass etwa die Hälfte der Knoten ausgewogen ist, die andere Hälfte nicht), so dass die Summe der Aufwände für Einfügung und Überprüfung[Anm 6] sowie für Löschung und Überprüfung[Anm 8] jeweils im Mittel konstant ist.

Da eine Rotation auch durch die Wurzel gehen kann, wird eine zusätzliche Datenstruktur benötigt, die den Baum repräsentiert und die hier Anker genannt sei. Sie enthält den Zeiger zur Wurzel und ist auf diese Weise Vater der Wurzel und Bestandteil der Datenstruktur AVL-Baum.

Iterative Programmierung

Der Anwender hat Vorteile davon, wenn der Programmierer die vom Aufschreiben her eleganten Rekursionen durch simple Iterationen ersetzt, da dadurch zunächst einmal der Speicherbedarf für den Programm-Stapelspeicher konstant gehalten werden kann. Für das Aufbewahren des Rückwegs zur Wurzel, der bei der rekursiven Programmierung meist im Programmstapelspeicher steckt, muss dann ein anderes, explizites Konstrukt gewählt werden, wie es der Cursor eines ist. Er kann sowohl als Einfügepunkt, zur Kennzeichnung des Löschkandidaten wie auch als Navigationsvehikel beim Traversieren dienen. Dadurch wird eine Trennung der modifizierenden Operationen von der Navigation möglich.

Trennung der Navigation von den modifizierenden Operationen

Einfüge- und Löschfunktion sind sinnvollerweise von der Suchfunktion zu trennen bei:

  • Einsatz zusätzlicher Navigationsfunktionen, z. B. einer in-order-Traversierung
  • Datenstrukturen, die mehrere Zugriffspfade besitzen, z. B. mehrere AVL-Strukturen

Vom Vorhandensein zusätzlicher Zugriffspfade hängt es auch ab, ob es genügt, den Stapel der Vorfahren im Cursor zu halten, oder ob ein Zeiger auf den Vater in jedem Knoten gebraucht wird – somit der Cursor nur aus Knotenzeiger und Richtung besteht.

Diese Modularisierung der Navigations- von den modifizierenden Operationen ist Voraussetzung für den im Mittel unterlogarithmischen (konstanten) Aufwand der letzteren, der in Anwendungssituationen mit starkem sequenziellem Anteil zu Buche schlagen kann.

Cursor

Die Größe und Komplexität des Cursors hängt entscheidend davon ab, ob der AVL-Knoten einen Zeiger zum Vaterknoten enthält oder nicht.

  1. Zeiger zum Vaterknoten vorhanden:
    Das Paar (Knoten, Richtung) stellt einen vollwertigen Cursor dar.
    Mit dem prozentualen Aufschlag auf den Speicherbedarf erkauft man sich auf jeden Fall eine prozentuale Einsparung an Laufzeit, da der Stapel der Vorfahren immer schon vorliegt.
  2. Zeiger zum Vaterknoten nicht vorhanden:
    Zusätzlich zum Paar (Knoten, Richtung) muss der Pfad vom Knoten zum Anker im Cursor gehalten werden mit einer Länge in O(log n).[Anm 10]

Anwendung mit mehreren AVL-Zugriffspfaden

Als Beispiel für eine Anwendung mit 2 Zugriffspfaden sei ein klassisches Speichermanagement gebracht. Elemente der Datenstruktur sind die freien Blöcke mit den Attributen (Feldern) Größe und Ort. Für beide Felder gebe es je eine Suchstruktur. Beim Akquirieren wird ein Block von Mindestgröße gesucht, ausgetragen und ein evtl. Rest wieder eingetragen. Bei der Speicherrückgabe wird nach Ort gesucht, auf Konfliktfreiheit mit den Nachbarblöcken geprüft (ebenfalls ein Beispiel für die Nützlichkeit der Einzel-Traversierung) und der zurückzugebende Block ggf. mit diesen verschmolzen. Alle Veränderungen müssen auf beiden Suchstrukturen durchgezogen werden. Enthalten die Datenstrukturen auch den Zeiger zum Vaterknoten, dann erspart das Hangeln von der einen Suchstruktur zur anderen einen Suchvorgang.[Anm 11]

Bei iterativer Programmierung kann man die Anpassungs- und Reparaturschleife auch in der umgekehrten Richtung, d. h. von der Wurzel zum Blatt, durchlaufen. Das ist insbesondere dann interessant, wenn auf den Baum hochgradig parallel (konkurrent) zugegriffen werden soll. Z. B. würde in einem Szenario „Suchen und Einfügen“ die Suchfunktion sich den tiefsten (letzten) unausgewogenen Knoten auf dem Pfad merken, ab dort den Baum sperren[Anm 12] und die Vergleichsergebnisse aufbewahren. Zum Fertigstellen der Einfügeoperation müsste dann der gesperrte Vorfahr (ggf. nach einer Rebalancierung) auf ausgewogen und alle seine Nachfahren bis zum Blatt auf die den eingeschlagenen Richtungen entsprechenden Balance-Werte gesetzt werden. Der Vorteil wäre, dass alle Knoten außerhalb des gesperrten Teilbaums von den anderen Prozessorkernen gleichzeitig mit der laufenden Einfügung konsistent besichtigt und auch verändert werden könnten.


Realistische Anwendungssituationen mit Performancedaten und -vergleichen mit anderen Suchalgorithmen sind zu finden bspw. bei Ben Pfaff.

Anmerkungen

  1. Da einem Knoten in umkehrbar eindeutiger Weise der von ihm gewurzelte maximale Unterbaum, „sein“ Teilbaum, zugeordnet werden kann, sollen Eigenschaften wie Höhe, Balance, Vater-Sohn-Beziehung usw. einem Knoten genauso wie einem solchen Teilbaum zukommen.
    Gleichzeitig fungiert ein Knoten, z. B. beim Umhängen eines Teilbaums, als dessen „Henkel“.
  2. Hier wird – wie auch sonst häufig bei gewurzelten Bäumen – dem leeren oder fehlenden Baum die Höhe 0 gegeben und einem Blatt die Höhe 1. Gezählt werden also die Ebenen der Knoten.
    Bei der Verwendung des Begriffs Tiefe seien dagegen die Zwischenräume (d. s. die Kanten) gezählt.
  3. mit   ,     und     (Zahl des goldenen Schnitts)
  4. mit   
  5. Jeder Bogen des Baums wird genau einmal absteigend und einmal aufsteigend besucht.
  6. a b Sei   die Anzahl der Knoten, die nicht Blätter sind und die zwei Teilbäume gleicher Höhe haben, und   die Anzahl der Knoten mit ungleich hohen Teilbäumen, dann ist   der Anteil der unausgewogenen. Nur die Fibonacci-Bäume und alle anderen dünnsten AVL-Bäume erreichen exakt  . Dagegen kommen nur die vollständig höhenbalancierten Binärbäume mit Knotenzahl   auf exakt  . Ferner sei angenommen, dass der gespiegelte Baum gleich häufig vorkommt wie das Original.
    Wir gehen rekursiv von folgender Situation aus: Bei einer Einfügung habe sich ergeben, dass die Höhe des Teilbaums, in dem die Einfügung stattfand, um 1 zugenommen hat. Beim Aufstieg zum Vaterknoten stellen wir fest, von welcher Seite wir kommen. Wir nehmen an: von rechts (das passt zu den Abb.en 2 und 3; die gespiegelte Alternative führt überdies zu denselben Werten). Wir überprüfen nun die Lage im Vaterknoten und haben die folgenden Fälle zu unterscheiden:
     
    vor
    Einfügung
      Häufigkeit  
    nach
    Einfügung
    Rebalan-
    cierung
    erforderlich
     
    da-
    nach
    Teilbaum
    wird
    höher um
    Nächste
    Ebene zu
    überprüfen
      links höher     Nein   Nein
      ausgewogen     Nein   Ja
      rechts höher     Ja     Nein
    Tab. 2: Zu unterscheidende Fälle nach einer Einfügung

    Die Wahrscheinlichkeit, bei einer Einfügung ausgehend von einer Ebene die nächsthöhere überprüfen zu müssen, ist also  . Die Wahrscheinlichkeit, dass wenigstens   Ebenen überprüft werden müssen, ist   und, dass genau   Ebenen überprüft werden müssen, ist  . Die Anzahl der zu überprüfenden Ebenen summiert sich dann im Mittel auf

     ,

    was bei dem plausiblen   ergibt, dass im Mittel etwa eine weitere Ebene überprüft werden muss.

  7. a b Es kostet sowohl bei der Einfüge- wie bei der Löschfunktion nur sehr wenige zusätzliche Maschinenbefehle, in einem gegebenen Cursor den Stapel der Vorfahren gültig zu halten.
  8. a b Über den Anteil der unausgewogenen Knoten seien dieselben Annahmen wie bei der Einfügung gemacht.
    Folgende Situation sei rekursiv angenommen: Nach einer Löschung habe sich die Höhe des Teilbaums, in dem die Löschung stattfand, um 1 vermindert. Ferner handele es sich o. B. d. A. um einen linken Teilbaum. Wir überprüfen nun die Lage im Vaterknoten dieses Teilbaums und unterscheiden die folgenden Fälle:
     
    vor
    Löschung
      Häufigkeit  
    nach
    Löschung
    Rebalan-
    cierung
    erforderlich
     
    da-
    nach
    Teilbaum
    wird nied-
    riger um
    Nächste
    Ebene zu
    überprüfen
      links höher     Nein   Ja
      ausgewogen     Nein   Nein
      rechts höher       Ja     1 Nein
          2 Ja
    1 Die vorletzte Zeile bezieht sich auf den Fall der Einfachrotation mit gleich hohen Söhnen

    2 und die letzte auf Rotationen mit nicht gleich hohen Söhnen, d. h. Doppelrotation (linker Sohn höher)
      oder Einfachrotation mit dem rechten Sohn höher.

    Tab. 3: Zu unterscheidende Fälle nach einer Löschung

    Bei einer Löschung ergibt sich eine Wahrscheinlichkeit von weniger als   für das Erfordernis, die nächsthöhere Ebene überprüfen zu müssen. Die Anzahl der zu überprüfenden Ebenen summiert sich dann im Mittel auf weniger als  , was beim plausiblen   bedeutet, dass im Mittel weniger als   Ebene eines Niveaus ≥ 2 überprüft werden muss.

  9. 1 Bit, wenn bei den Söhnen aufgezeichnet, mit der Bedeutung: „Knoten ist höher“ als Bruder.
  10. Als eine Datenstruktur, die im homogenen Arbeitsspeicher (Hauptspeicher) untergebracht ist, ist der AVL-Baum durch die Größe desselben beschränkt. Dadurch ergeben sich auch Beschränkungen für die Pfadlänge. Gängig sind Hauptspeicher mit 32 Bit und 64 Bit breiten 8-Bit-Byte-Adressen.
    Länge
    eines
    Zeigers
    in Bit
    maximale Anzahl adressierbarer Bytes minimale Knotenlänge
    (2 Zeiger+1 Byte
    +Nutzdaten1)
    maxi-
    male Anzahl Knoten
    Knotenzahl des nächstgrößeren Fibonacci-Baums ... ... der Höhe
    32 4294967296 2·4+1+3 = 12 3,6·108 433494436 41
    64 18446744073709551616 2·8+1+7 = 24 7,7·1018 1100087778366101930 86
    1 inklusive Schlüssel. Bei mehr Nutzdaten und weniger Hauptspeicher
      verringert sich die maximale Knotenzahl und Pfadlänge entsprechend.
    Tab. 4: Maximale Pfadlänge in Abhängigkeit von der Adressierungsbreite
  11. Wollte man jedoch ohne Zeiger zum Vater auskommen, so müsste man zum Aufbau des Stapels der Vorfahren, der erforderlich ist für das bei der Verschmelzung der Nachbarblöcke anfallende Entfernen derselben aus der Suchstruktur Größe, nach dem ersten Sortierbegriff Größe nachrangig nach Ort sortieren, um angesichts der inhärenten Mehrdeutigkeit des Schlüssels Größe den Aufwand für die Identifikation der Blöcke nicht ins Lineare ansteigen zu lassen.
  12. Genau genommen bedarf es einer Kette mit den Gliedern Sperren(Sohn) und Entsperren(Vater) bis zum ersten unausgewogenen Knoten, der zunächst gesperrt bleibt, bis er in ein neues Wechselspiel mit den Gliedern Sperren(unausgewogenen Knoten) und Entsperren(Vorfahr) eintritt. (Dabei bedeutet Sperren(Knoten) eigentlich Sperren(Teilbaum). Und: bei einer potenziellen Rebalancierung muss die Sperre sogar eine Ebene höher gehalten werden.) Ist der Einfügepunkt erreicht, kann auch schon mit der Korrektur- und Entsprerrschleife begonnen werden – für maximale Parellelität wieder in einer Sohn-Vater-Kette.
    Wenn alle nebenläufigen Mitspieler diese Gerichtetheit des Sperrprotokolls einhalten, kann eine zirkuläre Abhängigkeit und damit eine Verklemmung (deadlock) nicht entstehen.

Weitere Bäume

Literatur

  • Ralf Hartmut Güting, Stefan Dieker: Datenstrukturen und Algorithmen. Stuttgart 2004, ISBN 9783519221210.
  • Udi Manber: Introduction to Algorithms – A Creative Approach. 1989, Kapitel 4.3.4, S.75ff, Addison-Wesley Publishing Company.
Commons: AVL-Bäume – Sammlung von Bildern, Videos und Audiodateien

Einzelnachweise

  1. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein: Introduction to Algorithms. 2. Auflage. MIT Press, Cambridge MA 2001, ISBN 0-262-03293-7, S. 296 (englisch).
  2. G. M. Adelson-Velsky, E. M. Landis: An algorithm for the organization of information (Englische Übersetzung aus Doklady Akademii Nauk SSSR 146). In: Soviet Math. Doklady. 3. Jahrgang, 1962, S. 1259–1263.
  3. Donald E. Knuth: The Art of Computer Programming, Volume 3, Sorting and Searching, Second Edition. Addison-Wesley, 1998, ISBN 0-201-89685-0, 6.2.3 Balanced Trees, S. 458–478.