¢¡¤£¦¥¤§© ¤

Werbung
http://www.mpi-sb.mpg.de/~hannah/info5-ws00
IS
UN
R
S
WS 2000/2001
E R SIT
S
Bast, Schömer
SA
IV
A
Grundlagen zu
Datenstrukturen und Algorithmen
A VIE N
Lösungsvorschläge für das 7. Übungsblatt
Letzte Änderung am 13. Dezember 2000
Aufgabe 1
/* Die beiden sortierten Listen a und b werden sortiert in die Liste
c eingetragen. Dabei muss sichergestellt sein, dass in c nicht bereits
irgendwas steht. */
void slist_merge (slist& a, slist& b, slist& c)
{
handle d = c.end();
// handle to last sorted element
while (!a.empty() && !b.empty())
// while sorting still needed
{
if ((a.begin()->inf) <= (b.begin()->inf)) // compare data of list heads
{
c.insert_after(d,a.begin()->inf);
// insert at end of sorted list
d = d->next;
// new last sorted element
a.pop_front();
// delete smallest element
}
else
{
c.insert_after(d,b.begin()->inf);
// insert at end of sorted list
d = d->next;
// new last sorted element
b.pop_front();
// delete smallest element
}
}
if (a.empty())
{
while (!b.empty())
{
c.insert_after(d,b.begin()->inf);
d = d ->next;
b.pop_front();
}
}
else
// determine not empty list
// append c with rest of b
// s.a.
{
while (!a.empty())
{
c.insert_after(d,a.begin()->inf);
d = d->next;
a.pop_front();
}
}
delete a;
delete b;
// append c with rest of a
// s.a.
// clear memory
}
/* Die Liste a wird sortiert. */
void slist_mergesort(slist& a)
{
slist b;
slist c;
// first half of a
// second half of a
if (a.empty() || a.begin()->next == a.end()) // a empty or only one element
{
return (a);
// => no sorting needed
}
handle mitte = a.begin();
//
handle vor_ende = a.begin();
handle ende = a.begin()->next;
//
while(ende != a.end() && ende->next
{
mitte = mitte->next;
//
vor_ende = ende->next;
ende = ende->next->next;
//
}
//
mitte startet auf dem 1. Element
ende startet auf dem 2. Element
!= a.end())
mitte laeuft immer um 1 weiter,
ende um 2
dann sollte mitte in der Mitte stehen.
b.head = a.head;
// b ist die erste Haelfte
c.head = mitte->next;
// c ist die zweite Haelfte
mitte->next = b.head;
if(ende == a.end()) vor_ende->next = c.head;
else ende->next = c.head;
a.clear();
slist_mergesort(b);
slist_mergesort(c);
slist_merge(b,c,a);
// sort first half
// sort second half
// merge sorted halfs together
delete b;
delete c;
return(d);
// clear memory
// return handle to result
}
Aufgabe 2
handle find(const T& x)
{
handle v = head;
handle w = v->next;
if(w->inf == x)
return w;
//wenn Element vorne steht,
//ist keine Transposition notwendig
while (w->next != head) //untersuche jeweils w->next auf Wert x
{
if (w->next->inf == x)
{
handle u = w->next;
w->next = u->next;
u->next = v->next;
v->next = u;
return u;
}
w = w->next;
v = v->next;
}
return 0;
}
handle find(const T& x)
{
handle v = head;
handle w = v->next;
while (w != head)
//untersuche jeweils w auf Wert x
{
if (w->inf == x)
{
if(w != head->next) //nur wenn Element nicht schon vorne steht,
{
//mache move-to-front
v->next = w->next;
w->next = head->next;
head->next = w;
}
return w;
}
w = w->next;
v = v->next;
}
return 0;
}
Aufgabe 3
1. Seien a, b, c Elemente aus den jeweiligen Teilbäumen A, B bzw. C, dann gilt wegen (I)
a ≤ v ≤ b ≤ u ≤ c vor der Rotation. Die Rotation ändert nichts an der Ordnung der
Elemente untereinander, also ist noch immer a ≤ v ≤ b ≤ u ≤ c, und (I) ist wirklich
invariant unter diesen Operationen.
2. Ein Knoten des Suchbaums besteht aus dem zu speichernden Element selbst sowie Zeigern
auf den linken und rechten Teilbaum und einem Zeiger auf den Elternknoten:
template<class T> class TreeNode {
public:
T inf;
TreeNode *left, *right, *parent;
TreeNode (T info, TreeNode *p) {
inf = info;
left = 0;
right = 0;
parent = p;
}
~TreeNode() {}
};
Ein Suchbaum besteht aus einem Verweis auf den Wurzelknoten sowie (mindestens) den
folgenden Methoden:
template<class T> class SearchTree {
TreeNode<T> *root;
public:
SearchTree() { root = 0; }
~SearchTree() {}
void insert (T n) {
TreeNode<T> *p = 0;
TreeNode<T> *t = root;
while (t) {
p = t;
if (n < t->inf)
t = t->left;
else
t = t->right;
}
t = new TreeNode<T> (n,p);
// find leaf position
if (!p)
root = t;
else
if (n < p->inf)
p->left = t;
else
p->right = t;
// tree was empty
}
TreeNode<T> *find (T n) {
...
}
void rotateLeft (TreeNode<T> *v) {
...
}
void rotateRight (TreeNode<T> *v) {
...
}
};
Die Methoden zum links- bzw. rechtsrotieren sind symmetrisch, die Variablen sind benannt
wie im Bild zur Aufgabenstellung.
void rotateLeft (TreeNode<T> *v) {
TreeNode<T> *u = v->right;
// assumes right child of v exists
v->right = u->left;
// turn B into v’s right subtree
if (u->left)
u->left->parent = v;
u->parent = v->parent;
// link v’s parent to u
if (! v->parent)
root = u;
else
if (v == v->parent->left)
v->parent->left = u;
else
v->parent->right = u;
u->left = v;
// finally make v left child of u
v->parent = u;
}
void rotateRight (TreeNode<T> *u) {
TreeNode<T> *v = u->left;
// assumes left child of u exists
u->left = v->right;
// turn B into u’s left subtree
if (v->right)
v->right->parent = u;
v->parent = u->parent;
// link u’s parent to v
if (! u->parent)
root = v;
else
if (u == u->parent->left)
u->parent->left = v;
else
u->parent->right = v;
v->right = u;
u->parent = v;
// finally make u right child of v
}
Die (rekursive) Version der find (T, TreeNode<T>*)-Funktion (bezüglich eines Teilbaums)
liefert einen Zeiger auf das Element im Suchbaum zurück, bzw. einen nil-Zeiger, falls das
Element nicht vorhanden ist. Zum implementieren der transposition-Heuristik kann dieser
nun benutzt werden, um ein rotate durchzuführen. Abhängig davon, ob der gefundene
Knoten die Wurzel des linken oder rechten Teilbaums seines Elternknotens ist, wird dies ein
rotateRight oder rotateLeft sein.
TreeNode<T> *find (T n, TreeNode<T> *t) {
if (!t || t->inf == n)
return t;
if (t->inf > n)
return (find (n, t->left));
return (find (n, t->right));
}
TreeNode<T> *find (T n) {
TreeNode<T> *t = find (n, root);
if (t && t->parent) {
if (t->parent->left == t)
rotateRight(t->parent);
else
rotateLeft(t->parent);
}
return (t);
}
// t exists and is not the root
// t left child
// t right child
Bei der move to root-Heuristik werden Rotationen entlang des gesamten Suchpfades durchgeführt. In jedem Schritt vermindert sich die Tiefe von t im Baum um 1, damit ist die
Terminierung gewährleistet:
TreeNode<T> *find (T n) {
TreeNode<T> *t = find (n,root);
while (t && t->parent) {
if (t->parent->left == t)
rotateRight(t->parent);
else
rotateLeft(t->parent);
}
return (t);
}
// have not reached the root
// t left child
// t right child
3. Bei der transposition-Heuristik ist einfach einzusehen, dass sich die Tiefe des Baums nach
dem Aufruf der find-Funktion um maximal 1 erhöhen kann. Da nur ein einziges rotate
durchgeführt wird, dieses die Tiefe von B erhält, die Tiefe von C (im rotateRight-Fall) um
1 erhöht, und die von A sogar um 1 erniedrigt wird, kann die Tiefe des gesamten Suchbaums
sich um höchstens 1 erhöhen.
Überraschenderweise gilt diese Abschätzung auch für die move to root-Heuristik. Genauer
kann man zeigen: gibt es eine Folge von n rotate-Operationen, die einen Knoten u zur
Wurzel bringen, und die erste Operation erhöht die Tiefe, dann wird sie durch nachfolgende
Operationen nicht mehr weiter erhöht. Die wesentliche Beobachtung ist die folgende: Die
Tiefe des gesamten Baumes erhöht sich durch ein rotateRight (und analog ein rotateLeft)
nur, falls der Teilbaum C zur Tiefe des gesamten Baumes S beiträgt, d.h. dS (u) + dS (C) =
dS (S). Die Tiefe von Knoten im Teilbaum mit Wurzel v wird dabei höchstens kleiner, die
Tiefe aller anderen Knoten (i.e. nicht in A,B,C oder D enthalten) bleibt gleich.
S
S’
w
w
u
v
v
u
A
D
A
B
C
D
B
C
Bei einem nachfolgenden rotate (um die Kante (w, v)) wird nun entsprechend die Tiefe des
Teilbaums mit Wurzel v höchstens kleiner, die von D dagegen erhöht sich maximal um 1 und
somit gilt dS (S)+1 ≥ dS 0 (S 0 ) ≥ dS 0 (u)+dS 0 (C) ≥ dS 00 (S 00 ). Ein formaler Beweis geht mittels
Induktion über die Anzahl der durchgeführten rotate-Operationen, indem man zeigt:
Sei v1 , . . . , vn ein Pfad von v0 (dem gefundenen Element) zur Wurzel im Suchbaum S, und
sei
rotate
rotate
rotate
S = S0 −→ S1 −→ . . . −→ Sn
die Folge der durch die n rotate-Operationen entstehenden Suchbäume. Dann gilt für alle
1 ≤ i ≤ n:
Für alle Blätter b von Si im Teilbaum mit Wurzel vi ist d(b) ≤ d(S) + 1. Für alle Blätter b
von Si , die nicht im Teilbaum mit Wurzel vi sind, ist d(b) ≤ d(S).
Den Induktionsanfang haben wir oben schon gesehen: Bei Durchführung von einem rotate
wird die Tiefe des unter dem Knoten v1 liegenden Unterbaumes maximal um 1 erhöht, alle
anderen Blätter bleiben entweder gleich oder erhalten eine um 1 niedrigere Tiefe.
Für den Induktionsschritt nehmen wir an, daß wir bereits k Rotationen durchgeführt haben
und daß die Induktionsbehauptung für vk gilt. Jetzt wird vk mit vk+1 rotiert, dabei liegt
vk+1 über vk . Wenn man sich die beiden Rotationen ansieht, sieht man, daß die beiden
Unterbäume von vk ihre Tiefe behalten oder die Tiefe um 1 erniedrigt wird. Es gilt also
nach der Rotation immer noch für alle Blätter b im Teilbaum mit Wurzel vk : d(b) ≤ d(S)+1.
Die Blätter aus dem zweiten Teilbaum von vk+1 können evtl. ihre Tiefe um 1 erhöhen. Da
sie nach der Induktionsvoraussetzung Tiefe ≤ d(S) hatten, haben sie nach der Rotation
Tiefe ≤ d(S) + 1.
Also haben wir für alle Blätter b im Teilbaum mit Wurzel vk+1 gezeigt, daß ihre Tiefe
≤ d(S) + 1 ist. Alle anderen Blätter hatten vorher schon (nach Induktionsvoraussetzung)
Tiefe ≤ d(S). Bei der Rotation werden sie nicht verändert, so daß sie ihre Tiefe erhalten.
Es ist nicht sinnvoll, das zuletzt gefundene Element direkt als neue Wurzel zu setzen, da
nicht klar ist, wie in diesem Fall die Suchbaumeigenschaft aufrecht erhalten werden kann:
Um ein Element als neue Wurzel zu setzen (ohne weitere Umstrukturierung des restlichen
Baumes) müsste es entweder ein minimales oder ein maximales Element des gesamten Suchbaumes sein, was i.a. nicht gegeben ist.
Herunterladen