IV.3. Suchalgorithmen

Werbung
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.
Herunterladen