{{#switch: Vorlage:Str match |[|] = ERROR |(Direkteingabe der Klammern)}}
{{#switch: Vorlage:Str match |91|93 = ERROR |(HTML-Entität dezimal)}}
{{#switch: Vorlage:Str match |5b|5d = ERROR |(HTML-Entität hexadezimal)}}
{{#switch: Vorlage:Str match |lbrack|rbrack = ERROR |(benannte HTML-Entität)}}
=Vorlage:FN, Fehler in Parameter 1: Keine eckigen Klammern verwenden, das führt sonst zu Verwechslungen mit dem offiziellen MediaWiki-Belegsystem.
}}}}
{{#switch: Vorlage:Str match |[|] = ERROR |(Direkteingabe der Klammern)}}
{{#switch: Vorlage:Str match |91|93 = ERROR |(HTML-Entität dezimal)}}
{{#switch: Vorlage:Str match |5b|5d = ERROR |(HTML-Entität hexadezimal)}}
{{#switch: Vorlage:Str match |lbrack|rbrack = ERROR |(benannte HTML-Entität)}}
=Vorlage:FN, Fehler in Parameter 1: Keine eckigen Klammern verwenden, das führt sonst zu Verwechslungen mit dem offiziellen MediaWiki-Belegsystem.
}}}}
{{#switch: Vorlage:Str match |[|] = ERROR |(Direkteingabe der Klammern)}}
{{#switch: Vorlage:Str match |91|93 = ERROR |(HTML-Entität dezimal)}}
{{#switch: Vorlage:Str match |5b|5d = ERROR |(HTML-Entität hexadezimal)}}
{{#switch: Vorlage:Str match |lbrack|rbrack = ERROR |(benannte HTML-Entität)}}
=Vorlage:FNZ, Fehler in Parameter 1: Keine eckigen Klammern verwenden, das führt sonst zu Verwechslungen mit dem offiziellen MediaWiki-Belegsystem.
}}}}
Platz- und Zeit-Komplexitäten
Ein Rot-Schwarz-Baum, auch RS-Baum oder RB-Baum, ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}} oder RB tree) ist eine Datenstruktur vom Typ binärer Suchbaum, die „sehr schnellen“ Zugriff auf die in ihr gespeicherten Schlüssel garantiert. Rot-Schwarz-Bäume wurden zuerst 1972 von Rudolf Bayer beschrieben,<ref>{{#invoke:Vorlage:Literatur|f}}{{#if:
<math>2 \log_2(n+1)</math>.</ref> Somit können die wichtigsten Operationen in Suchbäumen – Suchen, Einfügen und Löschen – garantiert in <math>\mathcal O(\log n)</math> (s. Landau-Symbole) ausgeführt werden.
Definition
Zusätzlich zu den Eigenschaften des binären Suchbaums hat jeder Knoten des Rot-Schwarz-Baums ein weiteres Attribut, genannt »Farbe«, das zwei Werte annehmen kann, genannt »rot« (RED) und »schwarz« (BLACK). Diese Einfärbung hat die folgenden zwei Forderungen zu {{#if:trim|erfüllen:<ref>Der Text folgt Ben Pfaff: An Introduction to Binary Search Trees and Balanced Trees. Zu anderen Varianten der Forderungen s. die Anmerkung NIL-Knoten.</ref>}}
Jeder Pfad von einem gegebenen Knoten zu einem seiner Nachfahren mit Ausgangsgrad ≤1, d. h. zu Blättern oder Halbblättern, im Folgenden kurz Pfadenden{{#if:trim|genannt,<ref>bei Ben Pfaffnon-branching node</ref>}} enthält die gleiche Anzahl schwarzer Knoten.
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Eine unmittelbare Folge aus den Forderungen ist, dass ein Halbblatt (bspw. der Knoten <math>\mathsf{1}</math> im Beispielbaum) schwarz und sein (einzelnes) Kind (der Knoten <math>\mathsf{6}</math>) rot sein muss, denn beide sind Pfadenden, und der Pfad zum Pfadende {{#if:trim|<math>\mathsf{1}</math>links}} ist um genau den Knoten <math>\mathsf{6}</math> kürzer.
Aus beiden Forderungen zusammen folgt ähnlich unmittelbar, dass ein schwarzer Knoten, der nicht die Wurzel ist, ein Geschwister hat.
Schwarzhöhe, Baumhöhe
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Die auf allen Pfaden von Pfadende zu Wurzel gleiche Anzahl <math>s</math> schwarzer Knoten wird die »Schwarzhöhe« ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}})<ref>Bei Ben Pfaff ist die Schwarzhöhe eines Knotens die einheitliche Anzahl schwarzer Knoten auf allen Pfaden zu den Pfadenden im von ihm gewurzelten Teilbaum.</ref><ref>Mehlhorn 2008 S. 155 färbt die Kanten rot/schwarz und zählt als Schwarztiefe ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) eines Knotens die Zahl der schwarzen Kanten von ihm zur Wurzel.</ref> genannt: des Baums, aber auch seiner Wurzel. Nach dieser Definition ist ein Knoten der Schwarzhöhe 0 ein rotes Blatt (dann ist seine Baumhöhe 1) wie bspw. die Knoten <math>\mathsf{6, 22, 27}</math> im Beispielbaum – oder ein unechter (und leerer) Knoten mit der Baumhöhe 0. In diesem Artikel sind schwarze Knoten echt (und Schlüssel tragend) und haben sowohl Schwarzhöhe wie Baumhöhe {{#if:trim|≥ 1.}}
Die roten Knoten <math>\mathsf{8, 17},</math> aber auch die schwarzen Knoten <math>\mathsf{1, 11, 15, 25},</math> haben Schwarzhöhe 1. Die schwarzen Knoten <math>\mathsf{1, 25}</math> haben Baumhöhe 2, die Knoten <math>\mathsf{11, 15}</math> Baumhöhe 1, und <math>\mathsf{1}</math> ist der einzige Knoten mit Ausgangsgrad 1, das einzige Halbblatt.
Durch die zwei Forderungen<ref name="Schwarzwurzel">Einige Autoren fordern noch, dass die Wurzel des Baums schwarz zu färben sei. Nicht jedoch z. B. Mehlhorn 2008 und Sedgewick. Tatsächlich ist diese Forderung ohne merkliche Bedeutung für das Laufzeitverhalten und stört die Rekursivität der Algorithmen etwas. Nun verletzt die Umfärbung einer roten Wurzel in schwarz keine der anderen Forderungen. In den Algorithmen für Einfügung und Löschung kann man jedoch mit einer Fallunterscheidung weniger auskommen, wenn man bei einem roten Knoten immer einen Elterknoten voraussetzen kann.</ref> wird die wichtigste Eigenschaft von Rot-Schwarz-Bäumen sichergestellt:
Ist <math>s</math> die Schwarzhöhe des Baums, dann gibt es wegen der {{#if:trim|Forderung S#=}} auf einem Pfad von der Wurzel zu einem Pfadende genau <math>s</math> schwarze Knoten, aber wegen der {{#if:trim|Forderung !RR}} höchstens einen roten Knoten mehr als schwarze, also insgesamt maximal <math>2s\!+\!1</math> Knoten.
Damit gilt für die Zahl <math>n</math> aller Knoten im Baum {{#if:trim|<math>2^s\!\!-\!\!1 \le n \le 2^{2s+1}\!\!-\!\!1</math>,}} so dass der Rot-Schwarz-Baum immer gut genug balanciert ist – auf jeden Fall so gut, dass das Verhältnis zwischen der Baumhöhe <math>h ,</math> für die <math>s \le h \le 2s\!+\!1</math> gilt, und dem Logarithmus der Knotenzahl <math>n</math> beschränkt bleibt. Diese logarithmische Beschränkung, die im Abschnitt Höhenbeweis formal bewiesen wird, ist aber gleichzeitig das informationstheoretische Optimum, d. h. es gibt keinen Binärbaum mit kleinerer maximaler Pfadlänge <math>h\!\!-\!\!1 \in \mathcal{O}(\log n).</math>
Bei den herausgehobenen Operationen Suchen, Einfügen und Löschen ist der auf einer Ebene des Baums anfallende Aufwand konstant. Also ist die Laufzeit höchstens proportional zur Anzahl der Knoten in einem Pfad, die wieder durch die Höhe limitiert ist, welche ihrerseits durch den Logarithmus der Knotenzahl limitiert ist.
NIL-Knoten
Dieser Artikel nimmt die im Artikel Binärer Suchbaum in dessen Abb. 1A aufgezeigte und bei vielen Baumstrukturen übliche Sichtweise ein, bei der die Knoten die Schlüssel tragen (knotenorientierte Speicherung), unabhängig davon, ob sie interne Knoten oder (interne) Blätter sind.
Sehr verbreitet in der Literatur über Rot-Schwarz-Bäume ist die nebenstehende und im Artikel Binärer Suchbaum in dessen Abb. 1B gezeigte Sichtweise, bei der – ebenfalls knotenorientiert – die die Schlüssel tragenden Knoten als intern angesehen werden, dem Baum aber zusätzliche Knoten, so genannte »NIL-Knoten«, als externe Blätter angeheftet sind, die an den Pfadenden für die (randlosen, „offenen“) Intervalle ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}} auch missing nodes<ref name="persistentRB">J. R. Driscoll, N. Sarnak, D. D. Sleator, R. E. Tarjan: Making Data Structures Persistent. In: Journal of Computer and System Sciences. Band 38, No. 1, 1989 (cs.cmu.edu)</ref>) zwischen den Schlüsseln stehen. Die Darstellung mit {{#if:trim|NIL-Knoten}} bringt das einseitige Pfadende {{#if:trim|<math>\mathsf{1}</math>links}} (links am Knoten {{#if:trim|<math>\mathsf{1}</math>)}} ähnlich deutlich heraus wie die paarigen Pfadenden an den Knoten <math>\mathsf{6,11,15,22,27}.</math> Völlig unabhängig von der grafischen Darstellung lassen sich die NIL-Knoten sowohl als unterschiedslose Nullzeiger wie als unterschiedslose Zeiger zu ein und demselben Wächterknoten implementieren.
Diese Option beim Zeigerwert wird zur Unterscheidung vom Nullzeiger im Text als {{#if:trim|NIL-Zeiger}} oder NIL und im Beispielcode als NIL kenntlich gemacht.
Werden die NIL-Knoten jedoch als individuelle Knoten aufgefasst, dann kommt für sie eine Forderung hinzu, so dass folgende drei Forderungen aufzustellen {{#if:trim|}}
NIL-Knoten sind schwarz.
Kinder eines roten Knotens sind schwarz.
Die Anzahlen schwarzer Knoten in einem Pfad von einem gegebenen Knoten zu jedem seiner NIL-Knoten sind gleich.
Diese Auffassung hat für das Verständnis der Sachverhalte vernachlässigbaren Nutzen und hat bei „naiver“ Implementierung eher einen ungünstigen Einfluss.<ref group="Anm.">Werden nämlich die NIL-Knoten als minimale Rot-Schwarz-Knoten implementiert, so brauchen sie Speicher für zwei Kindzeiger, ggf. einen Elterzeiger, das Feld für die Farbe und ein Erkennungsfeld für die Eigenschaft »NIL-Knoten«. Bei <math>n</math> Schlüssel tragenden Knoten braucht man <math>n+1</math> NIL-Knoten, womit sich der Rot-Schwarz-Overhead mehr als verdoppelt. Die Verknüpfungen der NIL-Knoten mit den Schlüssel tragenden Knoten (bspw. bei den Rotationen) sind überdies zu pflegen, so dass sich auch die Laufzeit verlängert. Das bedeutet im Ergebnis einen erheblichen Aufwand für das Aufzeichnen und Pflegen von Informationen, die für die nachfolgenden Algorithmen nicht gebraucht werden oder sich unmittelbar aus den anderen Informationen herleiten lassen.</ref>
Datenstruktur
Der nachfolgende Beispielcode, der in der Programmiersprache C formuliert ist, nimmt Bezug auf eine Deklaration des Baumknotens der folgenden Art:
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
<syntaxhighlight lang="c" line start="10">
struct RBnode // Knotenfeld
{
struct RBnode* child[2]; // Zeiger zu ≤2 Kindknoten dieses Knotens
// oder NIL: kein Kindknoten
byte color; // RED oder BLACK
... // Benutzer-Daten (z. B. Schlüssel, Wert)
} ;
#define LEFT 0
#define RIGHT 1
#define left child[LEFT] // -> linker Kindknoten
#define right child[RIGHT] // -> rechter Kindknoten
#define RED 0
#define BLACK 1
</syntaxhighlight>
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Befindet sich NIL an einer Stelle im Baum, an der syntaktisch ein Zeiger zu einem Knoten erwartet wird, so soll dies verabredungsgemäß das Fehlen eines Knotens (einen Null- oder auch Wächterknoten) an dieser Stelle des Baums bedeuten (der Knoten gilt als unecht). M. a. W.: der von einem NIL-Knoten gewurzelte Teilbaum ist leer und hat Schwarz- und Baumhöhe 0.
Sein Zeigerwert {{#if:trim|X == NIL}} ist weniger wichtig als der Ort, wo dieser Wert in der Datenstruktur steht. Dieser Ort gibt die graphentheoretischen Beziehungen zu den anderen Elementen des Baums an:
Ohne selbst einen Knoten zu repräsentieren, kann er in Kindesbeziehung, und damit auch in Geschwister- und Onkelbeziehung zu anderen Knoten stehen – niemals aber ein Elter sein. Seine Baumhöhe und Schwarzhöhe gelten beide als 0, und Farbe ist ihm auch nicht zugeordnet. Er hat einen Elter (wenn er die Wurzel ist, den Baum als logischen Elter), aber niemals einen Zeiger zum Elter. Funktionell entspricht er exakt dem NIL-Knoten, ohne dass ihm allerdings irgendeine Individualität zukäme.
Werden der Datenstruktur Rot-Schwarz-Baum Operationen zum Zugriff und zur Verwaltung beigegeben, so werden diese nur dann als zugehörig angesehen, wenn sie die Rot-Schwarz-Forderungen aufrechterhalten, indem sie insbesondere den Baum nach einer modifizierenden Operation überprüfen und wenn nötig reparieren. So erweitert wird die Datenstruktur zu einem Abstrakten Datentyp (ADT). Bei Suchbäumen gibt es im Englischen dafür auch die Charakterisierung als self-balancing tree.
Die Navigationsoperationen, das sind die verschiedenen Suchoperationen, das Traversieren, Aufsuchen erstes oder letztes Element und ähnliche, lassen den Baum unverändert (nur lesender Zugriff) und funktionieren im Prinzip auf jedem binären Suchbaum. Die dortigen Algorithmen und Angaben zur Komplexität gelten genauso für Rot-Schwarz-Bäume – mit der Präzisierung, dass die Höhe des Rot-Schwarz-Baums sich immer logarithmisch zur Anzahl der Knoten verhält.
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Das Suchen eines Elements (oder einer Lücke) anhand seines Schlüssels ist die wichtigste unter den Navigationsoperationen. (Die {{#if:trim|(Höhen-)Balancierung}} des Rot-Schwarz-Baums versucht, auf diese Operation hin zu optimieren.) Sie unterstützt eine Art direkten Zugriff (im Gegensatz zum sequentiellen Zugriff der Traversierung) und wird in der Regel als vorausgehende Operation bei den Modifikationsoperationen (Einfügen oder Löschen) zum Auffinden der betreffenden Stelle im Baum eingesetzt.
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Zeichenerklärung zu den Diagrammen
Die komplizierteren Einzelfälle der Einfüge- und Löschoperation werden weiter unten in Diagrammen skizziert.
Diese Diagramme stellen die für die Rebalancierung relevanten Beziehungen und Farben von Knoten heraus.
Ein Diagramm hat drei Spalten, wobei die linke Spalte die Überprüfung auf der untersten Stufe (ersten Iteration) eines Falles und die rechte die auf einer höheren Ebene desselben Falles beschreibt.
Die mittlere Spalte unterteilt die Transformation in mindestens zwei Abschnitte:
Der erste (und oberste, mit »Bedingung« beschriftete) Abschnitt zeigt die Ausgangskonstellation,
eine eventuelle »Rotation« folgt im nächsten Schritt,
danach eine »Umfärbung«, falls erforderlich.
Wenn der Baum dann noch nicht rebalanciert ist, zeigt der letzte (mit »Neuzuweisung« beschriftete) Abschnitt die Konstellation nach der erneuten Zuweisung des Problemknotens <math>\mathsf{N}</math> und der durch ihn bestimmten anderen Knoten. Diese Konstellation wird zur Weiterbehandlung an den in der Spalte {{#if:trim|→Test}} eingetragenen Fall übergeben.
Die erste Iterationsstufe spielt sich auf der tiefstmöglichen Ebene des Baums ab. Dies gilt für Einfügungen wie auch erstaunlicherweise für Löschungen. Da Schwarzhöhen und Baumhöhen auf sie bezogen werden können, soll diese Iterationsstufe auch einen Namen als Variable haben, und zwar den Buchstaben <math>j</math> mit dem ersten Wert {{#if:trim|}} mit der Folge, dass in den komplizierteren Fällen der Problemknoten <math>\mathsf{N}</math> in der ersten Iterationsstufe die Schwarzhöhe <math>j-1\!=\!0</math> und in den höheren <math>j-1 \ge 1</math> hat.
Obwohl die Konstellation der untersten Stufe <math>j\!=\!1</math> in denen der höheren im Prinzip enthalten ist, werden die sie betreffenden Diagramme um der größeren Deutlichkeit willen gebracht, und zwar ohne leere Teilbäume. Beim Löschen wird in der untersten Stufe das Pfadende, das einen schwarzen Knoten verloren hat (und das dem Problemknoten <math>\mathsf{N}</math> entspricht, der hier leer ist), durch einen blauen Pfeil nach links Datei:NilBlue.svg markiert.
Der alte Problemknoten (<math>\mathsf{N}</math> im Abschnitt »Bedingung« eines Diagramms) hat eine blaue Kontur; und wenn die Operation nicht beendet ist, dann auch der neue (im Abschnitt »Neuzuweisung«).
Das Symbol Datei:TriangleTop.svg stellt einen {{#if:trim|!RR-konform}} bei seinem Elter angebundenen Teilbaum dar. Er hat (außer in den Abschnitten »Neuzuweisung«) die Schwarzhöhe <math>j\!-\!\!1</math>. {{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}} Die Schwarzhöhe ist in der ersten Iterationsstufe 0; dann besteht der Teilbaum entweder
aus einem roten Blatt oder
er ist leer, also ein NIL-Knoten, wann der „Knoten“ auch als unecht gilt.
Das Dreieck Datei:TriangleSubtree.svg (ohne die kleine schwarze Kreisfläche an der Spitze) symbolisiert einen {{#if:trim|!RR-konform}} bei seinem Elter angebundenen Teilbaum, dessen Schwarzhöhe <math>j\!-\!\!2</math> ist. Seine Schwarzhöhe ist also um 1 niedriger als die der {{#if:trim|Datei:TriangleTop.svg-Teilbäume.}} In der ersten Iterationsstufe wäre die Schwarzhöhe negativ, was so zu interpretieren ist, dass schon sein Elter leer ist.
Zweimal Datei:RedOrBlackNode.svg in einem Diagramm oder auf einer Zeile der Zusammenschau bedeutet zweimal dieselbe Knotenfarbe, schwarz oder rot.
Operation Einfügen
Die ersten Aktionen beim Einfügen eines neuen Knotens <math>\mathsf{N}</math> in einen Rot-Schwarz-Baum unterscheiden sich nicht von denen eines allgemeinen binären Suchbaums: Der Zeiger zum neuen Knoten ersetzt einen {{#if:trim|NIL-Zeiger,}} der an einem Pfadende steht und als Indikator für das Fehlen entweder der Wurzel, falls der Baum leer ist, oder – bei einem Schlüssel tragenden Knoten <math>\mathsf{P}</math> – für die Abwesenheit des linken oder des rechten Kindes steht.
Im unten stehenden Beispielcode wird angenommen, dass diese Richtung, die das letzte Ungleich-Ergebnis einer Suche nach dem Schlüssel von <math>\mathsf{N}</math> reflektiert, im Funktionsparameterdir ∈ {LEFT,RIGHT} übergeben {{#if:trim|}}
Ferner wird angenommen, dass diese Suchfunktion den Stapel{{#if:trim|struct RBnode* Parents[]}} der Vorfahren von <math>\mathsf{P}</math> gebildet {{#if:trim|}} Der ebenfalls übergebene Zeiger struct RBnode** pParent zeigt in diesem Stapel auf den Zeiger des direkten Elters des einzufügenden Knotens. Wenn der Baum leer ist, ist pParent < &Parents[0] (und dir irrelevant).
Andernfalls ist Parents[0] gleich dem Zeiger auf den Wurzelknoten, pParent ≥ &Parents[0], und dir die Kindesrichtung des neuen Knotens.
Der Knoten <math>\mathsf{N}</math> wird zunächst rot gefärbt, damit die Schwarzhöhe des Baumes erhalten bleibt.<ref>Ben Pfaff bringt außer der Variante des Textes auch eine Variante mit einem ersten eingefügten Knoten schwarzer Farbe.</ref>
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Die darauf folgenden Aktionen überprüfen, ob die Rot-Schwarz-Balance im Baum eingehalten ist und stellen sie wenn erforderlich wieder her.
Diese Reparatur wird als »Rebalancierung« ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) bezeichnet.
Der neue Knoten <math>\mathsf{N}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) ist der gerade betrachtete Problemknoten. Sein Elter ist <math>\mathsf{P}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}). Der Großelter sei <math>\mathsf{G}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) und der Onkel (das Geschwister des Elters) <math>\mathsf{U}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) genannt.
Der Kopf der Funktion RBinsert1 zum Einfügen eines Knotens nach einer vorher erfolgten und nicht erfolgreichen Suchoperation könnte wie folgt aussehen:<ref group="Anm.">Die Kurzschreibweise mit dem Pfeil -> ist erklärt im Artikel Verbund (Datentyp).</ref>
<syntaxhighlight lang="c" line start="100">
void RBinsert1(
RBtree* T, // -> Rot-Schwarz-Baum
struct RBnode* Parents[], // Liste der -> Vorfahren
struct RBnode** pParent, // ->-> Nachbarknoten (wird zum Elterknoten von N)
struct RBnode* N, // -> einzufügender Knoten
byte dir) // (Kindes-)Richtung der Einfügung ∈ {LEFT, RIGHT}
{
struct RBnode* P; // -> Elterknoten von N
struct RBnode* G; // -> Elterknoten von P
struct RBnode* U; // -> Onkel von N
assert(N != NIL); // N muss Zeiger zu einem echten Knoten sein.
N->left = NIL; // Dieses Programm ist hierfür zuständig.
N->right = NIL;
N->color = RED;
if (pParent >= &Parents[0]) { // N ist nicht die neue Wurzel
P = *pParent; // -> Nachbarknoten (wird zum Elterknoten von N)
assert(P->child[dir] == NIL); // darf kein echter Knoten sein
P->child[dir] = N; // neuer Knoten als dir-Kind von P
goto Start_E; // Sprung in die Schleife
} // end_if
// pParent < &Parents[0]
T->root = N; // N ist die neue Wurzel des Baums T.
return; // Einfügung fertiggestellt
do { // Beginn der (do while)-Schleife:
// pParent >= &Parents[0]
// ===> Es gibt den Elter P von N.
// (<=> Bedingung_E1 trifft NICHT zu.)
N = G; // neuer Problemknoten (kann die Wurzel sein)
P = *pParent; // -> Elterknoten von N
Start_E:
</syntaxhighlight>
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Rebalancierung nach dem Einfügen: Die Schleife zur Überprüfung und Reparatur des Baums steigt vom roten Blatt <math>\mathsf{N}</math> zur Wurzel auf. Ihre Invariante ist:
Die Forderung !RR (»Nicht Rot-Rot«) gilt für alle Paare {{#if:trim|(Knoten←Elter)}} im Baum mit höchstens einer Ausnahme – beim Paar {{#if:trim|(<math>\mathsf{N}</math>←<math>\mathsf{P}</math>).}} Dies wird als »Rot-Verletzung« ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) angesehen, welche <math>\mathsf{N}</math> als Problemknoten definiert. Gilt !RR auch dort, entweder weil der Elter <math>\mathsf{P}</math> nicht existiert (Fall E1) oder schwarz ist (Fall E2), dann ist der Baum in Rot-Schwarz-Form.
Zu Beginn jedes Iterationsschritts ist der Problemknoten rot.
Die Forderung S#= (»Schwarz-Zahl Gleich«) gilt für den ganzen Baum.
Einfügen: Zusammenschau der Fälle
Die Zusammenschau bringt die Diagramme in einer auf die Farbkonstellation zugespitzten Form.
Einfügen: Zusammenschau der Fälle<ref>Diese Aufteilung findet sich u. a. bei Ben Pfaff.</ref>
Die möglichen Konstellationen lassen sich in sechs Fälle aufteilen, zu denen es Transformationen (enthaltend Rotation und/oder Umfärbung) gibt, die entweder auf der betrachteten Ebene oder über weitere Iterationsschritte auf höheren Ebenen zur Rebalancierung des Baumes führen.
In der nebenstehenden Tabelle wird ein Fall durch eine Zeile repräsentiert, die
die »Bedingung«, das ist die Konstellation, die den Fall definiert und die dem Abschnitt »Bedingung« des zum Fall gehörigen Diagramms – falls vorhanden – entspricht,
die Fallnummer,
die Konstellation nach Umfärbung und/oder Rotation und ggf. Zuweisung in der Spaltengruppe »Ergebnis«
enthält. Eine Konstellation besteht aus maximal 4 Gegebenheiten, und zwar aus den Farben der 3 Knoten <math>\mathsf{P}, \mathsf{G}</math> und <math>\mathsf{U}.</math>
Manche Fälle benötigen darüber hinaus eine vierte Angabe x zur Kindesrichtung von <math>\mathsf{N},</math> und zwar steht »o« (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) für eine Rechts-Rechts- oder Links-Links-Kindesrichtung von <math>\mathsf{N}</math> zu <math>\mathsf{P}</math> zu <math>\mathsf{G}</math> und »i« (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) für Rechts-Links oder Links-Rechts.
Eine Zelle ist leer gelassen, wenn es auf die Angabe nicht ankommt. So gilt bspw. der Beispielcode im Fall E3 für beide Kindesrichtungen von <math>\mathsf{N}</math> und das, obwohl das zugehörige Diagramm nur eine Kindesrichtung zeigt.
Die Transformation, deren Fallnummer in der Spalte {{#if:trim|Fall→}} verlinkt ist, transformiert die Eingabe-Konstellation (als Teilbaum dargestellt im mit »Bedingung« beschrifteten Abschnitt des zum Fall gehörigen Diagramms) in die mit »Rotation« und/oder »Umfärbung« beschrifteten Abschnitte. Steht ein Häkchen »✓« in der Spalte {{#if:trim|→Test}}, dann reflektiert die Gruppe »Ergebnis« diesen Stand, mit dem alle Rot-Schwarz-Forderungen erfüllt sind und mit dem die Einfügeoperation abgeschlossen ist.
Andernfalls steht dort die Fallnummer derjenigen Bedingung, auf die die transformierte und neu zugewiesene Konstellation zu testen ist, wobei die entsprechende Zuweisung, angegeben in der Spalte »Neuzuweisung« für den Problemknoten <math>\mathsf{N},</math> auch die Knoten <math>\mathsf{P}, \mathsf{G}, \mathsf{U}</math> sowie die Angabe x zur Kindesrichtung von <math>\mathsf{N}</math> neu zuordnet.
Die Spaltengruppe »Ergebnis« und der mit »Neuzuweisung« beschriftete Abschnitt des Diagramms zeigt die Konstellation nach der Zuweisung; Fragezeichen stehen in den Zellen, auf deren Angabe es in den nachfolgenden Tests ankommt.
In der Spalte {{#if:trim|→Test}} kommt der Eintrag »E1« nur bei der Transformation_E3 vor. Er bedeutet einen neuen Iterationsschritt, der mit dem Test auf die {{#if:trim|while-Bedingung_E1}} in der {{#if:trim|do while-Schleife}} beginnt, und zwar zwei Baumebenen (eine Schwarzebene <math>\Delta s</math>) näher an der Wurzel. Danach sind auch die Schwarzhöhen aller relevanten Teilbäume ≥ 1.
Da die Anzahl der Baumebenen mit der Höhe <math>h</math> übereinstimmt, können maximal <math>\tfrac h2</math> Iterationen vorkommen. Nun ist der Aufwand für eine Iteration beschränkt (d. h. in <math>\mathcal{O}(1)</math>) und damit der für die gesamte Einfügeoperation in <math>\mathcal{O}(h) = \mathcal{O}(\log n)</math> (mit oder ohne Suchen). Die reine Rebalancierung ist gemäß Abschnitt Durchschnittliche und amortisierte Laufzeitim Mittel und amortisiert in <math>\mathcal{O}(1).</math>
Bei einem Eintrag in der Spalte »Rotation« ist eine Rotation an der Transformation beteiligt. Man entnimmt der Tabelle sofort, dass bei einer Einfügeoperation maximal zwei Rotationen vorkommen, und zwar bei Fall E5 nach Fall E4. Denn nach einer Rotation kommt kein neuer Iterationsschritt – die Rotationen befinden sich endrekursiv am Ende der letzten ausgeführten Iteration.
Die Kindesrichtung von <math>\mathsf{N}</math> entscheidet insbesondere über die nachfolgenden Rotationsrichtungen.
Bei Fall E4 bestimmt sie aber auch die Auswahl des Falles:
Hat <math>\mathsf{P}</math> dieselbe Kindesrichtung wie <math>\mathsf{N},</math> dann ist die Angabe x (s. Zusammenschau) auf »o« für »außen«, sonst auf »i« für »innen« zu setzen.<ref name="gespiegelt">Eine andere Möglichkeit ist, den Schleifenrumpf der {{#if:trim|do while-Schleife}} in zwei Versionen hinzuschreiben, in einer für die Kindesrichtung links und in einer für rechts (so bspw. Ben Pfaff).</ref>
Im folgenden Beispielcode zur Operation Einfügen hält die Variable dir die Kindesrichtung des Knotens <math>\mathsf{P} .</math> Die Diagramme zeigen <math>\mathsf{P}</math> immer als linkes Kind.
Jeder Fall wird unter seiner Fallnummer erläutert und einschließlich Test (auf die ihn charakterisierende »Bedingung«) und Transformation durch ein Stück {{#if:trim|C-Code}} genau beschrieben.<ref name="rekursiv">Für die Vorteile der iterativen Programmierung hinsichtlich Platz und Zeit gegenüber einer rekursiven Programmierung s. a. Binärer Suchbaum#Iterative Programmierung. Darüber hinaus erzwingt erstere eine genauere Beachtung der Knoteneigenschaften.</ref>
Einfügen Fall E2
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der Elter <math>\mathsf{P}</math> des (roten) Problemknotens <math>\mathsf{N}</math> ist schwarz.
Nach Eintritt dieser Bedingung gibt es kein Paar {{#if:trim|(Knoten←Elter)}} (mehr), das die {{#if:trim|Forderung !RR}} verletzt. Ferner ist nach Voraussetzung (Schleifeninvariante) die Anzahl der schwarzen Knoten auf allen Pfaden dieselbe.
Damit ist der Baum ohne weitere Aktion in Rot-Schwarz-Form.
<syntaxhighlight lang="c" line start="160">
if (P->color == BLACK) // Bedingung_E2
// Transformation_E2:
return; // Einfügung fertiggestellt
// In den verbleibenden Fällen ist der Elter P rot.
if (pParent <= &Parents[0]) // Bedingung_E6
goto Fall_E6; // Der Elter P ist die Wurzel.
</syntaxhighlight>
Im Folgenden ist der Elter <math>\mathsf{P}</math> nicht die Wurzel, so dass der Großelter <math>\mathsf{G}</math> existiert.
Da <math>\mathsf{P}</math> rot ist, muss <math>\mathsf{G}</math> schwarz sein (so bei den folgenden Fällen E3, E4 und E5).
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Sowohl der Onkel <math>\mathsf{U}</math> als auch der Elter <math>\mathsf{P}</math> des Problemknotens <math>\mathsf{N}</math> ist rot.
Die Umfärbung von <math>\mathsf{P}</math> und <math>\mathsf{U}</math> in schwarz und von <math>\mathsf{G}</math> in rot stellt die {{#if:trim|Forderung !RR}} im Teilbaum mit Wurzel <math>\mathsf{G}</math> wieder her (und zwar bei beiden Kindesrichtungen von <math>\mathsf{N}</math>). Auf keinem Pfad ändert sich die Anzahl der schwarzen Knoten.
Durch diese Transformation_E3 wird die »Rot-Verletzung« um zwei Baum-Ebenen (eine Schwarz-Ebene) nach oben verschoben mit <math>\mathsf{G}</math> als neuem Problemknoten.<ref group="Anm." name="CAuswertung">Man beachte die in der Programmiersprache C festgelegte Art der Auswertung der
Disjunktion:
(X == NIL)
|| (X->color == BLACK)
bzw. der
Konjunktion:
(X != NIL)
&& (X->color == RED)
die im Fall X == NIL die zweite Bedingung X->color == BLACK resp. X->color == REDnicht auswertet, sondern sofort zum {{#if:trim|else-Zweig}} verzweigt. Im Beispielcode sind die entsprechenden Zeilen gelb hinterlegt.
Zusätzliche Bemerkung:
Die Schwarzhöhe 0 kommt nur in der ersten Iteration der Reparaturschleife vor. Bei den höheren Iterationsstufen <math>j\ge 2</math> ist die Schwarzhöhe aller besuchten Knoten X positiv, somit auch X != NIL – und dieser Teil der Abfrage kann weggelassen werden. Weiß man andererseits, dass die Schwarzhöhe eines Knotens 0 ist, dann weiß man auch, dass er, wenn er existiert, nur rot sein kann.
Damit lässt sich also die genannte Konjunktion/Disjunktion nach Herausziehen der ersten Iteration auf folgende Weise vereinfachen:
erste Iteration:
if (X != NIL)
(welches X->color == RED dort immer impliziert)
höhere Iteration:
if (X->color == RED)
(da dort immer X != NIL)
</ref>
<syntaxhighlight lang="c" highlight="4" line start="180">
G = *(--pParent); // Der Großelter G von N existiert.
dir = (P == G->right); // Kindesrichtung von P
U = G->child[1−dir]; // Onkel
if (U == NIL || U->color == BLACK)
goto Test_E4; // Der Onkel U ist schwarz.
// Bedingung_E3: N+P+U rot
// Transformation_E3:
// Umfärbung:
P->color = BLACK;
U->color = BLACK;
G->color = RED;
// Iteration zwei Ebenen (1 Schwarzebene) höher
} while (--pParent >= &Parents[0]);
// Ende der (do while)-Schleife
</syntaxhighlight>
Einfügen Fall E1
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der Problemknoten (hier: <math>\mathsf{G}</math>) ist die Wurzel.
Nach der obigen Transformation_E3 ist {{#if:trim|!RR}} (»Nicht Rot-Rot«) überall erfüllt, und der Baum in Rot-Schwarz-Form.
Allerdings könnte man eine rote Wurzel ohne Verletzung der Rot-Schwarz-Forderungen immer auf schwarz umfärben<ref name="Schwarzwurzel" /> – was z. B. den Test auf Bedingung_E6 und den Fall E6 überflüssig machen würde.
<syntaxhighlight lang="c" line start="210">
// Bedingung_E1: pParent < &Parents[0]
// ===> G ist die alte und neue Wurzel des Baums T.
return; // Einfügung fertiggestellt
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der Problemknoten <math>\mathsf{N}</math> hat keinen oder einen schwarzen Onkel und hat eine Kindesrichtung entgegengesetzt zu der seines roten Elters <math>\mathsf{P} ,</math> d. h., <math>\mathsf{N}</math> ist innerer Enkel.
Eine Rotation um <math>\mathsf{P}</math><ref group="Anm." name="PivotsInneresKind3">In der ersten Iterationsstufe <math>j\!=\!1</math> ist der Teilbaum <math>\mathsf{3}</math> leer. Wenn die Knoten mit Elterzeiger implementiert werden, dann ist nach der Rotation der Elterzeiger dieses Teilbaums bei allen Iterationsstufen außer der ersten anzupassen.</ref> vertauscht die Rollen von <math>\mathsf{N}</math> und <math>\mathsf{P},</math> und zwar eine Linksrotation, wenn <math>\mathsf{P}</math> linkes Kind (d. h. {{#if:trim|dir == LEFT)}} ist, sonst eine Rechtsrotation.
(Im Folgenden wird eine solche Rotation als {{#if:trim|dir-Rotation}} bezeichnet.)
Dadurch werden die Pfade des Teilbaums <math>\mathsf{2}</math> so verändert, dass sie durch einen Knoten mehr und die des Teilbaums <math>\mathsf{4}</math> durch einen weniger führen. Da jedoch in beiden Fällen rote Knoten den Unterschied ausmachen, ändert sich an der Anzahl der schwarzen Knoten nichts, womit die {{#if:trim|Forderung S#=}} und die Schleifeninvariante erfüllt bleibt.
Die Ergebniskonstellation entspricht der Eingangskonstellation des Falles E5 mit <math>\mathsf{P}</math> als dem neuen Problemknoten.
<syntaxhighlight lang="c" line start="230">
Test_E4:
if (N != P->child[dir]) {
// Bedingung_E4: N ist innerer Enkel
// && N+P rot && U schwarz
// Transformation_E4:
// dir-Rotation um P:
P->child[1−dir] = N->child[dir]; // NIL oder neuer Zeiger zu Teilbaum 3
N->child[dir] = P;
G->child[dir] = N;
// Zuweisung nach der Transformation:
N = P; // neuer Problemknoten (rot) und äußerer Enkel
P = G->child[dir]; // neuer Elter von N (rot)
}
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der Problemknoten <math>\mathsf{N}</math> hat keinen oder einen schwarzen Onkel.
Seine Kindesrichtung ist dieselbe wie die von <math>\mathsf{P} ,</math> d. h., <math>\mathsf{N}</math> ist äußerer Enkel.
Eine (nicht-dir)-Rotation<ref group="Anm." name="eckig" /> um <math>\mathsf{G}</math><ref group="Anm." name="PivotsInneresKind3" /> macht <math>\mathsf{P}</math> zum Elter sowohl von <math>\mathsf{N}</math> als auch von <math>\mathsf{G}.</math> Da <math>\mathsf{P}</math> rot war, muss wegen {{#if:trim|Forderung !RR}} <math>\mathsf{G}</math> schwarz sein. Invertiert man nun die Farben von <math>\mathsf{G}</math> und <math>\mathsf{P},</math> so ist in dem dadurch entstehenden Baum die {{#if:trim|Forderung !RR}} wieder erfüllt. Die {{#if:trim|Forderung S#=}} bleibt ebenfalls erfüllt, da alle Pfade, die durch einen dieser drei Knoten laufen, vorher durch <math>\mathsf{G}</math> liefen und nun alle durch <math>\mathsf{P}</math> laufen, der inzwischen – wie <math>\mathsf{G}</math> vor der Transformation – der einzige schwarze der drei Knoten ist.
<syntaxhighlight lang="c" line start="270">
// Bedingung_E5: N ist äußerer Enkel && N+P rot && U schwarz
// Transformation_E5:
// (nicht-dir)-Rotation um G:
G->child[dir] = P->child[1−dir]; // NIL oder neuer Zeiger zu Teilbaum 3
P->child[1−dir] = G;
if (--pParent >= &Parents[0]) {
// Ersetze G bei seinem Elter durch P:
U = *pParent;
U->child[G == U->right] = P;
}
else // G war die Wurzel
T->root = P; // P ist die neue Wurzel
// Umfärbung:
P->color = BLACK;
G->color = RED;
return; // Einfügung fertiggestellt
</syntaxhighlight>
Einfügen Fall E6
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der rote Elter <math>\mathsf{P}</math> des Problemknotens <math>\mathsf{N}</math> ist gleichzeitig die Wurzel des Baums.
Eine Umfärbung von <math>\mathsf{P}</math> in schwarz stellt die {{#if:trim|Forderung !RR}} im ganzen Baum wieder her. Auf jedem Pfad erhöht sich die Anzahl der schwarzen Knoten um 1.
<syntaxhighlight lang="c" line start="310">
Fall_E6:
// Der Elter P von N ist die Wurzel des Baums.
// Transformation_E6:
// Umfärbung:
P->color = BLACK;
// Da P rot war,
// erhöht sich die Schwarzhöhe des Baumes um 1.
return; // Einfügung fertiggestellt
} // Ende RBinsert1
</syntaxhighlight>
Operation Löschen
Das Löschen eines Knotens, sein Name sei <math>\mathsf{N}</math>, aus einem Rot-Schwarz-Baum erfolgt wie bei einem binären Suchbaum.
Hat der Knoten <math>\mathsf{N}</math> ein oder kein Kind, dann kann die folgende Überlegung übersprungen werden.
Hat <math>\mathsf{N}</math> aber zwei (echte) Kinder, dann nimmt man, ohne letztlich die Sortierreihenfolge zu stören, als effektiv zu löschenden Knoten seinen hinsichtlich Sortierreihenfolge rechten (resp. linken) Nachbarknoten, welcher kein linkes (resp. kein rechtes) Kind haben kann.<ref>Diese Vorgehensweise wurde zum ersten Mal von T. Hibbard in 1962 vorgeschlagen, zitiert nach Robert Sedgewick, Kevin Wayne: Algorithms Fourth Edition. Pearson Education, 2011, ISBN 978-0-321-57351-3, S. 410 (englisch, abgerufen am 25. März 2018)</ref> Ein solcher Nachbarknoten ist ein Blatt oder Halbblatt, das tiefster linker Nachkomme des rechten Kindes (resp. tiefster rechter Nachkomme des linken Kindes) von <math>\mathsf{N}</math> ist.<ref group="Anm.">Im Fall, dass die RB-Software die Benutzerdaten nicht kennt und trotzdem generisch sein muss, sind an Stelle der Benutzerdaten die RB-Daten zu vertauschen, so dass der (wirklich zu löschende) Knoten (mit dem Schlüssel und den Benutzerdaten von <math>\mathsf{N}</math>) maximal ein RB-Kind hat. Bei dieser Vorgehensweise bleiben auch bspw. Zeiger auf gültige Knoten gültig.</ref>
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Geht man so vor, dann kann man sich darauf beschränken, dass das initiale <math>\mathsf{N}</math> maximal ein Kind hat.
Mit <math>\mathsf{P}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) sei der Elter von <math>\mathsf{N}</math> und mit dir (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) die Kindesrichtung von <math>\mathsf{N}</math> (rechtes oder linkes Kind von {{#if:trim|<math>\mathsf{P}</math>)}} bezeichnet.
Einfache Fälle
Den Fall, dass <math>\mathsf{N}</math> die kinderlose Wurzel ist, erledigt man sofort, indem man diese durch einen NIL-Knoten ersetzt.
Hat <math>\mathsf{N}</math> genau ein Kind, so ist dieses zwangsläufig rot.
Man färbt es schwarz und macht es an der Kindesrichtung dir zum Kind von <math>\mathsf{P}.</math> Damit ist <math>\mathsf{N}</math> aus dem Baum entfernt, und die {{#if:trim|Forderung !RR}} (»Nicht Rot-Rot«) bleibt erfüllt. Ferner verlaufen nun alle Pfade, die durch den gelöschten schwarzen Knoten verliefen, durch sein nunmehr schwarzes Kind, so dass auch die {{#if:trim|Forderung S#=}} (»Schwarz-Zahl Gleich«) erfüllt bleibt.
Hat <math>\mathsf{N}</math> kein Kind und ist rot, so wird es bei seinem (schwarzen) Elter einfach durch einen NIL-Knoten ersetzt.
Und da alle Pfade, die durch den roten Knoten verliefen, nach seiner Entfernung aus dem Baum durch einen roten Knoten weniger verlaufen, ändert sich an der Anzahl der schwarzen Knoten nichts, womit die {{#if:trim|Forderung S#=}} erfüllt bleibt.
Das Löschen eines schwarzen Blattes
Komplizierter sind die Fälle, wo <math>\mathsf{N}</math> nicht die Wurzel ist, kein Kind hat und schwarz, also ein schwarzes Blatt ist.
Als solches hat <math>\mathsf{N}</math> ein (echtes) Geschwister, das {{#if:trim|(nicht-dir)-Kind}}<ref group="Anm." name="eckig">Wegen der GleichsetzungenLEFT ≡ 0 und RIGHT ≡ 1 spiegelt die logische Negation
nicht-dir ≡ !dir ≡ 1−dir
die Richtung LEFT <math>\longleftrightarrow</math> RIGHT. Dabei ist dir = (N == P->right) die Kindesrichtung von <math>\mathsf{N}</math>.
Im Kontext der Farben spricht man bei Zuweisungen à la RED :≡ !BLACK und BLACK :≡ !RED von »Invertierung«.</ref> von <math>\mathsf{P},</math> welches mit <math>\mathsf{S}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) bezeichnet sei.
Dessen (möglicherweise leeres) {{#if:trim|dir-Kind}} <math>\mathsf{C}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) ist der »nahe« Neffe von <math>\mathsf{N}</math> mit derselben Kindesrichtung; das andere (ebenfalls möglicherweise leere) Kind <math>\mathsf{D}</math> (wie {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) der »ferne« Neffe.
Nach der Ersetzung von <math>\mathsf{N}</math> bei <math>\mathsf{P}</math> durch NIL sei diese Stelle im Baum weiterhin durch <math>\mathsf{N}</math> bezeichnet. Die durch <math>\mathsf{N}</math> führenden Pfade enthalten einen schwarzen Knoten weniger als vorher, also einen weniger als die ebenfalls durch <math>\mathsf{P}</math> führenden Pfade durch das Geschwister <math>\mathsf{S}</math>. Somit ergibt sich bei <math>\mathsf{P}</math> eine Verletzung der {{#if:trim|Forderung S#=,}} eine so genannte »Schwarz-Verletzung« ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}). Der Knoten <math>\mathsf{N}</math> wird deshalb als Problemknoten aufgefasst.
Im nachfolgenden Beispielcode ist angenommen, dass eine vorausgehende Suchfunktion, die den zu löschenden Knoten <math>\mathsf{N}</math> lokalisiert hat, den Stapel{{#if:trim|struct RBnode* Parents[]}} von dessen Vorfahren gebildet<ref name="Stapel" /> und dessen Zeiger an die Löschfunktion übergeben hat.
Ist <math>\mathsf{N}</math> die Wurzel, dann zeigt der ebenfalls übergebene Zeiger struct RBnode** pParent vor diesen Stapel (pParent < &Parents[0]).
Andernfalls zeigt er in diesem Stapel auf den Zeiger zum direkten Elter des zu löschenden Knotens, und es ist pParent ≥ &Parents[0] sowie Parents[0] gleich dem Zeiger auf den Wurzelknoten.
Eine andere, häufig verwendete Art, den Aufstieg im Baum zu unterstützen, ist die Implementierung eines Elterzeigers pro Knoten.
Der Kopf einer Funktion RBdelete2 zum Löschen eines schwarzen Knotens <math>\mathsf{N}</math> ohne Kind, der nicht die Wurzel ist, könnte dann wie folgt aussehen:<ref group="Anm.">Um der Kürze der Aufschreibung willen wird im Beispielcode einige Male goto verwendet. Es ist leicht, dies durch Verdoppelung des Codes zu vermeiden.</ref>
<syntaxhighlight lang="c" line start="500">
void RBdelete2(
RBtree* T, // -> Rot-Schwarz-Baum
struct RBnode* Parents[], // Liste der -> Vorfahren
struct RBnode** pParent, // ->-> Elterknoten von N
struct RBnode* N) // -> zu löschender Knoten, hier: schwarz
{
byte dir; // Richtung ∈ {LEFT, RIGHT}
struct RBnode* P; // -> Elterknoten von N
struct RBnode* S; // -> Geschwisterknoten von N
struct RBnode* C; // -> naher Neffe
struct RBnode* D; // -> ferner Neffe
// Für RBdelete2 muss erfüllt sein:
assert(N != NIL);
assert(pParent >= Parents[0]); // N ist nicht die Wurzel.
P = *pParent; // Elter von N
dir = (N == P->right); // Kindesrichtung von N:
// Wenn nicht right, dann left:
assert(N != P->right ? N == P->left : true);
assert(N->color == BLACK);
assert(N->left == NIL);
assert(N->right == NIL);
// Ersetze N bei seinem Elter P durch NIL:
P->child[dir] = NIL;
goto Start_L; // Sprung in die Schleife
</syntaxhighlight>
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Rebalancierung nach dem Löschen: Die Schleife zur Überprüfung und Reparatur des Baums steigt vom Problemknoten <math>\mathsf{N},</math> d. i. zunächst das neue Pfadende N = NIL, zur Wurzel auf. Ihre Invariante ist:
Die Anzahl der schwarzen Knoten in den Pfaden durch <math>\mathsf{N}</math> ist um eins kleiner als vorher, wogegen sie in den anderen Pfaden ungeändert ist. (Dies kann durch Abzählen der schwarzen Kreisflächen in der mit »Bedingung« beschrifteten Ausgangskonstellation eines Diagramms nachvollzogen werden.) Das bedeutet, dass, wenn es andere Pfade gibt, beim Elter von <math>\mathsf{N}</math> eine »Schwarz-Verletzung« vorliegt.
<math>\mathsf{N}</math> hat die Schwarzhöhe <math>j\!-\!\!1</math> und ist in der ersten Iterationsstufe leer (sein Zeigerwert NIL). Dieses Pfadende wird in den Diagrammen der ersten Iterationsstufe durch einen blauen Pfeil nach links Datei:NilBlue.svg symbolisiert. In den höheren Iterationsstufen ist <math>\mathsf{N}</math> ein echter schwarzer Knoten und wird in den Diagrammen mit der blauen Kontur des Problemknotens gebracht.<ref group="Anm.">Zu Beginn jedes Iterationsschritts hat das Geschwister <math>\mathsf{S}</math> die Schwarzhöhe {{#if:trim|<math>j</math>.}}</ref>
Die Forderung !RR (»Nicht Rot-Rot«) ist überall erfüllt.
Die Zusammenschau bringt die Diagramme in einer auf die Farbkonstellation zugespitzten Form.
Die möglichen Farbkonstellationen lassen sich in sechs Fälle gruppieren, zu denen es Transformationen (enthaltend Rotation und/oder Umfärbung) gibt, die entweder auf der betrachteten Ebene oder über weitere Iterationsschritte auf höheren Ebenen zu einer Rebalancierung des Baumes führen.
In der nebenstehenden Tabelle wird ein Fall durch eine Zeile repräsentiert, die
die »Bedingung«, das ist die Konstellation, die den Fall definiert,
die Fallnummer,
die Konstellation nach Transformation und ggf. Zuweisung in der Spaltengruppe »Ergebnis«
enthält. Eine Konstellation (4 Spalten) besteht aus den Farben der 4 Knoten <math>\mathsf{P}, \mathsf{C}, \mathsf{S}, \mathsf{D}.</math>
Eine Zelle ist leer gelassen, wenn es auf die Angabe nicht ankommt. Zweimal Datei:RedOrBlackNode.svg auf einer Zeile bedeutet zweimal dieselbe Knotenfarbe, schwarz oder rot.
Die Konstellationen in der Gruppe »Bedingung« genügen der Schleifeninvariante, und ihre logische Summe schöpft diese zu 100 % aus.
Sie sind im mit »Bedingung« beschrifteten, dem obersten Abschnitt des zum Fall gehörenden Diagramms nochmals als Teilbaum skizziert.
Die Transformation, deren Fallnummer in der Spalte {{#if:trim|Fall→}} verlinkt ist, transformiert die Eingabe in Konstellationen, die in den mit »Rotation« und/oder »Umfärbung« beschrifteten Abschnitten des Diagramms des Falles dargestellt sind.
Steht ein Häkchen »✓« in der Spalte {{#if:trim|→Test}}, dann reflektiert die Gruppe »Ergebnis« den Endstand, und die Löschoperation ist durch die Transformation abgeschlossen.
Andernfalls steht dort die Fallnummer derjenigen Bedingung, auf die die transformierte und neu zugewiesene Konstellation zu testen ist, wobei die entsprechende Zuweisung, angegeben in der Spalte »Neuzuweisung« für den Problemknoten <math>\mathsf{N},</math> auch die Knoten <math>\mathsf{P}, \mathsf{C}, \mathsf{S}, \mathsf{D}</math> eindeutig bestimmt.
Die Spaltengruppe »Ergebnis« und der mit »Neuzuweisung« beschriftete Abschnitt des Diagramms zeigt die Konstellation nach der Zuweisung; Fragezeichen stehen bei den Knoten, auf deren Farbe es in den nachfolgenden Tests ankommt.
Der Eintrag »L1« kommt nur bei der Transformation_L2 vor und bedeutet einen neuen Iterationsschritt auf der um 1 höheren Ebene im Baum (gleichzeitig eine Schwarzebene <math>\Delta s</math>), beginnend mit dem Test auf die Bedingung_L1. Danach sind die Schwarzhöhen aller Teilbäume ≥ 1.
Die Anzahl der Ebenen stimmt mit der Höhe <math>h</math> überein, so dass höchstens <math>h</math> Iterationen vorkommen können. Nun ist der Aufwand für eine Iteration beschränkt (d. h. in <math>\mathcal{O}(1)</math>) und damit der für die gesamte Löschoperation in <math>\mathcal{O}(h) = \mathcal{O}(\log n).</math> Gemäß Abschnitt Durchschnittliche und amortisierte Laufzeit ist der Rebalancierungsaufwand im Mittel sogar konstant.
Bei einem Eintrag in der Spalte Rotation ist eine Rotation an der Transformation beteiligt. Und die Tabelle zeigt, dass bei einer Löschung maximal drei Rotationen (von Fall L3 über L5 zu L6) erforderlich sind. Denn nach einer Rotation kommt kein neuer Iterationsschritt – die Rotationen befinden sich endrekursiv am Ende der letzten Iteration.
Die Kindesrichtung des Problemknotens <math>\mathsf{N}</math> bestimmt sowohl die nachfolgenden Rotationsrichtungen wie die Kindesrichtungen der Neffen <math>\mathsf{C}</math> und <math>\mathsf{D} .</math> Im Beispielcode wird dies mithilfe der Variablen dir{{#if:trim|}}
Die Diagramme bei den Fällen zeigen jedoch <math>\mathsf{N}</math> immer links von <math>\mathsf{P}</math>.
<syntaxhighlight lang="c" highlight="14,17" line start="550">
// Beginn der (do while)-Schleife:
do {
// pParent >= &Parents[0]
// ===> Bedingung_L1 trifft NICHT zu
N = P; // neuer Problemknoten (kann die Wurzel sein)
P = *pParent;
dir = (N == P->right); // Kindesrichtung von N
Start_L:
S = P->child[1−dir]; // Geschwister von N
if (S->color == RED)
goto Fall_L3; // Bedingung_L3: S rot ===> P+C+D schwarz
// S ist schwarz:
D = S->child[1−dir]; // ferner Neffe
if (D != NIL && D->color == RED)
goto Fall_L6; // der ferne Neffe D ist rot
C = S->child[ dir]; // naher Neffe
if (C != NIL && C->color == RED)
goto Fall_L5; // der nahe Neffe C ist rot
// Beide Neffen sind == NIL (erste Iteration) oder schwarz (später).
if (P->color == RED)
goto Fall_L4; // P rot && C+S+D schwarz <==> Bedingung_L4
</syntaxhighlight>
Jeder Fall wird unter seiner Fallnummer erläutert und einschließlich Test (auf die ihn charakterisierende Bedingung) und Transformation durch ein Stück C-Code genau beschrieben.<ref name="rekursiv" /><ref group="Anm." name="CAuswertung" />
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der Elter <math>\mathsf{P}</math> des Problemknotens <math>\mathsf{N}</math> und das Geschwister <math>\mathsf{S}</math> sind schwarz, ebenso die Kinder <math>\mathsf{C}</math> und <math>\mathsf{D}</math> von <math>\mathsf{S},</math> falls sie existieren.
Die Umfärbung des Knotens <math>\mathsf{S}</math> von schwarz in rot vermindert in allen Pfaden, die durch <math>\mathsf{S}</math> führen, die Zahl <math>s</math> der schwarzen Knoten um 1 auf {{#if:trim|<math>s\!\!-\!\!1</math>.}}
Das betrifft genau die Pfade, die durch <math>\mathsf{P},</math> aber nicht durch <math>\mathsf{N}</math> führen, welch letztere Pfade vorher schon genau <math>s\!\!-\!\!1</math> schwarze Knoten enthalten haben.
Damit sind es <math>s\!\!-\!\!1</math> schwarze Knoten in den Pfaden, die durch <math>\mathsf{P}</math> führen, und <math>s</math> in denen, die nicht durch <math>\mathsf{P}</math> führen – wenn es denn solche noch gibt.
Somit wird die erste Bedingung der Schleifeninvariante mit nunmehr <math>\mathsf{P}</math> als Problemknoten erfüllt.
<syntaxhighlight lang="c" line start="590">
// Bedingung_L2: P+C+S+D schwarz
// Transformation_L2:
// Umfärbung:
S->color = RED;
// Fortsetzung der Überprüfung des Baums
// eine Ebene höher durch Testen auf die
// Bedingung_L1:
} while (--pParent >= &Parents[0]);
// Ende der (do while)-Schleife.
</syntaxhighlight>
Löschen Fall L1
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der Problemknoten (hier: <math>\mathsf{P}</math>) ist die Wurzel.
In diesem Fall ist man fertig, da überhaupt alle Pfade durch <math>\mathsf{P}</math> führen (und somit alle Pfade einen schwarzen Knoten weniger enthalten als vorher).
Die Schwarzhöhe des Baumes verringert sich dabei um 1.
<syntaxhighlight lang="c" line start="620">
// Bedingung_L1: pParent < &Parents[0]
// ===> P ist die alte und neue Wurzel des Baums T.
return; // Löschung fertiggestellt
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Das Geschwister <math>\mathsf{S}</math> des Problemknotens <math>\mathsf{N}</math> ist rot.
Eine Rotation um <math>\mathsf{P}</math><ref group="Anm.">Wenn die Knoten mit Elterzeiger implementiert werden, dann ist der Elterzeiger des Knotens <math>\mathsf{C}</math> in allen Iterationen anzupassen, da die Schwarzhöhe von <math>\mathsf{C}</math> auch in der ersten Iteration ≥ 1 ist.</ref> macht <math>\mathsf{S}</math> zum Großelter von <math>\mathsf{N},</math> und zwar eine Linksrotation, wenn <math>\mathsf{N}</math> linkes Kind (d. h. {{#if:trim|dir == LEFT)}} ist, sonst eine Rechtsrotation.
(Im Folgenden wird eine solche Rotation als {{#if:trim|dir-Rotation}} bezeichnet.)
Danach invertiert man die Farben von <math>\mathsf{P}</math> und <math>\mathsf{S}.</math> Alle Pfade haben weiterhin dieselbe Anzahl an schwarzen Knoten, aber <math>\mathsf{N}</math> hat nun ein schwarzes Geschwister, <math>\mathsf{C},</math> und einen roten Elter, <math>\mathsf{P},</math> weswegen man nun zu Fall L4, L5 oder L6 weitergehen kann – mit <math>\mathsf{N}</math> als altem und neuem {{#if:trim|}}
<syntaxhighlight lang="c" highlight="21,24" line start="650">
Fall_L3:
// Bedingung_L3: S rot && P+C+D schwarz
// Transformation_L3:
C = S->child[dir]; // aufbewahren
// dir-Rotation um P:
P->child[1−dir] = C; // neuer Elter von C
S->child[dir] = P;
if (--pParent >= &Parents[0]) {
// Ersetze P bei seinem Elter durch S:
D = *pParent;
D->child[P == D->right] = S;
}
else // P war die Wurzel
T->root = S; // S ist die neue Wurzel
// Umfärbung:
S->color = BLACK;
P->color = RED;
// Zuweisung nach der Transformation:
S = C; // neues Geschwister von N (schwarz)
D = S->child[1−dir]; // ferner Neffe
if (D != NIL && D->color == RED)
goto Fall_L6; // der ferne Neffe D ist rot
C = S->child[ dir]; // naher Neffe
if (C != NIL && C->color == RED)
goto Fall_L5; // der nahe Neffe C ist rot
// Beide Neffen sind == NIL (erste Iteration)
// oder schwarz (später).
// Also P rot && C+S+D schwarz <==> Bedingung_L4.
// Das ist Fall_L4:
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Der Elter <math>\mathsf{P}</math> von <math>\mathsf{N}</math> ist rot, aber sowohl das Geschwister <math>\mathsf{S}</math> des Problemknotens <math>\mathsf{N}</math> als auch dessen Kinder <math>\mathsf{C}</math> und <math>\mathsf{D}</math> sind schwarz, falls sie existieren.
Eine Invertierung der Farben von <math>\mathsf{P}</math> und <math>\mathsf{S}</math> lässt die Anzahl der schwarzen Knoten auf den Pfaden, die durch <math>\mathsf{S}</math> laufen, unverändert, fügt aber auf allen Pfaden durch <math>\mathsf{N}</math> einen schwarzen Knoten hinzu und gleicht damit den fehlenden schwarzen Knoten auf diesen Pfaden aus.
<syntaxhighlight lang="c" line start="710">
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Das Geschwister <math>\mathsf{S}</math> von <math>\mathsf{N}</math> ist schwarz, der nahe Neffe <math>\mathsf{C}</math> rot, während der ferne Neffe <math>\mathsf{D},</math> falls er existiert, schwarz ist.
Der im Diagramm rot-schwarz dargestellte Knoten <math>\mathsf{P}</math> behält seine Farbe, rot oder schwarz.
Eine (nicht-dir)-Rotation um <math>\mathsf{S}</math><ref group="Anm." name="PivotsInneresKind3" /> macht <math>\mathsf{C}</math> zum Elter von <math>\mathsf{S}</math> und zugleich zum Geschwister von <math>\mathsf{N}.</math> Danach invertiert man die Farben von <math>\mathsf{S}</math> und <math>\mathsf{C}.</math>
Dadurch werden die Pfade des Teilbaums <math>\mathsf{2}</math> so verändert, dass sie durch einen roten Knoten weniger und die Pfade durch den Knoten <math>\mathsf{D}</math> durch einen mehr führen. Die Zahl der schwarzen Knoten auf diesen Pfaden bleibt jedoch gleich.
Ferner hat nun <math>\mathsf{N}</math> ein schwarzes Geschwister, <math>\mathsf{C},</math> dessen fernes Kind, <math>\mathsf{S},</math> rot ist, womit man zum Fall L6 weitergehen kann. Weder <math>\mathsf{N}</math> noch <math>\mathsf{P}</math> noch die Schwarzhöhe werden durch diese Transformation beeinflusst.
<syntaxhighlight lang="c" line start="740">
Fall_L5:
// Bedingung_L5: S+D schwarz && C rot
// Transformation_L5:
// (nicht-dir)-Rotation um S:
S->child[dir] = C->child[1−dir]; // NIL oder neuer Zeiger zu Teilbaum 3
C->child[1−dir] = S;
// dadurch wird S zum fernen Neffen von N
P->child[1−dir] = C;
// Umfärbung:
C->color = BLACK;
S->color = RED;
// Zuweisung nach der Transformation:
D = S; // neuer ferner Neffe
S = C; // neues Geschwister von N
// Weiter zu Fall_L6:
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Die Farbe des Elters <math>\mathsf{P}</math> ist beliebig.
Das Geschwister <math>\mathsf{S}</math> von <math>\mathsf{N}</math> ist schwarz, und der ferne Neffe <math>\mathsf{D}</math> von <math>\mathsf{N}</math> ist rot.
Eine dir-Rotation um <math>\mathsf{P}</math><ref group="Anm." name="PivotsInneresKind3" /> macht <math>\mathsf{S}</math> zum Großelter von <math>\mathsf{N}.</math>
Nun reicht es, <math>\mathsf{S}</math> die Farbe von <math>\mathsf{P}</math> zu geben und <math>\mathsf{P}</math> sowie <math>\mathsf{D}</math> schwarz zu färben. <math>\mathsf{N}</math> hat immer noch dieselbe Farbe, wodurch die {{#if:trim|Forderung !RR}} erfüllt bleibt. Aber <math>\mathsf{N}</math> hat nun einen zusätzlichen schwarzen Vorfahren: Falls <math>\mathsf{P}</math> vor der Transformation noch nicht schwarz war, so ist er nach der Transformation schwarz, und falls <math>\mathsf{P}</math> schon schwarz war, so hat <math>\mathsf{N}</math> nun <math>\mathsf{S}</math> als schwarzen Großelter, weswegen die Pfade, die durch <math>\mathsf{N}</math> führen, nun einen zusätzlichen schwarzen Knoten passieren.
Bei den Pfaden, die sich ändern und die nicht durch <math>\mathsf{N}</math> führen, gibt es zwei Möglichkeiten:
Der Pfad führt durch die beliebig eingefärbte Wurzel des Teilbaums <math>\mathsf{3}</math>, die das neue Geschwister von <math>\mathsf{N}</math> ist. Dann muss der Pfad sowohl vor als auch nach der Transformation durch <math>\mathsf{S}</math> und <math>\mathsf{P}</math> führen. Da die beiden Knoten aber nur ihre Farben vertauscht haben, ändert sich an der Anzahl der schwarzen Knoten auf dem Pfad nichts.
Der Pfad führt durch den neuen Onkel <math>\mathsf{D}</math> von <math>\mathsf{N},</math> welcher das {{#if:trim|(nicht-dir)-Kind}} des Geschwisters <math>\mathsf{S}</math> ist. In diesem Fall führte der Pfad vorher durch <math>\mathsf{D}, \mathsf{S}</math> und {{#if:trim|<math>\mathsf{P}</math>.}} Nach der Transformation führt er aber nur noch durch <math>\mathsf{D},</math> der von Rot auf Schwarz (die vorige Farbe von <math>\mathsf{S}</math>) umgefärbt wurde, und den Knoten <math>\mathsf{S},</math> welcher die Farbe von <math>\mathsf{P}</math> angenommen hat. Somit ändert sich die Anzahl der schwarzen Knoten eines solchen Pfades nicht.
Da die Anzahl der schwarzen Knoten in den Pfaden, die durch <math>\mathsf{N}</math> führen, sich um 1 erhöht, und in denen, die nicht durch <math>\mathsf{N}</math> führen, sich nicht ändert, ist die {{#if:trim|Forderung S#=}} wiederhergestellt.
<syntaxhighlight lang="c" line start="770">
Fall_L6:
// Bedingung_L6: S schwarz && D rot
// Transformation_L6:
// dir-Rotation um P:
P->child[1−dir] = S->child[dir]; // NIL oder neuer Zeiger zu Teilbaum 3
S->child[dir] = P;
if (--pParent >= &Parents[0]) // P war nicht die Wurzel
{
// Ersetze P bei seinem Elter durch S:
N = *pParent;
N->child[P == N->right] = S;
}
else
T->root = S; // S ist die neue Wurzel
// Umfärbung:
S->color = P->color;
P->color = BLACK;
D->color = BLACK;
return; // Löschung fertiggestellt
} // Ende RBdelete2
</syntaxhighlight>
Operationen mit ganzen Rot-Schwarz-Bäumen
Die folgenden zwei Operationen haben ganze Rot-Schwarz-Bäume als Ein- und Ausgabeoperanden. Sie gehören nicht zum Standardsatz und fehlen in manchen Implementierungen. Es soll aber hier gezeigt werden, dass auch sie mit logarithmischem Aufwand durchgeführt werden können.
Die Operation JOIN(TL,k,TR) verkettet (konkateniert) (englisch: join oder concat) zwei Rot-Schwarz-Bäume über einen Einzelschlüssel mit logarithmischem Aufwand.<ref name="join-tarjan">{{#invoke:Vorlage:Literatur|f}}</ref> Für die Sortierfolge müssen natürlich alle Schlüssel des ersten Baums TL dem Einzelschlüssel k und dieser allen Schlüsseln des zweiten TR vorangehen.<ref name="Knuth-S474">Donald E. Knuth: The Art of Computer Programming, Band 3, Sorting and Searching, 2. Auflage, Addison-Wesley, 1998, S. 474</ref>
Die In-Order-Traversierung des verketteten Baums entspricht der Nacheinanderausführung der Traversierung des ersten Baums, gefolgt von der des einzelnen Knotens, gefolgt von der des zweiten Baums.
Skizze des Algorithmus: Ohne Beschränkung der Allgemeinheit sei der erste Baum nicht niedriger als der zweite, und der zweite habe eine schwarze Wurzel (Knoten <math>\mathsf{H}</math> in der Abbildung) der Schwarzhöhe <math>s</math>. Ferner sei dem zweiten Baum sein erstes Element <math>\mathsf{G}</math> bereits entnommen. Es spielt nachher die Rolle eines „Bindeglieds“. Man steigt an der rechten Flanke des ersten Baums bis zu einem schwarzen Knoten der Schwarzhöhe <math>s</math> hinunter, sein Name sei <math>\mathsf{F}</math>.
Der Knoten <math>\mathsf{G}</math> wird rot gefärbt und zum rechten Kind des Elters <math>\mathsf{E}</math> von <math>\mathsf{F}</math> gemacht.
Ist <math>\mathsf{E}</math> schwarz, dann ist der verkettete Baum in Rot-Schwarz-Form und die Verkettung abgeschlossen.
Ist <math>\mathsf{E}</math> jedoch rot, dann ist sein linkes Kind schwarz.
Nun lässt sich eine Reparaturschleife anschließen, die zur Wurzel aufsteigend dieselbe Schleifeninvariante hat wie die Operation Einfügen,
und <math>\mathsf{G}</math> ist der Problemknoten der ersten Iterationsstufe.
Massenoperationen
Aufbauend auf der JOIN-Operation können weitere Massen- und Mengenoperationen gebildet werden,<ref name="join-based">{{#invoke:Vorlage:Literatur|f}}</ref>
die allesamt logarithmische Laufzeit haben, so z. B. die komplementäre Operation des Aufspaltens (englisch: split) eines Baums an einem Pfad.
Die Massenlöschung von allen Schlüsseln in einem zusammenhängenden Bereich (Intervall) kann durch zweimaliges Spalten und einmaliges Verketten geschehen oder, wenn das kleinste oder größte Element mit dabei ist, durch einmaliges Spalten. In ähnlicher Weise lässt sich ein Intervall mit logarithmischem Aufwand als Rot-Schwarz-Baum aus einem Rot-Schwarz-Baum herauspräparieren.
Eine Masseneinfügung kann durch einmaliges Spalten und zweimaliges Verketten durchgeführt werden, sofern die Menge als Rot-Schwarz-Baum vorbereitet ist und ihre Schlüssel in einem Intervall liegen, das im Zielbaum nicht vorkommt.
Mengenoperationen
Im selben Aufsatz wurde gezeigt, dass auch die Mengenoperationen Durchschnitt, Vereinigung und Differenz von digital repräsentierten (endlichen) Mengen sehr effizient (in logarithmischer Zeit) und in „natürlicher“ Parallelität implementiert werden können
(s. Binärer Suchbaum#Effiziente Massen- und Mengenoperationen aufbauend auf der JOIN-Operation).
Blelloch et. al. zeigen, dass sich diese Algorithmen sich so auf die 4 balancierten Suchbäume (AVL-, Rot-Schwarz-, Splay- und BB[α]) übertragen lassen, dass sich alles Spezifische im JOIN-Algorithmus abspielt.
Höhenbeweis
Wie schon in der Einleitung ausgeführt, ist die besondere Eigenschaft von Rot-Schwarz-Bäumen, dass sie in logarithmischer Zeit – genauer in <math>\mathcal O(\log n)</math> mit <math>n</math> als der Anzahl der Schlüssel – ein Element im Baum suchen, löschen oder einfügen können. Diese Operationen sind auf allen binären Suchbäumen von der Höhe <math>h</math> des Baumes abhängig. Je niedriger die Höhe des Baumes ist, desto schneller laufen die Operationen. Kann man nun beweisen, dass ein binärer Suchbaum mit <math>n</math> Elementen nie eine gewisse Höhe (im Falle des Rot-Schwarz-Baumes <math>2 \log_2(n\!+\!2)\!\!-\!\!2</math>) überschreitet, so hat man bewiesen, dass die oben genannten Operationen im schlechtesten Fall logarithmische Kosten haben.
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}
Rot-Schwarz-Bäume mit kleinster Knotenzahl zu gegebener Höhe
Knoten, und keinen mit weniger.<ref group="Anm.">Diese Minimalbäume spielen bei den Rot-Schwarz-Bäumen eine ähnliche Rolle wie die Fibonacci-Bäume bei den AVL-Bäumen.</ref><ref group="Anm.">Die Wurzel eines minimalen Rot-Schwarz-Baums ungerader Höhe kann natürlich auch schwarz sein.</ref>
Beweis
Damit ein Rot-Schwarz-Baum einer bestimmten Höhe <math>h</math> eine minimale Knotenzahl besitzt, muss er genau einen längsten Pfad enthalten, und dieser eine größtmögliche Anzahl roter Knoten, um eine möglichst große Baumhöhe mit möglichst kleiner Schwarzhöhe zu erreichen. Seine Einfärbung hat also unten beim Blatt mit Rot zu beginnen, und sich in der Folge nach oben bis zur Wurzel mit Schwarz und Rot streng abwechselnd fortzusetzen. Außerhalb dieses die Baumhöhe definierenden Pfades hat er nur schwarze Knoten.<ref>Sedgewick 2011 S. 444 Proof sketch</ref> Denn angenommen, es gäbe dort einen roten Knoten, dann beeinträchtigt das Entfernen desselben die Rot-Schwarz-Eigenschaften nicht, sofern einer von seinen zwei (schwarzen) Kindknoten an seine Stelle nachrückt und der andere gleichzeitig einschließlich seines Teilbaums entfernt wird. Alle Teilbäume außerhalb des längsten Pfades sind somit vollständige Binärbäume mit ausschließlich schwarzen Knoten.
Es gibt genau einen Rot-Schwarz-Baum der Höhe <math>h=1</math> mit einem roten Blatt, welches gleichzeitig die Wurzel ist. Also ist <math>m_1 = 1</math> in Übereinstimmung mit <math>2^{\lfloor (1+1)/2\rfloor} \!+\!2^{\lfloor 1/2 \rfloor} \!\!-\!\!2 = 2^1\!+\!2^0\!\!-\!\!2.</math>
Bei einem Minimalbaum (RBh in der Abbildung) der Höhe <math>h>1</math> sind die zwei Kindbäume der Wurzel von unterschiedlicher Höhe: der höhere enthält den die Höhe definierenden längsten Pfad und ist ein Minimalbaum der Höhe <math>h\!\!-\!\!1</math> mit <math>m_{h-1}</math> Knoten und der Schwarzhöhe <math>s:=\lfloor(h\!\!-\!\!1)/2\rfloor</math>; der niedrigere ist ein vollständiger Binärbaum mit Höhe = Schwarzhöhe <math>s,</math> hat also <math>2^s\!\!-\!\!1=2^{\lfloor(h-1)/2\rfloor}\!\!-\!\!1</math> Knoten. Damit ist rekursiv
Der Graph der Funktion <math>m_h</math> ist konvex nach unten und stückweise linear mit den Ecken an den Punkten <math>(h=2k\;|\;m_h=2^{k+1}\!\!-\!\!2)</math> mit <math>k \in \N .</math> Ferner ist <math>m_h=</math> A027383(h–1) für <math>h\geq 1</math> mit der {{#if:trim|Folge A027383 in OEIS.}}
Umformung
Wegen <math>3 > 2^{\tfrac32}</math> (eine Folge von <math>9>8</math>) haben wir für ungerades <math>h</math> die Ungleichung
so dass sich für alle (geraden wie ungeraden) <math>h</math>
<math>m_h \geq 2 \cdot 2^{\tfrac{h}2}-2</math>
ergibt.
Da <math>m_h</math> die kleinste Knotenzahl <math>n</math> für alle Rot-Schwarz-Bäume der Höhe <math>h</math> ist, gilt
<math>2 \cdot 2^{\tfrac{h}2}-2 \leq m_h \leq n ,</math>
so dass <math>h</math> sich im Intervall
<math>\log_2(n+1)</math>
<math>\leq h \leq</math>
<math>2 \log_2(n+2)-2 = 2 \log_2 (n/2 +1)</math>
<math>\bigl[ < 2 \log_2(n+1) \; \bigr]</math>
(vollständiger Binärbaum)
(minimaler RB-Baum)
befindet.<ref>Gleichheit am oberen Rand gilt für minimale RB-Bäume RB2k gerader Baumhöhe <math>h = 2 \cdot k</math> mit <math>n = 2 \cdot 2^k-2</math> Knoten und nur für diese. Damit ist die Ungleichung geringfügig genauer als das weitverbreitete <math>h < 2 \log_2(n+1) ,</math> z. B. in Cormen S. 264.</ref><ref group="Anm.">Diese RB-Bäume gestatten darüber hinaus nur eine einzige Einfärbung, sind aber nicht die einzigen RB-Bäume mit dieser Eigenschaft. Zur Höhe <math>h</math> gibt es <math>2^{h-1}</math> Formen von Minimalbäumen, da die Position des längsten Pfades der Position eines externen Blattes des vollständigen Binärbaums der Höhe <math>h\!\!-\!\!1</math> entspricht und durch diese Position die Lage aller anderen Knoten bestimmt ist. (Die Abbildung zeigt die äußerste linke Position.)</ref>
Folgerung
Somit folgt die Behauptung, dass ein Rot-Schwarz-Baum eine maximale Höhe <math>h</math> von <math>2 \log_2(n\!+\!2)\!\!-\!\!2</math> hat und damit die Operationen suchen, einfügen und löschen in logarithmischer Zeit erledigen kann. Drückt man dieses Ergebnis in der O-Notation aus, so ergibt sich für die Kosten der oben genannten Operationen, dass sie alle in <math>\mathcal O(\log n)</math> liegen mit <math>n</math> als der Zahl der Schlüssel.<ref group="Anm.">Als eine Datenstruktur, die im homogenen Arbeitsspeicher (Hauptspeicher) untergebracht ist, ist der Rot-Schwarz-Baum durch dessen Größe beschränkt, also auch die Höhe des Baums und die Längen der Pfade von Blatt zu Wurzel.
Der Programmierer wird seinen Anwendern gegenüber eher die Knotenzahl einschränken als die Baumhöhe, die für den Anwender eher zufälliger Natur ist und ihn normalerweise nicht interessiert.
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Die folgende Tabelle gibt zu jeder Knotenzahl <math>n</math> eine Höhenangabe <math>h</math> zurück, die von einem Rot-Schwarz-Baum mit <math>\leq n</math> Elementen nicht überschritten wird. Gängig sind Hauptspeicher mit 32 Bit und 64 Bit breiten virtuellen 8-Bit-Byte-Adressen. Da in einem 32-Bit-Adressraum maximal <math>2^{32}</math> Bytes untergebracht werden können und ein Knoten mindestens 2 Kind-Zeiger à je 4 Bytes benötigt, kann bei Benutzerdaten (Schlüssel + Wert) von <math>b</math> Bytes pro Knoten ein Baum mit maximal <math>n = 2^{32}/(2\cdot 4 \!+\! b)</math> Knoten untergebracht werden; bei <math>b = 3</math> sind das <math>n = 390451572 .</math> Wie die Tabelle zeigt, kann im 32-Bit-Adressraum die Höhe <math>h=55</math> durch einen Rot-Schwarz-Baum unmöglich überschritten werden. Für einen Adressraum mit 8 Byte breiten Adressen und <math>b=1</math> Byte Benutzerdaten hat man <math>n \le 2^{64}/(2\cdot 8 \!+\! b)</math><math>= 1085102592571150095 ,</math> so dass <math>h \leq 117</math> bleibt und beim Einsatz von <math>8 h = 8\cdot 117 = 936</math> Bytes für den Elter-Stapel dieser nicht überlaufen kann.
Mit den Bezeichnungen im Text ist <math>n = m_{h+1}-1,</math> eine Maßgabe, die so knapp wie möglich unterhalb der Knotenzahl des nächstgrößeren Minimalbaums bleibt.
Ist der Speicherbedarf für alle Knoten gleich, können die Benutzerdaten pro Knoten maximal
<math>b =
\begin{cases}
\\
\\
\end{cases}
</math>
<math>2^{32}/n-2\cdot 4</math>
im 32-Bit-Adressraum (4 Bytes/Adresse)
<math>2^{64}/n-2\cdot 8</math>
im 64-Bit-Adressraum (8 Bytes/Adresse)
Bytes umfassen.
Fazit
Ist der Stapelspeicher{{#if:trim|struct RBnode* Parents[]}} für die Elter-Zeiger bei einem 32-Bit-Adressraum auf wenigstens <math>h = 54</math> Einträge mit insgesamt <math>4 h = 4\cdot 54 = 216</math> Bytes ausgelegt, so kann er unmöglich überlaufen. Dasselbe gilt bei einem 64-Bit-Adressraum für <math>h = 117</math> und einem Elter-Stapel der Größe <math>8 h = 8\cdot 117 = 936</math> Bytes. Bevor nämlich in beiden Fällen der Elter-Stapel überläuft, ist der Adressraum schon geplatzt. Ben Pfaff hat #define RB_MAX_HEIGHT 128.</ref>
„Consider an (a,b)-tree with b ≥ 2a that is initially empty. For any sequence of n insert or remove operations, the total number of split or fuse operations is {{#if:trim|O(n).“}}
In den Kontext der Rot-Schwarz-Bäume übersetzt heißt das:
„Für eine beliebige Folge von <math>n</math> Einfüge- und Löschoperationen in einen anfänglich leeren Rot-Schwarz-Baum ist die Anzahl der Farbwechsel und Rotationen in {{#if:trim|<math>\mathcal{O}(n)</math>.“}}
Damit ist der Aufwand pro Operation in einer solchen Sequenz in <math>\mathcal{O}(1),</math> also amortisiert konstant.
Im Beweis, der für (2,4)-Bäume geführt wird und bei dem die Account-Methode zum Zug kommt, werden nur die split- und fuse-Operationen abgerechnet – ein Aufsuchen der betroffenen Stelle im Baum wird überhaupt nicht erwähnt und auch nicht mitgezählt. Die Aussage bezieht sich also auf die reinen Reparaturkosten.</ref> und damit auch im Mittel konstante.
Anwendungen von (binären) Suchbäumen, die neben Sequenzen von Einfügungen und Löschungen auch Suchoperationen enthalten, sind asymptotisch durch die logarithmische Laufzeit der letzteren dominiert.
Interessant ist der amortisiert konstante Modifikations-Aufwand jedoch, wenn der Suchbaum persistent gehalten werden soll, d. h. alle Versionen zugänglich bleiben sollen (s. a. Persistent data structure).<ref name="persistentRB" /><ref>Lightweight Java implementation of Persistent Red-Black Trees</ref>
Anzahlen von Rot-Schwarz-Bäumen
Die Folge A001131<math>(n\!+\!1)</math> in OEIS gibt in Abhängigkeit von der Knotenzahl <math>n</math> die Gesamtzahl der Rot-Schwarz-Bäume, Folge A001137 in OEIS derer mit schwarzer Wurzel und Folge A001138 in OEIS derer mit roter Wurzel.
Die nebenstehende Abbildung zeigt den balancierten Binärbaum mit 3 Schlüsseln in 3 verschiedenen regelkonformen Rot-Schwarz-Einfärbungen.
Beim Suchen und Traversieren ist wegen der identischen Verzweigungen der 3 Bäume alles Verhalten einschließlich Laufzeit gleich. Unterschiede gibt es aber bei Modifikationen, bspw. bei Einfügungen: Bei der linken Einfärbung sind alle Einfügungen vom Typ Transformation_E3 gefolgt von Transformation_E1, bei den rechten 2 Einfärbungen sind alle vom Typ Transformation_E2.
Zwar wird in einem reinen Einfügeszenario von den 3 möglichen Bäumen mit 3 Knoten (Schlüsseln) nur der eine Baum mit schwarzer Wurzel und 2 roten Kindern (der linke in der Abbildung) gebildet. Bezieht man jedoch Löschungen mit ein, dann kommen die zwei anderen Rot-Schwarz-Bäume (rechts in der Abbildung) hinzu,
und dies mit den oben beschriebenen Algorithmen für Einfügung und Löschung – jeweils bei geeignet gewählter Schlüsselfolge, die durch den Knoten mit blauer Kontur angegeben ist.
Die 4 kleinen Grafiken links und rechts zeigen, wie kleine Bausteine eines Rot-Schwarz-Baums (linke Hälften der Grafiken) mit einem (dicken) Knoten eines 2-3-4-Baums (rechte Hälften der Grafiken) zur Entsprechung gebracht werden können.
Man erzeugt aus einem Rot-Schwarz-Baum einen 2-3-4-Baum, indem man rote Kinder entsprechend ihrer Kindesrichtung links oder rechts als Datenelemente in den schwarzen Elterknoten hereinholt.<ref>Mehlhorn 1988 S. 193</ref><ref>Wenn man die zwei Wahlmöglichkeiten bei 3 Kindern auf eine (bspw. auf die erste, die obere) einschränkt, kommt man zu den LLRB (Abkürzung für {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) Bäumen, die im Wesentlichen dieselben informationstheoretischen Eigenschaften haben, bei deren Implementierung aber laut Sedgewick weniger Fälle zu unterscheiden sind (s. Robert Sedgewick. Left-leaning Red–Black Trees und PDF).</ref>
Umgekehrt kann man einen 2-3-4-Baum ganz einfach in einen Rot-Schwarz-Baum überführen: Aus einem Knoten mit 2 Datenelementen und 3 Kindzeigern (wie der Knoten [NIL,1,6] in der Abbildung) wird ein schwarzer Knoten (Datenelement) mit 1 Kindzeiger und einem roten Kindknoten (Datenelement), der noch 2 Kindzeiger enthält; aus einem Knoten mit 3 Datenelementen und 4 Kindzeigern (wie die Knoten [8,13,17] und [22,25,27] in der Abbildung) wird ein schwarzer Knoten (Datenelement) mit 2 roten Kindknoten (jeweils 1 Datenelement und 2 Kindzeiger).
Darüber hinaus gibt es Entsprechungen bei den Modifikationsoperationen (Einfügen, Löschen) zwischen Farbwechsel und Rotationen auf Seite der Rot-Schwarz-Bäume und den Aktionen Spalten ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) und Verschmelzen ({{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) bei den 2-3-4-Bäumen.
Im Gegensatz zu 2-3-4-Bäumen muss man bei Rot-Schwarz-Bäumen nicht den »Füllzustand« (Speicherausnutzung, {{#invoke:Vorlage:lang|full|CODE=en|SCRIPTING=Latn|SERVICE=englisch}}) der Knoten beobachten und verwalten, weshalb letztere als sehr effiziente Art der Implementierung der 2-3-4-Bäume gelten.<ref>Mehlhorn 1988 S. 187</ref>
Vergleich mit AVL-Baum
Die Menge der AVL-Bäume ist eine echte Teilmenge in der Menge der Rot-Schwarz-Bäume. Denn jeder Binärbaum, der das AVL-Kriterium erfüllt, lässt sich in einer das Rot-Schwarz-Kriterium erfüllenden Weise {{#if:trim|}}
Es gibt aber Rot-Schwarz-Bäume, die das AVL-Kriterium nicht erfüllen. Die nebenstehende Abbildung zeigt zum Beispiel einen Rot-Schwarz-Baum mit 6 Knoten und der externen Pfadlängensumme 21, während 20 die größte externe Pfadlängensumme bei AVL-Bäumen (und zugleich die kleinstmögliche für alle Binärbäume) dieser Knotenzahl ist. Konsequenterweise ist auch die Worst-Case-Höhe des AVL-Baums kleiner als die des Rot-Schwarz-Baums, und zwar asymptotisch um den Faktor {{#if:trim|(2 log2Φ)−1 ≈ 0,720.}} Allgemein werden AVL-Bäume als besser balanciert und ihr Suchverhalten als günstiger angesehen.
Die Laufzeiten für alle angeführten Operationen unterscheiden sich im Mittel und im Worst Case asymptotisch nur um einen konstanten Faktor, gehören also derselben Komplexitätsklasse an. Der Rot-Schwarz-Baum bietet allerdings amortisiert konstante Einfüge- und Löschkosten (jeweils nur Rebalancierung – ohne Navigation). Beim AVL-Baum sind nur die Einfügekosten amortisiert konstant, die Löschkosten immerhin im Mittel konstant.
Realistische Anwendungssituationen mit Performancedaten und -vergleichen – auch mit weiteren Suchalgorithmen und Spielarten der Datenstrukturen – finden sich bei Ben Pfaff. Seine Ergebnisse zeigen in 79 Messungen unter anderem die sehr große Ähnlichkeit von AVL-Bäumen (AVL) und Rot-Schwarz-Bäumen (RB) mit Laufzeitverhältnissen {{#if:|AVLRB⁄{{{3}}}|{{#if:RB|AVL⁄RB|{{#if:AVL|1⁄AVL|⁄}}}}}} zwischen 0,677 und 1,077 bei einem Median von ≈0,947 und einem geometrischen Mittelwert von ≈0,910.
Der Speicherplatzbedarf ist praktisch identisch: 1 Bit für das Rot-Schwarz-Attribut gegenüber 2 Bits<ref group="Anm.">oder auch nur 1 Bit (s. AVL-Implementierung)</ref> für den AVL-Balance-Faktor. Während die Balance-Faktoren eines AVL-Baums direkt von seiner (graphentheoretischen) Gestalt abhängen, sind bei Rot-Schwarz-Bäumen derselben Gestalt – außer bei den Minimalbäumen gerader Höhe – unterschiedliche Einfärbungen möglich (s. Abschnitt Anzahlen von Rot-Schwarz-Bäumen). Dabei wirken sich die Unterschiede der Einfärbungen nur auf die Modifikations- und nicht auf die Navigationsoperationen aus. Des Weiteren kann jede mögliche Gestalt eines AVL-Baums durch gezielte Einfügungen auch hergestellt werden. Bezogen auf die Baumform gilt dies auch für Rot-Schwarz-Bäume; es gibt aber Baumformen, bei denen durchaus regelkonforme Einfärbungen in einem reinen Einfügeszenario nicht bewirkt werden können.
Eine Folge dieser etwas größeren Freiheitsgrade ist, dass im Rot-Schwarz-Baum die für die Einfügung oder Löschung erforderlichen Farbänderungen und Rotationen schon während des Suchvorgangs – also beim Abstieg – vorgenommen werden können.<ref>
{{#if:Vorlage:Cite book/URL|{{#iferror: {{#iferror:{{#invoke:Vorlage:FormatDate|Execute}}|Vorlage:FormatDate/Wartung/Error}}| |}}}}{{#if:{{#if:
</ref>
Diese {{#if:trim|»Top-Down-Strategie«<ref>Mehlhorn 1988 S. 197–198.</ref>}} ist bspw. für nebenläufige und persistente Programmierung {{#if:trim|}}
So bleibt beim Einfügen der frisch eingefügte Knoten rot. Das bedeutet, dass eine zugehörige Suchfunktion im Abstieg den Baum entlang dem betroffenen Pfad (in logarithmischer Zeit) so vorbereiten kann, dass das endgültige Einfügen unmittelbar bei einem schwarzen Elter in Form eines roten Knotens geschehen oder eben (nach gfl. Inspektion des Elters) auch unterbleiben kann. Genauso kann beim Löschen eine (andere) Suchfunktion den Baum im Abstieg so vorbereiten, dass der zu löschende Knoten rot ist. In beiden Fällen bleibt der Baum sowohl beim Durchführen wie beim Unterlassen der Modifikation ein gültiger Rot-Schwarz-Baum, einer Modifikation, die beim Einfügen nur aus dem Setzen eines einzigen Zeigers besteht und beim Löschen nur geringfügig komplizierter ist. Demgegenüber gibt es beim AVL-Baum Baumformen, bei denen die Entscheidung betreffend den Vollzug der Modifikation nicht mit derart geringer Implikation offen gehalten werden kann.
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Ben Pfaff: An Introduction to Binary Search Trees and Balanced Trees. Free Software Foundation, Boston 2004, ftp.gnu.org (PDF gzip; 1662 kB; abgerufen am 13. Januar 2019).
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Ben Pfaff: Performance Analysis of BSTs in System Software. Stanford University, 2004, stanford.edu (PDF; 316 kB).
| {{#if:||{{#if:{{#invoke:URIutil|plainISBN|0262032937}}|Der BibISBN-Eintrag [[Vorlage:BibISBN/{{#invoke:URIutil|plainISBN|0262032937}}]] ist nicht vorhanden. Bitte prüfe die ISBN und lege ggf. einen {{#ifeq:Vorlage:bibISBN/{{#invoke:URIutil|plainISBN|0262032937}}|Rot-Schwarz-Baum|{{#switch:{{{LINK}}}|JA=|NEIN=}}}}[[[:Vorlage:Neuer Abschnitt/URL]] neuen Eintrag] an.|Die angegebene ISBN „0262032937“ ist fehlerhaft. Bitte prüfe und korrigiere die ISBN.}}{{#ifeq: 0 | 0 | }}}}}}
| {{#if:||{{#if:{{#invoke:URIutil|plainISBN|3893194622}}|Der BibISBN-Eintrag [[Vorlage:BibISBN/{{#invoke:URIutil|plainISBN|3893194622}}]] ist nicht vorhanden. Bitte prüfe die ISBN und lege ggf. einen {{#ifeq:Vorlage:bibISBN/{{#invoke:URIutil|plainISBN|3893194622}}|Rot-Schwarz-Baum|{{#switch:{{{LINK}}}|JA=|NEIN=}}}}[[[:Vorlage:Neuer Abschnitt/URL]] neuen Eintrag] an.|Die angegebene ISBN „3893194622“ ist fehlerhaft. Bitte prüfe und korrigiere die ISBN.}}{{#ifeq: 0 | 0 | }}}}}}
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Robert Sedgewick, Kevin Wayne: Algorithms Fourth Edition. Pearson Education, 2011, ISBN 978-0-321-57351-3, S. 432–447.
Andrew Binstock, Jon Rex: Practical Algorithms for Programmers. Addison-Wesley Professional, 1995, ISBN 0-201-63208-X.
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Kurt Mehlhorn: Datenstrukturen und effiziente Algorithmen. Teubner Stuttgart 1988, ISBN 3-519-12255-3.
| {{#if:||{{#if:{{#invoke:URIutil|plainISBN|9783540779773}}|Der BibISBN-Eintrag [[Vorlage:BibISBN/{{#invoke:URIutil|plainISBN|9783540779773}}]] ist nicht vorhanden. Bitte prüfe die ISBN und lege ggf. einen {{#ifeq:Vorlage:bibISBN/{{#invoke:URIutil|plainISBN|9783540779773}}|Rot-Schwarz-Baum|{{#switch:{{{LINK}}}|JA=|NEIN=}}}}[[[:Vorlage:Neuer Abschnitt/URL]] neuen Eintrag] an.|Die angegebene ISBN „9783540779773“ ist fehlerhaft. Bitte prüfe und korrigiere die ISBN.}}{{#ifeq: 0 | 0 | }}}}}}
{{#invoke:Vorlage:Anker|f |errCat=Wikipedia:Vorlagenfehler/Vorlage:Anker |errHide=1}}Roger Whitney (San Diego State University): CS 660: Red–Black tree notes