Algorithmen und Datenstrukturen Übung 5. Perfekt ausgeglichene binäre Bäume, statisch optimierte Bäume1 Perfekt ausgeglichener, binärer Suchbaum Hier geht es darum, entartete Bäume (schiefe Bäume, Äste werden zu linearen Listen, etc.) zu vermeiden. Statische Optimierung heißt: Der ganze Baum wird neu (bzw. wieder neu) aufgebaut. Bei der dynamischen Optimierung wird der Baum während des Betriebs (bei jedem Ein - und Ausfügen) optimiert. Ein binärer Suchbaum sollte immer ausgeglichen sein. Der folgende Baum 1 2 3 4 5 ist zu einer linearen Liste degeneriert und läßt sich daher auch nicht viel schneller als eine lineare Liste durchsuchen. Ein derartiger binärer Suchbaum entsteht zwangsläufig, wenn die bei der Eingabe angegebene Schlüsselfolge in aufsteigend sortierter Reihenfolge vorliegt. Der vorliegende binäre Suchbaum ist selbstverständlich nicht ausgeglichen. Es gibt allerdings auch Unterschiede bei der Beurteilung der Ausgeglichenheit, z.B.: Die vorliegenden Bäume sind beide ausgeglichen. Der linke Baum ist perfekt ausbalanciert. Jeder Binärbaum ist perfekt ausbalanciert, falls jeder Knoten über einen linken und rechten Teilbaum verfügt, dessen Knotenzahl sich höchstens um den Wert 1 unterscheidet. Der rechte Teilbaum ist ein in der Höhe ausgeglichener (AVL 2-)Baum. Die Höhe der Knoten zusammengehöriger linker und rechter Teilbäume unterscheiden sich höchstens um den Wert 1. Jeder perfekt ausgeglichene Baum ist gleichzeitig auch ein in der Höhe ausgeglichener Binärbaum. Der umgekhrte Fall trifft allerdings nicht zu. Es gibt einen einfachen Algorithmus zum Erstellen eines pefekt ausgeglichenen Binärbaums, falls (1) die einzulesenden Schlüsselwerte sortiert in aufsteigender Reihenfolge angegeben werden (2) bekannt ist, wieviel Objekte (Schlüssel) werden müssen. import java.io.*; class PBBknoten { // Instanzvariable protected PBBknoten links; protected PBBknoten rechts; // linker Teilbaum // rechter Teilbaum 1 Vgl. PBB.java, PR43205 2 nach den Anfangsbuchstaben der Namen seiner Ent decker: Adelson-Velski u. Landes 1 Algorithmen und Datenstrukturen public int daten; // Dateninhalt der Knoten // Konstruktoren public PBBknoten() { this(0,null,null); } public PBBknoten(int datenElement) { this(datenElement, null, null ); } public PBBknoten(int datenElement, PBBknoten l, PBBknoten r) { daten = datenElement; links = l; rechts = r; } public PBBknoten getLinks() { return links; } public PBBknoten getRechts() { return rechts; } } public class PBB { static BufferedReader ein = new BufferedReader(new InputStreamReader( System.in)); // Instanzvariable PBBknoten wurzel; // Konstruktor public PBB(int n) throws IOException { if (n == 0) wurzel = null; else { int nLinks = (n - 1) / 2; int nRechts = n - nLinks - 1; wurzel = new PBBknoten(); wurzel.links = new PBB(nLinks).wurzel; wurzel.daten = Integer.parseInt(ein.readLine()); wurzel.rechts = new PBB(nRechts).wurzel; } } public void ausgPBB() { ausg(wurzel,0); } private void ausg(PBBknoten b, int nSpace) { if (b != null) { ausg(b.rechts,nSpace += 6); for (int i = 0; i < nSpace; i++) System.out.print(" "); System.out.println(b.daten); ausg(b.links, nSpace); 2 Algorithmen und Datenstrukturen } } // Test public static void main(String args[]) throws IOException { int n; System.out.print("Gib eine ganze Zahl n an, "); System.out.print("gefolgt von n ganzen Zahlen in "); System.out.println("aufsteigender Folge"); n = Integer.parseInt(ein.readLine()); PBB b = new PBB(n); System.out.print( "Hier ist das Resultat: ein perfekt balancierter Baum, "); System.out.println("die Darstellung erfogt um 90 Grad versetzt"); b.ausgPBB(); } } Schreibtischtest: Wird mit n = 10 aufgerufen, dann wird anschließend die Anzahl der Knoten berechnet, die sowohl in den linken als auch in den rechten Teilbaum eingefügt werden. Da der Wurzelknoten keinem Teilbaum zugeordnet werden kann, ergeben sich für die beiden Teilbäume (10 – 1) Knoten. Das ergibt nLinks = 4, nRechts = 5. Anschließend wird der Wurzelknoten erzeugt. Es folgt der rekursive Aufruf wurzel.links = new PBB(nLinks).wurzel; mit nLinks = 4. Die Folge davon ist: Einlesen von 4 Zahlen und Ablage dieser Zahlen im linken Teilbaum. Die danach folgende Zahl wird im Wurzelknoten abgelegt. Der rekursive Aufruf wurzel.rechts = new PBB(nRechts).wurzel; mit nRechts = 5 verarbeitet die restlichen 5 Zahlen und erstellt damit den rechten Teilbaum. Durch jedem rekursiven Aufruf wird ein Baum mit zwei ungefähr gleich großen Teilbäumen erzeugt. Da die im Wurzelknoten enthaltene Zahl direkt nach dem erstellen des linken Teilbaum gelesen wird, ergibt sich bei aufsteigender Reihenfolge der Eingabedaten ein binärer Suchbaum, der außerdem noch perfekt balanciert ist. Statische Optimierung Der Algorithmus zum Erstellen eines perfekt ausgeglichenen Baums kann zur statischen Optimierung binärer Suchbäume verwendet werden. Das Erstellen des binären Suchbaums erfolgt dabei nach der bekannten Verfahrensweise. Wird ein solcher Baum in Inorder-Folge durchlaufen, so werden die Informationen in den Baumknoten aufsteigend sortiert. Diese sortierte Folge ist Eingangsgröße für die statische Optimierung. Es wird mit der sortiert vorliegende Folge der Schlüssel ein perfekt ausgeglichener Baum erstellt. 3