IV.3. Suchalgorithmen 3.1. Einführung Gegeben sei ein Array item a[n +1], wobei die Datensätze an den Positionen 1,..., n stehen. Jedes Element hat eine Schlüsselkomponente a[i].key. Gesucht wird ein Element mit einem gegebenen Schlüssel k (i. Allg. sind die Schlüssel alle verschieden). Sequentielle Suche Die Schlüssel jedes Elementes werden mit k verglichen bis ein Element mit Schlüssel k gefunden wird. Stopper auf Position 0 erübrigt den Test, ob Array zu Ende ist. void sequentialsearch (int k) { int i; a[0] . key := k; {Stopper } i := n + 1; do i := i – 1 while ( a[i] . key != k); if (i != 0) // a[i] ist gesuchtes Element else // es gibt kein Element mit Schlüssel k } Offenbar ist hier T(n) = Tavg (n) = Θ(n) Im folgenden seien die Sätze nach ihren Schlüsseln aufsteigend sortiert: a[1].key ≤ " ≤ a[n].key . Binäre Suche Man betrachtet das mittlere Element a[m] . Falls k < a[m].key , so wird (nach dem gleichen Verfahren) in der linken Hälfte gesucht; falls k > a[m].key , so wird in der rechten Hälfte gesucht; falls k = a[m].key , so ist ein passendes Element gefunden. void binsearch(int p, r, k ) { // a[p], ..., a[r] wird durchsucht int m; if (p > r) // es gibt kein Element mit Schlüssel k else { m = (p + r) / 2; if (k < a[m] . key) binsearch(p, m – 1, k); else if (k > a[m] . key) binsearch(m + 1, r, k); else // a[m] ist gesuchtes Element } } Aufruf erfolgt durch binsearch(1, n, k) im HP Offenbar ist hier T (n) = Θ(log 2 n) . Man kann zeigen, daß auch Tavg (n) = Θ(log 2 n) . 3.2. Suchbäume Suchbäume sind binäre Bäume; die Knoten enthalten die Schlüssel (oder die Datensätze) und zwar mit der Eigenschaft: Für jeden Knoten p gilt: Alle Schlüssel im linken Teilbaum von p sind kleiner als der Schlüssel von p und alle Schlüssel im rechten Teilbaum von p sind größer als der Schlüssel von p. (Wir nehmen an, dass alle Schlüssel verschieden sind) Beispiel: 5 7 3 2 4 6 8 ist ein Suchbaum. Wir implementieren hier den Suchbaum als binären Baum, wobei aber noch insbesondere Methoden zum Suchen eines Schlüssels, zum Einfügen eines Schlüssels und zum Entfernen eines Schlüssels definiert werden. Beim Entfernen ist insbesondere der Fall zu behandeln, dass der zu entfernende Knoten einen linken und rechten Sohn hat. Dann wird der symmetrische Nachfolger des Schlüsselknotens gesucht – das ist der Knoten, der den kleinsten Schlüssel hat, der größer als der zu löschende ist – man findet ihn als am weitesten links stehenden Knoten im rechten Teilbaum des Schlüsselknotens. Man muss dann den Schlüssel k von p durch den Schlüssel y des symmetrischen Nachfolgers ersetzen und dann den symmetrischen Nachfolger streichen – So bleibt es ein Suchbaum. Beispiel: a) Im obigen Baum 7 streichen; symmetrischer Nachfolger ist 8 ⇒ 5 8 3 2 6 4 b) Im obigen Baum 5 streichen – symmetrischer Nachfolger ist 6 ⇒ 6 7 3 2 4 8 //File sbknoten.h //Definition der Template-Klasse Sbknoten #ifndef SBKNOTEN_H #define SBKNOTEN_H template<class ElementTyp> class Sbaum; //forward Daklaration template<class ElementTyp> class Sbknoten { friend class Sbaum<ElementTyp>; //Sbaum wird Freundklasse public: Sbknoten(const ElementTyp &); //Konstruktor ~Sbknoten(); //Destruktor private: ElementTyp element; //gespeichertes Element Sbaum<ElementTyp> *linksPtr; //Zeiger auf linken Teilbaum Sbaum<ElementTyp> *rechtsPtr; //Zeiger auf rechten Teilbaum }; //Konstruktor template<class ElementTyp> Sbknoten<ElementTyp>::Sbknoten(const ElementTyp & el) : element(el), linksPtr(new Sbaum<ElementTyp>), rechtsPtr(new Sbaum<ElementTyp>) {} template<class ElementTyp> Sbknoten<ElementTyp>::~Sbknoten() { delete linksPtr; delete rechtsPtr; } #endif //File sbaum.h //Definition der Template-Klasse Sbaum #ifndef SBAUM_H #define SBAUM_H #include <iostream> #include "sbknoten.h" using std::cout; enum Ordnung {PREORDER, INORDER, POSTORDER}; template<class ElementTyp> class Sbaum { public: Sbaum() : wurzelPtr(0) {}; //Konstruktor Sbaum(const ElementTyp & el) { //Konstruktor wurzelPtr = new Sbknoten<ElementTyp>(el);} Sbaum(const Sbaum & b) {copy(b);} //Kopierkonstrktor ~Sbaum() {delete wurzelPtr;} //Destruktor bool leer() const {return !wurzelPtr ;} ElementTyp & getEl() const { if(!leer()) return wurzelPtr->element; else cout << "Baum ist leer\n";} Sbaum * getLinks() const { if(!leer()) return wurzelPtr->linksPtr; else return 0;} Sbaum * getRechts() const { if(!leer()) return wurzelPtr->rechtsPtr; else return 0; } Sbaum & l_baum() const { if (!leer()) return *wurzelPtr->linksPtr; else cout << "Baum ist leer\n" } Sbaum & r_baum() const { if (!leer()) return *wurzelPtr->rechtsPtr; else cout << "Baum ist leer\n"; } Sbaum & operator= ( Sbaum & b); bool operator== (const Sbaum & b) const; void print(Ordnung ord) const; Sbaum<ElementTyp> * suchen(const ElementTyp & el); bool einfuegen(const ElementTyp & el); bool entfernen(const ElementTyp & el); private: Sbknoten<ElementTyp> *wurzelPtr; //Zeiger auf Wurzel //Hilfsfunktion void copy(const Sbaum& b); //Hilfsfunktion Sbaum<ElementTyp> * symmNachfolger(); }; template<class ElementTyp> void Sbaum<ElementTyp>::copy(const Sbaum & b) { if(b.leer()) wurzelPtr = 0; else { wurzelPtr = new Sbknoten<ElementTyp>(b.getEl()); l_baum().copy(b.l_baum()); r_baum().copy(b.r_baum()); } } template<class ElementTyp> Sbaum<ElementTyp> & Sbaum<ElementTyp>::operator= ( Sbaum & b) { if(b.wurzelPtr != wurzelPtr) { delete wurzelPtr; copy(b); } return *this; } template<class ElementTyp> bool Sbaum<ElementTyp>::operator== (const Sbaum & b) const { return (leer() && b.leer()) || (!leer() && !b.leer() && getEl() == b.getEl() && l_baum() == b.l_baum() && r_baum() == b.r_baum()); } template<class ElementTyp> void Sbaum<ElementTyp>::print(Ordnung ord) const { switch (ord) { case PREORDER: if( !leer() ) { cout << getEl() << ' '; l_baum().print(PREORDER); r_baum().print(PREORDER); } break; case INORDER: if( !leer() ) { l_baum().print(INORDER); cout << getEl() << ' '; r_baum().print(INORDER); } break; case POSTORDER: if( !leer() ) { l_baum().print(POSTORDER); r_baum().print(POSTORDER); cout << getEl() << ' '; } } } template<class ElementTyp> Sbaum<ElementTyp> * Sbaum<ElementTyp>::suchen(const ElementTyp & el) { if (leer()) return 0; if (el == getEl()) return this; if (el < getEl()) return l_baum().suchen(el); else return r_baum().suchen(el); } template<class ElementTyp> bool Sbaum<ElementTyp>::einfuegen(const ElementTyp & el) { if(leer()) { *this = Sbaum(el); return true; } else if (el < wurzelPtr->element) l_baum().einfuegen(el); else if (el > getEl()) r_baum().einfuegen(el); else { cout << "Duplikat\n"; return false; } } template<class ElementTyp> Sbaum<ElementTyp> * Sbaum<ElementTyp>::symmNachfolger() { Sbaum<ElementTyp> * hilf = wurzelPtr->rechtsPtr; while(!hilf->l_baum().leer()) hilf = hilf->getLinks(); return hilf; } template<class ElementTyp> bool Sbaum<ElementTyp>::entfernen(const ElementTyp & el) { if(leer()) return false; if(el < getEl()) return l_baum().entfernen(el); if(el > getEl()) return r_baum().entfernen(el); if(l_baum().leer()) { Sbknoten<ElementTyp> * hilfsPtr = wurzelPtr; wurzelPtr = r_baum().wurzelPtr; hilfsPtr->rechtsPtr = 0; delete hilfsPtr; return true; } if(r_baum().leer()) { Sbknoten<ElementTyp> * hilfsPtr = wurzelPtr; wurzelPtr = l_baum().wurzelPtr; hilfsPtr->linksPtr = 0; delete hilfsPtr; return true; } Sbaum<ElementTyp> * s = symmNachfolger(); ElementTyp elm = s->getEl(); wurzelPtr->element = elm; s->entfernen(elm); } #endif //File treiber_sbaum.cpp #include <iostream> using std::cout; using std::cin; #include "sbknoten.h" #include "sbaum.h" int main() { Sbaum<int> sbaumi, sbaumi1; Sbaum<int> * ptr; cout << "Baum ist leer: " << sbaumi.leer() << '\n'; sbaumi.entfernen(1); cout << "Baum ist leer: " << sbaumi.leer() << '\n'; sbaumi = Sbaum<int>(5); sbaumi.print(INORDER); cout << "\n\n\n"; sbaumi.l_baum() = Sbaum<int>(3); sbaumi.r_baum() = Sbaum<int>(7); sbaumi.l_baum().r_baum() = Sbaum<int>(4); sbaumi.print(PREORDER); cout << '\n'; sbaumi.print(INORDER); cout << '\n'; sbaumi.print(POSTORDER); cout << '\n'; ptr = sbaumi.suchen(5); if (ptr != 0) cout << (*ptr).getEl() <<'\n'; ptr = sbaumi.suchen(6); if (ptr != 0) cout << (*ptr).getEl() <<'\n'; sbaumi.einfuegen(6); sbaumi.print(INORDER); cout << '\n'; sbaumi.einfuegen(1); sbaumi.print(INORDER); cout << '\n'; sbaumi.print(PREORDER); cout << '\n'; sbaumi.entfernen(5); sbaumi.print(PREORDER); cout << '\n'; sbaumi.print(POSTORDER); cout << '\n'; sbaumi.print(INORDER); cout << "\n\n"; cout << "Baeume gleich? " << (sbaumi == sbaumi1) <<'\n'; Sbaum<double> sbaumd(4.5); sbaumd.print(PREORDER); cout << '\n'; return 0; } Die Ausgabe des Programmes ist: Baum ist leer: 1 Baum ist leer: 1 5 5 3 4 5 3 1 5 6 1 1 3 4 7 4 5 7 3 7 5 4 3 3 3 4 3 5 4 1 1 3 4 6 5 4 4 7 6 7 6 7 7 6 7 6 7 Baeume gleich? 0 4.5 Press any key to continue Ein solcher Suchbaum ist aber nur dann eine effiziente Struktur, wenn er geringe Tiefe hat – am besten wäre ein vollständiger Binärbaum (dann dauert die Suche höchstens O( log 2 (n) ). Wenn aber durch Einfügen ein „natürlicher“ Baum erzeugt wird, hängt seine Gestalt von der Reihenfolge der Eingabe ab: a) Für die Reihenfolge {1, 3, 14, 15, 27, 39} ⇒ 1 3 14 15 27 39 b) Für die Reihenfolge {15, 39, 3, 27, 1, 14} 15 39 3 1 14 27 Frage: Wie gut ist ein solcher natürlicher Baum im Mittel? (über alle n! möglichen Reihenfolgen gemittelt) Ein Maß für die Güte ist die interne Pfadlänge I = ∑ (Tiefe( p) + 1) p (summiert über alle Knoten p) und die durchschnittliche Suchpfadlänge I = I / n . Satz. Die durchschittliche Suchpfadlänge I eines zufällig erzeugten binären Suchbaumes mit n Knoten ist im Mittel um ca. 40% schlechter als die eines vollständigen Suchbaumes. Trotzdem kann Einfügen und Entfernen einen Suchbaum „degenerieren“ lassen. Außerdem ist im schlechtesten Fall sowieso der Aufwand Ω(n) . ⇒ Balancierte Binärbäume – AVL-Bäume (Adelson-Velskij und Laudis) Ein AVL-Baum ist ein binärer Suchbaum, wenn für jeden Knoten p des Baumes gilt, daß sich die Tiefe des linken Teilbaumes von p von der Tiefe des rechten Teilbaumes von p höchstens um 1 unterscheidet. Man kann zeigen, daß solche AVL-Bäume eine Tiefe ≤ 1.44... log 2 n Haben. Sie sind also effizient! Es gibt (raffinierte) Algorithmen zum Einfügen und Entfernen von Schlüsseln, die die AVL-Eigenschaft erhalten(!) und überdies von der Komplexität O(log n) sind! Def.