Algorithmen und Datenstrukturen Übung 9: Bayer-Bäume Bayer-Baumknoten Die Klasse CBNode zeigt die Implementierung eines Bayer-Baumknotens1. Der Konstruktor dieser Klasse leistet die Arbeit und bekommt dazu einen Übergabeparameter (M) geliefert, der die Ordnung des (M-ären) Bayer-Baums beschreibt. import java.util.*; /* * The CBNode class represents on single Node of the Bayer Tree. */ public class CBNode { // Instanzvariable protected Vector key, nodeptr, initkey, initvec; int count; // Konstruktor /* * constructs a single Bayer Tree node with M references to subnodes. */ CBNode(int M) { // System.out.println("CBNode(): constructor invoked!"); nodeptr = new Vector(); initvec = new Vector(); initvec = null; key = new Vector(); initkey = new Vector(); initkey = null; for(int i = 0; i <= M; i++) { nodeptr.addElement((Object)initvec); // System.out.println(i + "ter Nodepointer erzeugt. Wert: " + nodeptr.elementAt(i)); } for(int j = 0; j <= M - 1; j++) { key.addElement((Object)initkey); // System.out.println(j + "ter Key erzeugt. Wert: " + key.elementAt(j)); } count = 0; // System.out.println("count Wert: " + count); } } Bayer-Baum Die Klasse CBayerTree umfaßt den Algorithmus2 zum Einfügen und Löschen von Bayer-Baumknoten 1 2 Vgl. PR44200, CBNode.java Vgl. PR44200, CbayerTree.java 1 Algorithmen und Datenstrukturen class CBayerTree { // protected Instanzvariablen /* * Die Variable MAERER definiert die Ordnung des Bayer-Baums, * d.h. der Nachfolgerknoten bzw. Teilbäume eines Knoten * (max. MAERER). Jeder Knoten besteht aus MAERER Referenzen * auf Subknoten und MAERER - 1 Schlüsseln. */ protected int MAERER = 5; /* * contains the reference to the CVisualizeTree class. It will be set in * the constructor. */ protected CVisualizeTree mpCVisualizeTree; /* * contains the reference to the CExtendedCanvas class. It will be set in * the constructor. */ protected CExtendedCanvas mpCExtendedCanvas; /* * Referenz zum Wurzelknoten während der Lebensdauer des Baums. */ protected CBNode root; /* * speichert den neuen Schluessel, der in den baum eingefügt werden soll. */ protected Integer newValue; /* * Indikator fuer den Wurzelknoten. */ private int rootflag = 0; // Public Operationen // Der Konstruktor umfasst zwei Paramerte zur Visualisierung /* public CBayerTree(CExtendedCanvas pCExtendedCanvas, CVisualizeTree pCVisualizeTree); Einfuegen public int Insert(Integer value); ; Rueckgabe: -1, falls Fehlversuch; anderenfalls 0 Parameter: einzufuegender Schluessel Loeschen public int Delete(Integer delValue); ; Rueckgabe: -1, falls Fehlversuch; anderenfalls 0 Parameter: einzufuegender Schluessel public boolean searchkey(int key); Rueckgabe: true, falls der Schlüssel gefunden wurde; anderenfalls false Geschuetzte Methoden protected CBNode insrekurs(CBNode tempnode, Integer insValue); Private Methoden private int delrekurs(CBNode delNode, Integer value); ..... */ } Operationen für Bayer-Bäume 2 Algorithmen und Datenstrukturen Suchen eines Schlüssels Gegeben ist der folgende Ausschnitt eines B-Baums mit N Schlüsseln: +-------------------------------+ | | | Z0S1Z1S2 .......... ZN-1SNZN | | | +-------------------------------+ Beim Suchen eines Schlüssels X können folgende Situationen auftreten: 1) S[I] < X < S[I + 1] für 1 <= I <= N Die Suche wird fortgesetzt in dem Knoten, auf den Z[I]^ zeigt. 2) S[N] < X Die Suche wird im Knoten fortgesetzt, auf den Z[N]^ zeigt. 3) X < S[1] Die Suche wird im Knoten fortgesetzt, auf den Z[0]^ zeigt. Hat einer der angegebenen Referenzen den Wert „null“, dann existiert in dem vorliegenden Baum kein Teilbaum, der diesen Schlüssel enthält (d.h. die Suche ist beendet). /* *Rueckgabe ist true, falls der Schlüssel gefunden wurde; * anderenfalls false. */ public boolean searchkey(int key) { boolean found = false; int i = 0, n; CBNode node = root; while(node != null) { i = 0; n = node.count; // search key in actual node while(i < n && key > ((Integer)node.key.elementAt(i)).intValue()) { i++; } // end while if(i < n && key == ((Integer)node.key.elementAt(i)).intValue()) { found = true; } if(node.nodeptr.elementAt(i) != null) { mpCVisualizeTree.MoveLevelDown(i); } node = (CBNode) node.nodeptr.elementAt(i); } // end while (node != null) return found; } // end searchkey Methode Einfügen Bsp.: Der Einfügevorgang in einem Bayer-Baum der Klasse 2 1) Aufnahme der Schlüssel 1, 2, 3, 4 in den Wurzelknoten 3 Algorithmen und Datenstrukturen 1 3 2 4 2) Zusätzlich wird der Schlüssel mit dem Wert 5 eingefügt 1 3 2 4 5 Normalerweise würde jetzt rechts von "4" ein neuer Knotem erzeugt. Das würde aber zu einem Knotenüberlauf führen. Nach dieser Erweiterung enthält der Knoten eine ungerade Zahl von Elementen ( 2 M 1 ). Dieser große Knoten kann in 2 Söhne zerlegt werden, nur das mittlere Element verbleibt im Vaterknoten. Die neuen Knoten genügen wieder den B-Baum-Eigenschaften und könnem weitere Daten aufnehmen. 3 1 2 4 5 Beschreibung des Algorithmus für das Einfügen Ein neues Element wird grundsätzlich in einen Blattknoten eingefügt. Ist der Knoten mit Schlüsseln voll, so läuft bei der Aufnahme eines weiteren Schlüssels der Knoten über. 2M +---------------------------------------------+ | | | ........... SX-1ZX-1SXZX ..... | | | +---------------------------------------------+ +----------------------------------------------+ | | | Z0S1Z1 .... ZM-1SMZMSM+1 ....... Z2MS2M+1 | | Überlauf | +----------------------------------------------+ Der Knoten wird geteilt: Die vorderen Schlüssel verbleiben im alten Knoten, der Schlüssel mit der Nummer M+1 gelangt als Trennschlüssel in den Vorgängerknoten. Die M Schlüssel mit den Nummern M+2 bis 2 M 1 kommen in den neuen Knoten. +-----------------------------+ | ....SX-1ZX-1SM+1ZYSXZX .... | +-----------------------------+ +------------------+ |Z0S1 .... ZM-1SMZM| +------------------+ +-----------------------------+ |ZM+1SM+2ZM+2 ..... S2M+1Z2M+1| +-----------------------------+ Die geteilten Knoten enthalten genau M Elemente. Das Einfügen eines Elements in der vorangehenden Seite kann diese ebenfalls zum Überlaufen bringen und somit die Aufteilung fortsetzen. Der B-Baum wächst demnach von den Blättern bis zur Wurzel. /* *Einfügen eines neuen Schlüssels. Rückgabe ist –1, falls * es misslingt, anderenfalls 0 4 Algorithmen und Datenstrukturen * Parameter: value einzufuegender Wert */ public int Insert(Integer value) { if(root == null) { root = new CBNode(MAERER); root.key.setElementAt((Object)value, 0); root.count = 1; } // end if else { if(searchkey(((Integer)value).intValue()) == true) { //System.out.println("double key found and will be ignored!"); return -1; } // end if CBNode result; result = insrekurs(root, value); if(result != null) { CBNode node = new CBNode(MAERER); node.key.setElementAt(newValue, 0); node.nodeptr.setElementAt(root, 0); node.nodeptr.setElementAt(result, 1); node.count = 1; root = node; } // end if(result) } // end else mpCVisualizeTree.DeleteRootKnot(); mpCExtendedCanvas.repaint(); rootflag = 0; this.drawTree(root); mpCExtendedCanvas.repaint(); return 0; } // end Insert() Methode Die Methode „Insert“ ruft „insrekurs()“ auf. Diese rekursive Methode leistet die eigentliche Arbeit /* * der neue Wert wird rekursiv in den Baum eingebracht */ protected CBNode insrekurs(CBNode tempnode, Integer insValue) { CBNode result; result = null; newValue = insValue; if(tempnode.nodeptr.elementAt(0) != null) // kein Blatt -> Rekursion { int pos = 0; while(pos < tempnode.count && newValue.intValue() > ((Integer)tempnode.key.elementAt(pos)).intValue()) { pos++; } // end while result = insrekurs((CBNode)tempnode.nodeptr.elementAt(pos), newValue); if(result == null) { return null; // if result = null: nothing has to be inserted into // node-> finished! } // end if(resul == null) } // end if(tempnode.nodeptr.elementAt(0) != null) 5 Algorithmen und Datenstrukturen // insert a element CBNode node = null; int flag = 0; int s = tempnode.count; if(s >= MAERER - 1) // split the knot { tempnode.count = (MAERER - 1) / 2; node = new CBNode(MAERER); node.count = (MAERER - 1) / 2; for(int d = ((MAERER - 1) / 2); d > 0;) { if(flag != 0 || ((Integer)tempnode.key.elementAt(s - 1)).intValue() > newValue.intValue()) { node.nodeptr.setElementAt(tempnode.nodeptr.elementAt(s), d); node.key.setElementAt(tempnode.key.elementAt(--s), --d); } // end if(flag != 0 ... else { node.nodeptr.setElementAt(result, d); node.key.setElementAt(newValue, --d); flag = 1; } // end else } // end if(s >= MAERER - 1) if(flag != 0 || ((Integer)tempnode.key.elementAt(s - 1)).intValue() > newValue.intValue()) { node.nodeptr.setElementAt(tempnode.nodeptr.elementAt(s), 0); } // end if else { node.nodeptr.setElementAt(result, 0); } // end else } // end if(s >= MAERER - 1) else { tempnode.count++; } // end else // shift for(; s > 0 && ((Integer)tempnode.key.elementAt(s - 1)).intValue() > newValue.intValue(); s--) { tempnode.nodeptr.setElementAt(tempnode.nodeptr.elementAt(s), s + 1); tempnode.key.setElementAt(tempnode.key.elementAt(s - 1), s); } // end for tempnode.key.setElementAt(newValue, s); tempnode.nodeptr.setElementAt(result, s + 1); newValue = (Integer) tempnode.key.elementAt((MAERER - 1) / 2); return node; } // end insrekurs() methode Löschen Grundsätzlich ist zu unterscheiden: 1. Das zu löschende Element ist in einem Blattknoten 2. Das Element ist nicht in einem Blattknoten enthalten. In diesem Fall ist es durch eines der benachbarten Elemente zu ersetzen. Entlang des rechts stehenden Zeigers Z ist hier zum Blattknoten hinabzusteigen und das zu löschende Element durch das äußere linke Element von Z zu ersetzen. 6 Algorithmen und Datenstrukturen Auf jeden Fall darf die Anzahl der Schlüssel im Knoten nicht kleiner als M werden. /* * loescht einen Schlüssel. Rückgabe ist * anderenfalls 0. * * Parameter: zu löschender Wert */ public int Delete(Integer delValue) -1, falls es misslingt; { mpCVisualizeTree.MoveToRoot(); if(searchkey(((Integer)delValue).intValue()) == false) { return -1; } // end if if(delrekurs(root, delValue) == 0) { CBNode temp = root; root = (CBNode)root.nodeptr.elementAt(0); temp = null; } // end if mpCVisualizeTree.DeleteRootKnot(); mpCExtendedCanvas.repaint(); rootflag = 0; this.drawTree(root); mpCExtendedCanvas.repaint(); return 0; } // end Delete() Methode Die eigentliche Arbeit beim Löschen übernimmt die rekursive Methode „delrekurs()“ /* * deletes the value rekursively from the tree. * */ private int delrekurs(CBNode delNode, Integer value) { int result = 0, pos, found = 0; Vector nullVec = new Vector(); nullVec = null; if(delNode != null) { CBNode node; for(pos = 0; pos < delNode.count; pos++) { if(value.intValue() <= ((Integer)delNode.key.elementAt(pos)).intValue()) { if(value.intValue() < ((Integer)delNode.key.elementAt(pos)).intValue()) { result = delrekurs((CBNode)delNode.nodeptr.elementAt(pos), (Integer)value); } // end if else { if(delNode.nodeptr.elementAt(0) == null) // leaf node! { for(; (pos + 1) < delNode.count; pos++) { 7 Algorithmen und Datenstrukturen delNode.key.setElementAt(delNode.key.elementAt(pos + 1), pos); } // end for //System.out.println("Ende weil Blatt!"); return --delNode.count; } // end if else { node = (CBNode)delNode.nodeptr.elementAt(pos + 1); while(node.nodeptr.elementAt(0) != null) { node = (CBNode)node.nodeptr.elementAt(0); } // end while delNode.key.setElementAt(node.key.elementAt(0), pos); //System.out.println("Tausch, loesche rechts"); pos++; result = delrekurs((CBNode)delNode.nodeptr.elementAt(pos), (Integer)delNode.key.elementAt(pos - 1)); } // end else } // end else found = 1; break; } // end if } // end for if(found == 0) { result = delrekurs((CBNode)delNode.nodeptr.elementAt(pos), (Integer)value); } // end if if(result < ((MAERER - 1) / 2)) { CBNode temp; int l = 0, r = 0; if(pos > 0) { l = ((CBNode)delNode.nodeptr.elementAt(pos - 1)).count; } // end if if(pos < delNode.count) { r = ((CBNode)delNode.nodeptr.elementAt(pos + 1)).count; } // end if node = (CBNode)delNode.nodeptr.elementAt(pos); if(l > (MAERER - 1) / 2) // steal a key from left { //System.out.println("klaue einen Schluessel von links"); temp = (CBNode)delNode.nodeptr.elementAt(pos - 1); for(int i = node.count; i > 0; i--) { node.key.setElementAt(node.key.elementAt(i - 1), i); node.nodeptr.setElementAt(node.nodeptr.elementAt(i), i + 1); } // end for node.nodeptr.setElementAt(node.nodeptr.elementAt(0), 1); node.count++; node.key.setElementAt(delNode.key.elementAt(pos - 1), 0); node.nodeptr.setElementAt(temp.nodeptr.elementAt(temp.count), 0); delNode.key.setElementAt(temp.key.elementAt(--temp.count), pos - 1); } // end if else { if(r > (MAERER - 1) / 2) // steal a key from right { // System.out.println("klaue Schluessel von rechts"); temp = (CBNode)delNode.nodeptr.elementAt(pos + 1); node.key.setElementAt(delNode.key.elementAt(pos), node.count); 8 Algorithmen und Datenstrukturen node.count++; node.nodeptr.setElementAt(temp.nodeptr.elementAt(0), node.count); delNode.key.setElementAt(temp.key.elementAt(0), pos); for(int i = 1; i < temp.count; i++) { temp.key.setElementAt(temp.key.elementAt(i), i - 1); temp.nodeptr.setElementAt(temp.nodeptr.elementAt(i), i - 1); } // end for temp.nodeptr.setElementAt(temp.nodeptr.elementAt(temp.count), temp.count - 1); temp.count--; } // end if else { //System.out.println("Knoten verschmelzen !"); if(r == 0) { pos--; } // end if node = (CBNode)delNode.nodeptr.elementAt(pos); temp = (CBNode)delNode.nodeptr.elementAt(pos + 1); node.key.setElementAt(delNode.key.elementAt(pos), node.count++); node.nodeptr.setElementAt(temp.nodeptr.elementAt(0), node.count); for(int i = 0; i < temp.count; i++) { node.key.setElementAt(temp.key.elementAt(i), node.count++); node.nodeptr.setElementAt(temp.nodeptr.elementAt(i + 1), node.count); } // end for // System.out.println( // "Jetzt kann der Knoten rechts daneben entfernt werden!"); delNode.count--; // delNode.nodeptr.elementAt(pos + 1) = null; // geht das so? delNode.nodeptr.setElementAt((Object)nullVec, pos + 1); while(pos < delNode.count) { delNode.key.setElementAt(delNode.key.elementAt(pos + 1), pos); pos++; delNode.nodeptr.setElementAt(delNode.nodeptr.elementAt(pos + 1), pos); } // end while } // end else } // end else } // end if return delNode.count; } // end if else { return 0; } // end else } // end delrekurs() Methode 9