Algorithmen und Datenstrukturen Übung 4: Die generische Klasse „AvlBaum“ in Java 1 Ein binärer Suchbaum hat die AVL -Eigenschaft, wenn sich in jedem Knoten sich die Höhen der beiden Teilbäume höchstens um 1 unterscheiden. Diese Last („Balance“) muß in einem Knoten gespeichert sein. Es genügt aber als Maß für die Unsymmetrie eine Höhendifferenz festzuhalten, die nur die Werte –1 (linkslastig), 0 (gleichlastig) und +1 (rechtslastig) annehmen kann. Die AVL -Eigenschaft ist verletzt, wenn diese Höhendifferenz +2 bzw. –2 ist. Der Knoten, der diesen Wert erhalten hat, ist der Knoten „alpha“, dessen Unausgeglichenheit auf einen der folgenden 4 Fälle zurückzuführen ist: 1. Einfügen in den linken Teilbaum, der vom linken Nachkommen des Knoten „alpha“ bestimmt ist. 2. Einfügen in den rechten Teilbaum, der vom linken Nachkommen des Knoten „alpha“ bestimmt ist. 3. Einfügen in den linken Teilbaum, der vom rechten Nachkommen des Knoten „alpha“ bestimmt ist. 4. Einfügen in den rechten Teilbaum, der vom rechten Nachkommen des Knoten „alpha“ bestimmt ist Fall 1 und Fall 4 bzw. Fall 2 und Fall 3 sind Spiegelbilder, zeigen also das gleiche Verhalten. Fall 1 kann durch einfache Rotation behandelt werden und ist leicht zu bestimmen, daß das Einfügen „außerhalb“ (links – links bzw. rechts – rechts im Fall 4) stattfindet. Fall 2 kann durch doppelte Rotation behandelt werden und ist ebenfalls leicht zu bestimmen, da das Einfügen „innerhalb“ (links –rechts bzw. rechts – links) erfolgt. Die einfache Rotation Die folgende Darstellung beschreibt den Fall 1 vor und nach der Rotation: k2 k1 k1 k2 Z Y X Y Z X Die folgende Darstellung bes chreibt Fall 4 vor und nach der Rotation : k1 k2 k2 k1 X Y X Y Z Doppelrotation Die einfache Rotation führt in den Fällen 2 und 3 nicht zum Erfolg. 1 Vgl. PR43210 1 Z Algorithmen und Datenstrukturen Fall 2 muß durch eine Doppelrotation (links – rechts) behandelt werden. k3 k2 k1 k1 k3 D k2 B A C A B D C Auch Fall 3 muß durch Doppelrotation behandelt werden k1 k2 k2 k1 k3 A k3 D B A B C D C Implementierung Zum Einfügen eines Knoten mit dem Datenwert „x“ in einen AVL-Baum, wird „x“ rekursiv in den betoffenen Teilbaum eingesetzt. Falls die Höhe dieses Teilbaums sich nicht verändert, ist das Einfügen beendet. Liegt Unausgeglichenheit vor, dann ist einfache oder doppelte Rotation (abhängig von „x“ und den Daten des betroffenen Teilbaums) nötig. Avl-Baumknoten Er enthält für jeden Knoten eine Angabe zu r Höhe(ndifferenz) seiner Teilbäume. // Baumknoten fuer AVL-Baeume class AvlKnoten { // Instanzvariable protected AvlKnoten links; // Linkes Kind protected AvlKnoten rechts; // Rechtes Kind protected int hoehe; // Hoehe public Comparable daten; // Das Datenelement // Konstruktoren public AvlKnoten(Comparable datenElement) { this(datenElement, null, null ); } public AvlKnoten( Comparable datenElement, AvlKnoten lb, AvlKnoten rb ) { daten = datenElement; 2 Algorithmen und Datenstrukturen links = lb; rechts = rb; hoehe = 0; } } Der Avl-Baum Bei jedem Schritt ist festzustellen, ob die Höhe des Teilnaums, in dem ein Element eingefügt wurde, zugenommen hat. /* * Rueckgabe: Hoehe des Knotens, oder -1, falls null. */ private static int hoehe(AvlKnoten b) { return b == null ? -1 : b.hoehe; } Die Methode „insert “ führt das Einfügen eines Baumknoten in den Avl-Baum aus: /* * Interne Methode zum Einfuegen eines Baumknoten in einen T eilbaum. * x ist das einzufuegende Datenelement. * b ist der jeweilige Wurzelknoten. * Rueckgabe der neuen Wurzel des jeweiligen Teilbaums. */ private AvlKnoten insert(Comparable x, AvlKnoten b) { if( b == null ) b = new AvlKnoten(x, null, null); else if (x.compareTo( b.daten) < 0 ) { b.links = insert(x, b.links ); if (hoehe( b.links ) - hoehe( b.rechts ) == 2 ) if (x.compareTo( b.links.daten ) < 0 ) b = rotationMitLinksNachf(b); else b = doppelrotationMitLinksNachf(b); } else if (x.compareTo( b.daten ) > 0 ) { b.rechts = insert(x, b.rechts); if( hoehe(b.rechts) - hoehe(b.links) == 2) if( x.compareTo(b.rechts.daten) > 0 ) b = rotationMitRechtsNachf(b); else b = doppelrotationMitRechtsNachf( b ); } else ; // Duplikat; tue nichts b.hoehe = max( hoehe( b.links ), hoehe( b.rechts ) ) + 1; return b; } 3 Algorithmen und Datenstrukturen Einfache Rotation RotationMitLinksNachf dreht den Linken Teilbaum der folgenden Darstellung nach rechts. k2 k1 k1 k2 Z Y X Y Z X * Rotation Binaerbaumknoten mit linkem Nachfolger. * Fuer AVL-Baeume ist dies eine einfache Rotation (Fall 1). * Aktualisiert Angaben zur Hoehe, dann Rueckgabe der neuen Wurzel. */ private static AvlKnoten rotationMitLinksNachf(AvlKnoten k2) { AvlKnoten k1 = k2.links; k2.links = k1.rechts; k1.rechts = k2; k2.hoehe = max( hoehe( k2.links ), hoehe( k2.rechts ) ) + 1; k1.hoehe = max( hoehe( k1.links ), k2.hoehe ) + 1; return k1; } Doppelrotation “doppelrotationMitLinksNachf” führt die folgende Umstellung der Baumknoten aus: k3 k2 k1 k1 k3 D k2 B A C A B C /* * Doppelrotation Fall 2: erstes linkes Kind mit seinem rechten Kind; * dann Knoten k3 mit neuem linken Kind. * Aktualisieren der Hoehen, * dann Rueckgabe der neuen Wurzel. */ private static AvlKnoten doppelrotationMitLinksNachf(AvlKnoten k3) { k3.links = rotationMitRechtsNachf( k3.links ); return rotationMitLinksNachf( k3 ); } 4 D Algorithmen und Datenstrukturen Lösungen // Baumknoten fuer AVL-Baeume class AvlKnoten { // Instanzvariable protected AvlKnoten links; // Linkes Kind protected AvlKnoten rechts; // Rechtes Kind protected int hoehe; // Hoehe public Comparable daten; // Das Datenelement // Konstruktoren public AvlKnoten(Comparable datenElement) { this(datenElement, null, null ); } public AvlKnoten( Comparable datenElement, AvlKnoten lb, AvlKnoten rb ) { daten = datenElement; links = lb; rechts = rb; hoehe = 0; } } // Binaerer Suchbaum // // // // // // // // // // // // // Konstruktor: Initialisierung der Wurzel mit null *** oeffentlich zugaengliche Methoden ******************** void insert( x ) --> Fuege x ein void remove( x ) --> Entferne x Comparable find( x ) --> Gib das Element zurueck, das zu x passt Comparable findMin( ) --> Rueckgabe des kleinsten Elements Comparable findMax( ) --> Rueckgabe des groessten Elements boolean isEmpty( ) --> Return true if empty; else false void makeEmpty( ) --> Entferne alles void printTree( ) --> Ausgabe der Baum-Elemente in sort. Folge void ausgBinaerBaum() --> Ausgabe der Binaerbaum-Elemente /* * Implementierung AVL-Baum * Vergleiche basieren auf der Methode compareTo. */ public class AvlBaum { /* Wurzel des Baums */ private AvlKnoten wurzel; /* * Default-Konstruktor */ public AvlBaum( ) { wurzel = null; } /* * Einfuegen; Duplikaten werden ignoriert. * x ist das einzufuegende Datenelement. 5 Algorithmen und Datenstrukturen */ public void insert(Comparable x ) { wurzel = insert( x, wurzel ); } /* * Entfernen eines Baumknotens. Falls x nicht gefunden wird, * geschieht nichts. * x ist das zu entfernende Datenelement. */ public void remove(Comparable x ) { System.out.println( "Entschuldigung, remove wurde nicht implementiert" ); } /* * Bestimme das kleinste Datenelement im Baum.. * Rueckgabe: kleinstes Datenelement oder null, * falls der Baum leer ist. */ public Comparable findMin( ) { return elementAt( findMin(wurzel)); } /* * Finde das groesste Datenelement im Baum. * Rueckgabe: groesstes Datenelement oder null, falls der Baum leer ist. */ public Comparable findMax( ) { return elementAt(findMax(wurzel)); } /* * Finde ein datenelement im Baum. * x enthaelt das zu suchende Datenelement. * Rueckgabe: das gesuchte Datenelement oder null, * falls es nicht gefunden wurde. */ public Comparable find(Comparable x) { return elementAt(find(x, wurzel)); } /* * Loeschen des Baums. */ public void makeEmpty( ) { wurzel = null; } /* * Test, ob der Baum leer ist. * Rueckgabe true, falls leer; anderenfalls false. */ public boolean isEmpty( ) { return wurzel == null; } /* * Gib den Inhalt des Baums in sortierter Folge vor. */ public void printTree( ) { if( isEmpty( ) ) 6 Algorithmen und Datenstrukturen System.out.println( "Baum ist leer" ); else printTree( wurzel ); } /* * Ausgabe der Elemente des binaeren Baums um 90 Grad versetzt */ public void ausgBinaerBaum() { if( isEmpty() ) System.out.println( "Leerer baum" ); else ausgBinaerBaum(wurzel,0); } /* * Interne Methode fuer den Zugriff auf einen Baumknoten. * Parameter b referenziert den Baumknoten. * Rueckgabe des Datenelements im Baumknoten oder null, * falls b null ist. */ private Comparable elementAt(AvlKnoten b ) { return b == null ? null : b.daten; } /* * Interne Methode zum Einfuegen eines Baumknoten in einen Teilbaum. * x ist das einzufuegende Datenelement. * b ist der jeweilige Wurzelknoten. * Rueckgabe der neuen Wur zel des jeweiligen Teilbaums. */ private AvlKnoten insert(Comparable x, AvlKnoten b) { if( b == null ) b = new AvlKnoten(x, null, null); else if (x.compareTo( b.daten) < 0 ) { b.links = insert(x, b.links ); if (hoehe( b.links ) - hoehe( b.rechts ) == 2 ) if (x.compareTo( b.links.daten ) < 0 ) b = rotationMitLinksNachf(b); else b = doppelrotationMitLinksNachf(b); } else if (x.compareTo( b.daten ) > 0 ) { b.rechts = insert(x, b.rechts); if( hoehe(b.rechts) - hoehe(b.links) == 2) if( x.compareTo(b.rechts.daten) > 0 ) b = rotationMitRechtsNachf(b); else b = doppelr otationMitRechtsNachf( b ); } else ; // Duplikat; tue nichts b.hoehe = max( hoehe( b.links ), hoehe( b.rechts ) ) + 1; return b; } /* * Interne Methode zum Bestimmen des kleinsten Datenelements. * b ist die Wurzel. * Rueckgabe: Knoten mit dem kleinsten Datenelement. */ private AvlKnoten findMin(AvlKnoten b) 7 Algorithmen und Datenstrukturen { if (b == null) return b; while(b.links != null ) b = b.links; return b; } /* * Interne Methode zum Bestimmen des groessten Datenelements. * b ist die Wurzel. * Rueckgabe: Knoten mit dem groessten Datenelement. */ private AvlKnoten findMax(AvlKnoten b ) { if (b == null) return b; while (b.rechts != null) b = b.rechts; return b; } /* * Internal Methode zum Bestimmen eines Datenelements. * x ist das zu suchende Datenelement. * b ist die Wurzel. * Rueckgabe: Knoten mit dem passenden Datenelement. */ private AvlKnoten find(Comparable x, AvlKnoten b) { while( b != null ) if (x.compareTo( b.daten) < 0 ) b = b.links; else if( x.compareTo( b.daten ) > 0 ) b = b.rechts; else return b; // Passt return null; // Passt nicht } /* * Interne Methode zur Ausgabe eines Teilbaums in sortierte Folge. * b ist die Wurzel. */ private void printTree(AvlKnoten b) { if( b != null ) { printTree( b.links ); System.out.println( b.daten ); printTree( b.rechts ); } } /* * Ausgabe des Binaerbaums um 90 Grad versetzt */ private void ausgBinaerBaum(AvlKnoten b, int stufe) { if (b != null) { ausgBinaerBaum(b.links, stufe + 1); for (int i = 0; i < stufe; i++) { System.out.print(' '); } System.out.println(b.daten); 8 Algorithmen und Datenstrukturen ausgBinaerBaum(b.rechts, stufe + 1); } } /* * Rueckgabe: Hoehe des Knotens, oder -1, falls null. */ private static int hoehe(AvlKnoten b) { return b == null ? -1 : b.hoehe; } /* * Rueckgabe: Maximum aus lhs und rhs. */ private static int max( int lhs, int rhs ) { return lhs > rhs ? lhs : rhs; } /* * Rotation Binaerbaumknoten mit linkem Nachfolger. * Fuer AVL-Baeume ist dies eine einfache Rotation (Fall 1). * Aktualisiert Angaben zur Hoehe, dann Rueckgabe der neuen Wurzel. */ private static AvlKnoten rotationMitLinksNachf(AvlKnoten k2) { AvlKnoten k1 = k2.links; k2.links = k1.rechts; k1.rechts = k2; k2.hoehe = max( hoehe( k2.links ), hoehe( k2.rechts ) ) + 1; k1.hoehe = max( hoehe( k1.links ), k2.hoehe ) + 1; return k1; } /* * Rotate binary tree node with right child. * For AVL trees, this is a single rotation for case 4. * Update heights, then return new wurzel. */ private static AvlKnoten rotationMitRechtsNachf(AvlKnoten k1) { AvlKnoten k2 = k1.rechts; k1.rechts = k2.links; k2.links = k1; k1.hoehe = max( hoehe( k1.links ), hoehe( k1.rechts ) ) + 1; k2.hoehe = max( hoehe( k2.rechts ), k1.hoehe ) + 1; return k2; } /* * Double rotate binary tree node: first left child * with its right child; then node k3 with new left child. * For AVL trees, this is a double rotation for case 2. * Update heights, then return new wurzel. */ private static AvlKnoten doppelrotationMitLinksNachf(AvlKnoten k3) { k3.links = rotationMitRechtsNachf( k3.links ); return rotationMitLinksNachf( k3 ); } /* * Double rotate binary tree node: first right child * with its left child; then node k1 with new right child. * For AVL trees, this is a double rotation for case 3. * Update heights, then return new wurzel. */ private static AvlKnoten doppelrotationMitRechtsNachf(AvlKnoten k1) 9 Algorithmen und Datenstrukturen { k1.rechts = rotationMitLinksNachf(k1.rechts); return rotationMitRechtsNachf(k1); } } public class AvlBaumTest { // Test-Programm public static void main(String [] args) { // Test Nr. 1 AvlBaum b = new AvlBaum(); final int ZAHLEN = 4000; final int LUECKE = 37; System.out.println( "Pruefung... (keine weiteren Ausgaben bedeutet Erfolg)"); for( int i = LUECKE; i != 0; i = (i + LUECKE) % ZAHLEN) b.insert( new Integer( i ) ); for(int i = 1; i < ZAHLEN; i+= 2 ) b.remove( new Integer( i ) ); if (ZAHLEN < 40) b.printTree( ); if ( ((Integer)(b.findMin( ))).intValue( ) != 2 || ((Integer)(b.findMax( ))).intValue( ) != ZAHLEN - 2 ) System.out.println( "FindMin oder FindMax -Fehler!" ); for( int i = 2; i < ZAHLEN; i+=2 ) if( ((Integer)(b.find( new Integer( i ) ))).intValue( ) != i ) System.out.println( "Find Fehler Nr.1!" ); for( int i = 1; i < ZAHLEN; i+=2 ) { if( b.find( new Integer( i ) ) != null ) System.out.println( "Find Fehler Nr.2!" ); } // Test Nr.2 AvlBaum b1 = new AvlBaum(); for (int i = 0; i < 10; i++) { // Erzeuge eine Zahl zwischen 0 und 100 Integer r = new Integer((int)(Math.random()*100)); b1.insert(r); } System.out.println("Inorder-Durchlauf"); b1.printTree(); System.out.println(); System.out.println("Baumdarstellung um 90 Grad versetzt"); b1.ausgBinaerBaum(); System.out.print("Kleinster Wert: "); System.out.print(((Integer)(b1.findMin())).intValue()); System.out.println(); System.out.print("Groesster Wert: "); System.out.print(((Integer)(b1.findMax())).intValue()); System.out.println(); for (int i = 0; i < 10; i++) { // Erzeuge eine Zahl zwischen 0 und 100 Integer r = new Integer((int)(Math.random()*100)); if ( b1.find(r) != null ) { b1.remove( r ); } // else System.out.println(r.intValue() + " nicht gefunden"); } 10 Algorithmen und Datenstrukturen b1.ausgBinaerBaum(); // Test Nr. 3 AvlBaum b2 = new AvlBaum(); for (int i = 0; i < 20; i++) // 20 Zusfallsstrings speichern { String s = "Zufallszahl " + (int)(Math.random() * 100); b2.insert(s); } b2.printTree(); // Sortiert wieder ausdrucken // Test Nr.4 AvlBaum b3 = new AvlBaum(); for (int i = 0; i < 20; i++) // { Integer r = new Integer(i); b3.insert(r); } b3.ausgBinaerBaum(); } } 11