Algorithmen und Datenstrukturen Übung 6. Rot-Schwarz-Bäume Top-Down 2.-3-4-Bäume Zum Ausschluß des ungünstigsten Falls bei binären Suchbäumen ist eine gewisse Flexibilität in den verwendeten Datenstrukturen nötig. Das kann bspw. durch Aufnahme von mehr als einem Schlüssel in Baumknoten erreicht werden. So soll es 3-Knoten bzw. 4-Knoten geben, die 2 bzw. 3 Schlüssel enthalten können: - Ein 3-Knoten besitzt 3 von ihm ausgehende Verkettungen -- eine für alle Datensätze mit Schlüsseln, die kleiner sind als seine beiden Schlüssel -- eine für alle Datensätze, die zwischen den beiden Schlüsseln liegen -- eine für alle Datensätze mit Schlüsseln, die größer sind als seine beiden Schlüssel. - Ein 4-Knoten besitzt vier von ihm ausgehende Verkettungen, nämlich eine Verkettung für jedes der Intervalle, die durch seine drei Schlüssel definiert werden. Rot-Schwarz-Bäume Es ist möglich 2 -3-4-Bäume als gewöhnliche binäre Bäume (mit nur zwei Knoten) darzustellen, wobei nur ein zusätzliches Bit je Knoten verwendet wird. Die Idee besteht darin, 3-Knoten und 4 -Knoten als kleine binäre Bäume darzustellen, die durch „rote“ Verkettungen miteinander verbunden sind, im Gegensatz zu den schwarzen Verkettungen, die den 2 -3-4-Baum zusammenhalten: oder Abb.: Rot-schwarze Darstellung von Bäumen 4-Knoten werden als 2-Knoten dargestellt, die mittels einer roten Verkettung verbunden sind. 3-Knoten werden als 2 –Knoten dargestellt, die mit einer roten Markierung verbunden sind. Zu jedem 2-3-4-Baum gibt es viele ihm entsprechende Rot-Schwarz-Bäume. 1 Algorithmen und Datenstrukturen Eine Variante von Rot-Schwarz-Bäumen Nach jeder Einfüge -Operation bzw. Entferne-Operation lassen sich Rot-Schwarz-Bäume durch Rotationen ausgleichen. Eine Variante von Rot-Schwarz-Bäumen ist durch folgende Farbeigenschaften definiert: 1. Jeder Knoten ist entweder rot oder schwarz gefärbt. 2. der Wurzelknoten ist schwarz gefärbt. 3. Falls ein Knoten rot gefärbt ist, müssen seine Nachfolger schwarz gefärbt sein. 4. Jeder Pfad von einem Knoten zu einer „Null-Referenz“ muß die gleiche Anzahl von schwarzen Knoten enthalten. Eine Folgerung dieser Farbregeln ist: Die Höhe eines Rot-Schwarz-Baums ist etwa 2 ⋅ log( N + 1) . Aufgabe: Ermittle, welche Gestalt jeweils ein nach den vorliegenden Farbregeln erstellte Rot-SchwarzBaum beim einfügen folgenden Schlüssel „10 85 15 70 20 60 30 50 65 80 90 40 5 55“ annimmt 10 10 85 10 85 15 15 10 85 70 15 10 85 70 20 15 10 70 20 85 60 15 10 70 20 85 60 2 Algorithmen und Datenstrukturen 30 15 10 70 30 85 20 60 50 30 15 10 70 20 60 85 50 65 30 15 10 70 20 60 85 50 65 80 30 15 10 70 20 60 85 50 65 80 90 30 15 10 70 20 60 85 50 65 3 80 90 Algorithmen und Datenstrukturen 40 30 15 70 10 20 60 85 50 65 80 90 40 5 30 15 70 10 20 60 5 85 50 65 80 90 40 55 30 15 70 10 20 60 5 85 50 40 65 9 85 55 80 90 Die Abbildungen zeigen, daß im Durchschnitt der Rot-Schwarz-Baum ungefähr die Tiefe eines AVLBaums besitzt. Der Vorteil von Rot-Schwarz-Bäumen ist der geringere Overhead zur Ausführung von Einfügevorgängen und die geringere Anzahl von Rotationen. „Top -Down“ Rot-Schwarz-Bäume Kann auf dem Weg nach unten festgestellt werden, daß ein Knoten X zwei rote Nachfolgeknoten hat, dann wird X rot und die beiden „Kinder“ schwarz: X c1 X c2 c1 c2 Das führt zu einem Verstoß gegen Bedingung 3, falls der Vorgänger von X auch rot ist. In diesem Fall können aber geeignete Rotationen herangezogen werden: 4 Algorithmen und Datenstrukturen G P P S X C X A G A B B S C G P X S X P G C A A B1 B1 B2 B2 S C Der folgende Fall kann nicht eintreten: Der Nachbar vom Elternknoten ist rot gefärbt. Auf dem Weg nach unten muß der Knoten mit zwei roten Nachfolgern einen schwarzen Großvaterknoten haben. Implementierung Sie wird dadurch erschwert, daß einige Teilbäume (z.B. der rechte Teilbaum des Knoten 10 im vorliegenden Bsp.) leer sein können, und die Wurzel des Baums (, da ohne Vorgänger, ) einer speziellen Behandlung bedarf. Aus diesem Grund werden hier „Sentinel“-Knoten verwendet: - einer für die Wurzel. Dieser Knoten speichert den Schlüssel − ∞ und einen rechten Link zu dem realen Knoten - einen Nullknoten (nullNode), der eine Null-Referenz anzeigt. Der Inorder-Durchlauf nimmt aus diesem Grund folgende Gestalt an: /* Ausgabe der im Baum gespeicherten Datenelemente in sortierter Reihenfolge. */ public void printTree( ) { if( isEmpty() ) System.out.println("Leerer Baum"); else printTree( header.rechts ); } /* * Interne Methode fuer die Ausgabe des Baum in sortierter Reihenfolge. * b ist der Wurzelknoten. */ private void printTree(RotSchwarzKnoten b) { if (b != nullNode ) { printTree(b.links); System.out.println(b.daten ); printTree(b.rechts ); } } 5 Algorithmen und Datenstrukturen Programme: 1 // BaumKnoten fuer RotSchwarzBaeume class RotSchwarzKnoten { // Instanzvariable public Comparable daten; // Dateninformation der Knoten protected RotSchwarzKnoten links; // linkes Kind protected RotSchwarzKnoten rechts; // rechtes Kind protected int farbe; // Farbe // Konstruktoren RotSchwarzKnoten(Comparable datenElement) { this(datenElement, null, null ); } RotSchwarzKnoten(Comparable datenElement, RotSchwarzKnoten l, RotSchwarzKnoten r) { daten = datenElement; links = l; rechts = r; farbe = RotSchwarzBaum.BLACK; } } // // // // // // // // // // // // // // Die Klasse RotSchwarzBaum Konstruktion: mit einem "negative infinity sentinel" ******************PUBLIC OPERATIONEN********************* void insert(x) --> Insert x void remove(x) --> Entferne x (nicht implementiert) Comparable find(x) --> Rueckgabe des Datenelements, das x enthaelt Comparable findMin() --> Rueckgabe des kleinsten Datenelements Comparable findMax() --> Rueckgabe des groessten Datenelements boolean isEmpty() --> Rueckgabe true, falls leer; anderenfalls false void makeEmpty() --> Entferne alles void printTree() --> Ausgabe in aufsteigend sortierter Folge void ausgRotSchwarzBaum() --> Ausgabe des Baums um 90 Grad versetzt /* * Implementierung eines RotSchwarzBaum. * Vergleiche basieren auf der Methode compareTo. */ public class RotSchwarzBaum { private RotSchwarzKnoten header; private static RotSchwarzKnoten nullNode; static // Static Initialisierer for nullNode { nullNode = new RotSchwarzKnoten(null); nullNode.links = nullNode.rechts = nullNode; } static final int BLACK = 1; // Black must be 1 static final int RED = 0; // Fuer "insert routine" und zugehoerige unterstuetzende Routinen private static RotSchwarzKnoten current; private static RotSchwarzKnoten parent; private static RotSchwarzKnoten grand; 1 PR43220 6 Algorithmen und Datenstrukturen private static RotSchwarzKnoten great; /* * Baumkonstruktion. * negInf ist ein Wert, der kleiner oder gleich zu allen anderen Werten ist. */ public RotSchwarzBaum(Comparable negInf) { header = new RotSchwarzKnoten(negInf); header.links = header.rechts = nullNode; } /* * Einfügen in den Baum. Duplikate werden ueberlesen. * "item" ist das einzufuegende Datenelement. */ public void insert(Comparable item) { current = parent = grand = header; nullNode.daten = item; while( current.daten.compareTo( item ) != 0 ) { great = grand; grand = parent; parent = current; current = item.compareTo(current.daten ) < 0 ? current.links : current.rechts; // Pruefe, ob zwei rote Kinder; falls es so ist, fixiere es if( current.links.farbe == RED && current.rechts.farbe == RED ) reOrientierung( item ); } // Fehlanzeige fuer Einfuegen, falls schon da if( current != nullNode ) return; current = new RotSchwarzKnoten( item, nullNode, nullNode ); // Anhaengen an die Eltern if( item.compareTo( parent.daten ) < 0 ) parent.links = current; else parent.rechts = current; reOrientierung( item ); } /* * Entferne aus dem Baum. * Nicht implementiert * x ist das zu entfernemde Datenelement. */ public void remove( Comparable x ) { System.out.println("Entfernen ist nicht implementiert"); } /* * Finde das kleinste Datenelement im Baum. * Rueckgabe kleinstes Datenelement oder null, falls leer. */ public Comparable findMin( ) { if (isEmpty( )) return null; RotSchwarzKnoten itr = header.rechts; while( itr.links != nullNode ) itr = itr.links; return itr.daten; } /* * Finde das groesste Datenelement im Baum. 7 Algorithmen und Datenstrukturen * Rueckgabe des groessten Datenelemnts oder null, falls leer. */ public Comparable findMax( ) { if (isEmpty( )) return null; RotSchwarzKnoten itr = header.rechts; while( itr.rechts != nullNode ) itr = itr.rechts; return itr.daten; } /* * Finde ein Datenelement im Baum. * x enthaelt das zu suchende Datenelement. * Rueckgabe des passenden Datenelements oder null, falls nicht gefunden. */ public Comparable find(Comparable x) { nullNode.daten = x; current = header.rechts; for( ; ; ) { if( x.compareTo( current.daten ) < 0 ) current = current.links; else if( x.compareTo( current.daten ) > 0 ) current = current.rechts; else if( current != nullNode ) return current.daten; else return null; } } /* * Mache den Baum logisch leer. */ public void makeEmpty( ) { header.rechts = nullNode; } /* * Test, ob der Baum logisch leer ist. * Rueckgabe true, falls leer; anderenfalls false. */ public boolean isEmpty( ) { return header.rechts == nullNode; } /* * Ausgabe der im Baum gespeicherten Datenelemente in sortierter Reihenfolge. */ public void printTree( ) { if( isEmpty() ) System.out.println("Leerer Baum"); else printTree( header.rechts ); } /* * Interne Methode fuer die Ausgabe des Baum in sortierter Reihenfolge. * b ist der Wurzelknoten. */ private void printTree(RotSchwarzKnoten b) 8 Algorithmen und Datenstrukturen { if (b != nullNode ) { printTree(b.links); System.out.println(b.daten ); printTree(b.rechts ); } } /* * Ausgabe des Binaerbaums um 90 Grad vesetzt */ public void ausgRotSchwarzBaum() { ausgRotSchwarzBaum(header.rechts,0); } private void ausgRotSchwarzBaum(RotSchwarzKnoten b, int nSpace) { if (b != nullNode) { ausgRotSchwarzBaum(b.links,nSpace += 6); for (int i = 0; i < nSpace; i++) System.out.print(" "); System.out.println(b.daten + " " + b.farbe); ausgRotSchwarzBaum(b.rechts, nSpace); } } /* * Interne Routine, die waehrend eines Einfuegevorgangs aufgerufen wird * Falls ein Knoten zwei rote Kinder hat, fuehre Tausch der Farben aus * und rotiere. * item enthaelt das einzufuegende Datenelement. */ private void reOrientierung(Comparable item) { // Tausch der Farben current.farbe = RED; current.links.farbe = BLACK; current.rechts.farbe = BLACK; if (parent.farbe == RED) // Rotation ist noetig { grand.farbe = RED; if ( (item.compareTo( grand.daten) < 0 ) != (item.compareTo( parent.daten) < 0 ) ) parent = rotation(item, grand); // Start Doppelrotation current = rotation(item, great); current.farbe = BLACK; } header.rechts.farbe = BLACK; // Mache die Wurzel schwarz } /* * Interne Routine, die eine einfache oder doppelte Rotation veranlasst. * Da das Ergebnis an "parent" geheftet wird, gibt es vier Faelle. * Aufruf durch reOrientierung. * "item" ist das Datenelement reOrientierung. * "parent" ist "parent" von der wurzel des rotierenden Teilbaums. * Rueckgabe: Wurzel des rotierenden Teilbaums. */ private RotSchwarzKnoten rotation(Comparable item, RotSchwarzKnoten parent) { if (item.compareTo(parent.daten) < 0) return parent.links = item.compareTo( parent.links.daten) < 0 ? rotationMitLinksNachf(parent.links) : // LL 9 Algorithmen und Datenstrukturen rotationMitRechtsNachf(parent.links) ; // LR else return parent.rechts = item.compareTo(parent.rechts.daten) < 0 ? rotationMitLinksNachf(parent.rechts) : // RL rotationMitRechtsNachf(parent.rechts); // RR } /* * Rotation Binaerbaumknoten mit linkem Nachfolger. */ static RotSchwarzKnoten rotationMitLinksNachf( RotSchwarzKnoten k2 ) { RotSchwarzKnoten k1 = k2.links; k2.links = k1.rechts; k1.rechts = k2; return k1; } /* * Rotate Binaerbaumknoten mit rechtem Nachfolger. */ static RotSchwarzKnoten rotationMitRechtsNachf(RotSchwarzKnoten k1) { RotSchwarzKnoten k2 = k1.rechts; k1.rechts = k2.links; k2.links = k1; return k2; } } import java.io.*; public class RotSchwarzBaumTest { public static void main(String[ ] args) { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader ein = new BufferedReader(isr); String eingabeZeile = null; RotSchwarzBaum b = new RotSchwarzBaum(new Integer(Integer.MIN_VALUE)); System.out.println("Eingabe ganzzahliger Werte: "); while (true) { try { eingabeZeile = ein.readLine(); int a = Integer.parseInt(eingabeZeile); if (a == 0) break; b.insert(new Integer(a)); b.ausgRotSchwarzBaum(); } catch(IOException e) { System.out.println(e.toString()); // System.exit(0); } } // b.printTree(); b.ausgRotSchwarzBaum(); } } 10