1 = ∈ ∈∃ = ∈ ∧∈ = = K = = = ∈ ∈∀

Werbung
Definition 3:
Ein Baum T ist ein Tupel
T = (N, E)
mit der Menge N von Knoten(-punkten)
N = {ni | i ∈ Ν}, 0 ≤ card ( N ) < ∞
und der Menge E der Kanten(-linien)
E ⊆ N ×N
Begriffe & Eigenschaften (Teil 1)
Ø
Ein besonderer Knoten ist die Wurzel (root) r:
sie besitzt - im Gegensatz zu einem „normalen“ Knoten - keinen
Vorgänger (parent) p sondern nur 0 ... k Nachfolger (children) c i
p
X
r
r
p
c
p
(p,c) ∈ E
c
c1
c2
c3
...
ck
Geschwister (siblings)
Wurzel (root) r:
R = { r | ∃ p ∈ N : ( p , r ) ∈ E },
Vorgänger (parent) p,
Nachfolger (children) c i
Geschwister:
}
card ( R ) = 1
∀c ∈ N \ R : card ({ p | ( p , c ) ∈ E }) = 1
parent ( c i ) = p ,
child ( p ) = c i
S p = { c1 K c k } = { c i | c i ∈ N ∧ ( p , c i ) ∈ E }
1
Ø
Jeder Knoten ausser der Wurzel hat genau einen parent (Vorgänger)
Ø
Ein Baum ist zusammenhängend, d.h. man kann von einem beliebigen
Knoten aus über eine Folge von parent-Knoten zur Wurzel
„aufsteigen“. Ein „Aufstieg“ oder „Abstieg“ im Baum über m Knoten
hinweg heisst Pfad mit Länge m-1.
Die Höhe h eines Knotens ist die Länge des Pfades von der Wurzel bis
zu diesem Knoten. Für jeden Knoten gibt es genau 1 Pfad von der
Wurzel zu diesem Knoten. Die Höhe h des Baumes ist der längstm ögliche Pfad im Baum, also das Maximum aller Höhen.
Ø
Ein „normaler“ Knoten (mit Vorgänger und Kind(ern)) heisst innerer
Knoten, ein Knoten ohne Kind(er) heisst Blatt.
Der Grad eines Knotens ist die Anzahl seiner Kinder. Ein Blatt besitzt
also den Grad 0 (deg(p)=0 ⇔ Knoten p ist Blatt). Der Grad eines
Baumes T ist das Maximum der Knotengrade. Ist deg(T) ≤ 2, so ist T
ein Binärbaum, ist deg(T) > 2, so ist T ein Mehrweg-/Vielwegbaum.
Ø
Die Menge aller Kinder eines Knotens n bilden zusammen mit n den
Teilbaum mit Wurzel n
Ø
Ein Baum T heisst der Höhe nach ausgeglichen (height balanced),
wenn die Höhe aller Teilbäume zu einem Knoten n (= Wurzel zu
diesen Teilbäumen) sich maximal um 1 unterscheidet.
Ein Baum T heisst dem Gewicht nach ausgeglichen (weight balanced)
oder auch vollständig ausgeglichen, wenn sich die Teilbaum-KnotenAnzahlen aller Teilbäume zu einem Knoten n (= Wurzel zu diesen
Teilbäumen) maximal um 1 unterscheidet.
height balanced
weight balanced
2
Anwendung von Bäumen
Ø
UNIX-Dateisystem. Dieses ist im Prinzip ein Vielweg-Baum, dessen
Kanten über die Links hergestellt sind.
Ø
Datenorganisation ( → Zugriffsbeschleunigung)
Die sortierte Datenmenge einer Datei sei im Hauptspeicher in einem
„sortierten“ Baum abgelegt:
Für jeden Knoten gilt: Im linken Teilbaum nur kleinere, im rechten nur
grössere Elemente.
(Binär-)Suche nach Element x: Vergleiche x mit Wurzel, falls grösser,
dann rechter Teilbaum, falls kleiner, dann linker Teilbaum u.s.w.
Hö he des Baumes bestimmend für maximale Suchzeit; deshalb sollte
der Baum möglichst ausgeglichen sein.
Suche, ob k in Datenmenge enthalten: k<m → k>g → k=k → k enthalten
Suche, ob z in Datenmenge enthalten: z>m → z>q → z>t → z ≠ v → z nicht enthalten
3
Ø
Decodier(binär)baum für Morsezeichen
Begriffe & Eigenschaften (Teil 2)
Ø
In geordnete Bäumen sind die Kinder-Knoten (Geschwister) eines
parent-Knotens in einer zugewiesenen „horizontalen“ Ordnung sortiert.
p
c1
c2
c3
...
ck
zugewiesene Ordnung
Ø
Ein ganzer Binärbäumen ist ein Binärbaum, bei dem alle Knoten mit
weniger als 2 Kindern durch Blätter „binär ergänzt“ wurden; alle
ursprünglichen Knoten sind dann innere Knoten (deg = 2), die Blätter
sind alle vom Grad deg = 0. Der ganze Binärbaum muss nicht
ausgeglichen sein.
Ø
Ein vollständiger Binärbaum ist ein optimal ausgeglichener, ganzer
Binärbaum, alle möglichen Pfade Blatt → Wurzel sind gleich lang (=
Hö he h des Baums)
4
Tiefe
0
1
Höhe
h = 3
2
3
Dieser vollständige Binärbaum (jeder Knoten 2 Kinder) besitzt:
♣ 2 h Blätter (hier 2 3 = 8)
♣ 2 0 + 2 1 + ... 2 h -1 = geometr. Reihe = (2 h - 1)/(2-1) = (2 h - 1) innere Knoten
Ø
Ein vollständiger k-stelliger Baum (jeder Knoten k Kinder) besitzt:
♣ Anzahl Blätter = k h
♣ Anzahl innerer Knoten = k 0 + k 1 + ... k h -1 = geometr. Reihe = (k h - 1)/(k-1)
♣ eine Höhe h = log k (Anzahl Blätter)
1. T e r m - B ä u m e
•
•
•
Notation arithmetischer Ausdrücke
Transformation eines arithmetischen Ausdrucks in
Postfix-Notation
Generierung von Analyse-Bäumen („parse-trees)
5
Notation arithmetischer Ausdrücke
Ø
Infix-Notation
ist Standard-Notation mathematischer Ausdrücke
• 1-stelligen Operatoren: <aus> = <op><operand>
<op> = + | –
• 2-stellige Operatoren: <aus> = <operand><op><operand>
<op> = + | – | • | /
• Strukturierung durch Klammern: ( | )
Ø
Präfix-Notation für
• 2-stellige Operatoren:
Ø
<aus> = <op><operand><operand>
Postfix-Notation für
• 2-stellige Operatoren: <aus> = <operand><operand><op>
• keine Strukturierung durch Klammern notwendig!!
→ maschinelle Auswertung
Beispiel:
Ø
•
Baum- Notation:
+
A
–
B
Ø
Infix-Notation:
(A + B) • (C – D)
Ø
Präfix-Notation:
•+ A B – C D
Ø
Postfix-Notation: A B + C D – •
C
D
6
Transformation eines arithmetischen Ausdrucks in
Postfix- Notation
Ø
Der arithmetische Ausdruck sei in Infix-Notation gegeben (StandardNotation)
A + (B – C)/D
Ø
shunting-yard algorithm (Verschiebebahnhof-Algorithmus)
Ø
Regeln des shunting-yard algorithm
Die Zeichenfolge des Ausdrucks wird von rechts nach links bewegt;
dabei wird jedes Symbol individuell evaluiert nach 5 Regeln:
1 Transfer: Operanden (am Anfang des Eingabestrings) → in Ausgabestring
2 push: linke (öffnende) Klammer → auf stack
3 REPEAT pop UNTIL (: alle Operatoren auf dem stack bis zur öffnenden
Klammer → in Ausgabestring; dann werden beide Klammern gelöscht.
4 WHILE priority(top) >= operator DO pop: Operatoren auf dem Stack → in
Ausgabestring bis das oberste Stack-Element von geringerer Priorität ist.
(steigende Priorität: ( → + – → • / )
Falls Stack leer oder oberstes Stack-Element von geringerer Priorität,
dann Operator → auf Stack (push)
5 WHILE NOT stack_empty DO pop: Input string leer ⇒ alle Elemente auf
Stack → Ausgabestring
7
8
Generierung von Analyse- B ä u m e n ( „ p a r s e -trees)
Ø
arithmetische Ausdrücke: duale Operanden ⇔ binäre Bäume
Ø
Infix- & Postfix-Notation
Infix:
A • (((B + C) • (D • E) ) + F)
Postfix: A B C + D E • • F + •
Ø
← für uns
← für den Rechner mit Stack
•
Analysebaum
+
A
•
F
+
B
•
C
D
E
Aufbau eines Analysebaums aus einer Eingabe in Postfix- Notation
TYPE
LINK = POINTER TO NODE;
NODE = RECORD
info
:
left, right :
END;
VAR
x, z :
c
:
link
info
CHAR;
LINK
right
left
LINK;
CHAR;
PROCEDURE createLeaf() : LINK;
VAR
leaf :
LINK;
BEGIN
NEW(leaf);
leaf^.left := leaf;
leaf^.right := leaf;
RETURN leaf
END createLeaf;
leaf
right
left
9
z
BEGIN
stackInit;
z := createLeaf();
REPEAT
REPEAT
ReadChar(c)
UNTIL c <> ;
(* skip blanks *)
x
NEW (x);
x^.info := c;
IF (c = '*') OR (c = '+') THEN
x^.right := pop;
x^.left
:= pop
ELSE
x^.right := z;
x^.left
:= z
END;
x
z
push(x)
UNTIL EOLN
END ....
10
1. Repräsentation allgemeiner Bäume
•
•
•
allgemeine Bäume
reine bottom-up Baumbearbeitung
reine top-down Baumbearbeitung
Allgemeine Bäume
Ø
Allgemeine Bäume besitzen Knoten mit variabler Anzahl von KindKnoten
Ø
Die Repräsentation bestimmt die Art der Operationen und umgekehrt
11
reine bottom-u p B a u m b e a r b e i t u n g
Ø
Bäume, die nur von den Blättern aufwärts (bottom-up) bearbeitet
werden
Ø
Für jeden Knoten wird nur der Verweis zum parent-Knoten
repräsentiert
parent
RECORD
elem
parent
END
r
: elemtype;
:= LINK
Hinweis : Die Wurzel r zeigt auf sich selbst
Ø
Einfache und effiziente Realisierung mittels Feldern:
info[.]
:
parent[.] :
Ø
Information, z.B. Zeichen
Index des parent-Knoten
Beispiel:
10
E
3
11
9
A
R
E
k
8
A
S
1
2
T
info[k]
:
1
:
A S
parent[k] : 3
M
P
L
E
4
5
6
7
2
3
4
5
6
7
8
9
10
11
A M P
L
E
T R
E
E
3 10 8 8
8
8
9 10 10
10
12
reine top-d o w n B a u m b e a r b e i t u n g
Ø
Verwaltung von Kind-Knoten ohne vorherige Allokation einer
spezifischen (maximalen) Anzahl.
⇒ verkettete Liste für Repräsentation der Knoten (variable Anzahl)
Ø
Konzept: Knoten des Baums haben genau 2 Zeiger:
♠ Zeiger L(inks) zum 1. (am weitesten links stehenden) Kind-Knoten
♠ Zeiger R(echts) zu den (eigenen) Geschwister-Knoten
♠ Der letzte Geschwister-Knoten zeigt zurück auf parent-Knoten
(→ Schleifen)
p
n
s1
.....
sk
Geschwister s 1 ...s k von n
c
Ø
Frage: Gibt es einen Unterschied zwischen dieser (Baum-)Struktur
und binären Bäumen?
Antwort: Nein
E
Ø
Beispiel:
A
E
R
A
S
A
R
E
T
E
M
A
S
T
P
M
P
L
E
L
E
13
1. Traversieren von Bäumen
•
•
•
•
•
Traversieren von Bäumen allgemein
Strategie pre -order
Strategie in -order
Strategie post -order
Strategie level -order
Traversieren von Bäumen allgemein
Ø
Travesieren eines Baumes bedeutet das systematische Absuchen
eines Baumes, wobei jeder Knoten „besucht“ wird (visit). „Besuchen“
steht für: auslesen, einlesen, vergleichen, markieren, ...
Ø
Hier: elementare Operationen auf Binärbäumen
Ø
Beispielbaum:
P
M
S
A
L
E
A
R
E
T
E
14
visit(t) zuerst
Strategie pre-order
Ø
Rekursive Strategie:
Wurzel → linker Teilbaum → rechter Teilbaum
P
M
PROCEDURE traverse(t: LINK)
BEGIN
IF t <> z THEN
visit(t);
traverse(t^.L);
traverse(t^.R)
END
END traverse;
L
S
A
z ist Zeiger auf Blatt
E
A
R
E
T
E
Knotenfolge:
P M S A A L E R T E E
Rekursionstiefe:
1 2 3 4 4 2 3 4 5 5 6
TYPE
LINK
NODE
=
=
POINTER TO NODE
RECORD
elem : <elemtype> ;
L, R : LINK (* Verzweigung L(eft) & R(ight*)
END
PROCEDURE traverse(t: LINK)
BEGIN
IF t <> z THEN
visit(t);
traverse(t^.L);
traverse(t^.R)
END
END traverse;
15
nicht-rekursive Pre-Order-Traversierung → Stack-basierte Lösung
Ø
(Annahme: Stack-Initialisierung außerhalb der Prozedur !)
PROCEDURE traverse (t : LINK);
BEGIN
push(t)
WHILE NOT stack_empty DO
t := pop;
visit(t);
IF (t^.R <> z) THEN
push(t^.R)
END;
IF (t^.L <> z) THEN
push(t^.L)
END
END
pre-order
Travesiere rechts
Travesiere links
END traverse;
Ø
Beispiel:
t
t^.L
t^.R
t^.L^.L
visit(t)
visit(t^.L)
visit(t^.L^.L)
t^.L^.L
t^.L^.R
t
t^.L
t^.L^.R
t^.R
t^.R
visit(t^.L^.R)
visit(t^.R)
t^.L^.R
t^.R
t^.R
16
visit(t) in der Mitte
Strategie in-order
Ø
Rekursive Strategie:
linker Teilbaum → Wurzel → rechter Teilbaum
P
M
PROCEDURE traverse(t: LINK)
BEGIN
IF t <> z THEN
traverse(t^.L);
visit(t);
traverse(t^.R)
END
END traverse;
L
S
A
z ist Zeiger auf Blatt
E
A
R
E
T
E
Knotenfolge:
A S A M P L E T R E E
Rekursionstiefe:
4 3 4 2 1 2 3 5 4 6 5
TYPE
LINK
NODE
=
=
POINTER TO NODE
RECORD
elem : <elemtype> ;
L, R : LINK (* Verzweigung L(eft) & R(ight*)
END
PROCEDURE traverse(t: LINK)
BEGIN
IF t <> z THEN
traverse(t^.L);
visit(t);
traverse(t^.R)
END
END traverse;
17
Ø
nicht-rekursive In-Order-Traversierung → Die stack-basierte Lösung ist
ein Beispiel dafür, dass die rekursive Programmierung viel eleganter ist
(aber auch langsamer). In der stack-basierten Lösung sind 2 Sonderfälle zu berücksichtigen.
Sonderfall 1
Sonderfall 2
visit(t) zuletzt
Strategie post-order
Ø
Rekursive Strategie:
linker Teilbaum → rechter Teilbaum → Wurzel
P
M
S
A
z ist Zeiger auf Blatt
PROCEDURE traverse(t: LINK)
BEGIN
IF t <> z THEN
traverse(t^.L);
traverse(t^.R);
visit(t)
END
END traverse;
L
E
A
R
E
T
E
Knotenfolge:
A A S M T E E R E L P
Rekursionstiefe:
4 4 3 2 5 6 5 4 3 2 1
18
TYPE
LINK
NODE
=
=
POINTER TO NODE
RECORD
elem : <elemtype> ;
L, R : LINK (* Verzweigung L(eft) & R(ight*)
END
PROCEDURE traverse(t: LINK)
BEGIN
IF t <> z THEN
traverse(t^.L);
traverse(t^.R);
visit(t)
END
END traverse;
Strategie level-order
Ø
Nicht-rekursive Strategie:
oben → unten und auf jedem level links → rechts
P
M
S
A
L
E
A
R
E
T
E
Knotenfolge:
P M L S E A A R T E E
19
nicht-rekursive Level-Order-Traversierung → Queue-basierte Lösung
Ø
es existiert keine offensichtliche rekursive Lösung!
Ø
Anpassung der (nicht-rekursiven, stack-orientierten) Pre-OrderProzedur:
PROCEDURE traverse (t : LINK);
BEGIN
enqueue(t)
WHILE NOT queue_empty DO
t := dequeue(t);
visit(t);
IF (t^.L <> z) THEN
enqueue(t^.L)
END;
IF (t^.R <> z) THEN
enqueue(t^.R)
END
END
END traverse;
Ø
Beispiel:
t
t^.L
t^.R
t^.L^.L
t^.L^.R
oben rein
Queue!
unten raus
20
1. B i n ä r e S u c h b ä u m e
•
•
•
•
•
Struktur
Aufwand
Implementierung des Baums
Einfügen eines Knotens
Löschen eines Knotens
Struktur
Ø
Problem: Eine Menge von INTEGER/CARDINAL-Zahlen (oder anderen Elementen, für die eine Ordnung gilt) soll eingelesen und so
abgespeichert werden, dass für weitere Elemente mö glichst schnell
entschieden werden kann, ob diese zur eingelesenen Menge gehören
oder nicht.
Ø
Lösung: Die einzulesende Menge wird in einer binären Baumstruktur
abgelegt, sodass jeder Knoten ein Element enthält und für jeden
Teilbaum gilt:
Elemente im
<
linken Teilbaum
Wurzelelement
<
Elemente im
rechten Teilbaum
25
Ø
Suchbaum
8
Ø
32
Beispiel:
5
2
13
6
11
30
19
26
38
31
36
41
21
Aufwand
Ø
Gegeben: Ein vollständiger Binärbaum T und eine Zahl x, die als
Suchschlüssel dient.
Ø
Verfahren:
1 Vergleich von x mit Wurzelelement, wenn gleich, dann gefunden → fertig
2 wenn kleiner, suche im linken Teilbaum,
wenn grösser, suche im rechten Teilbaum
3 falls x nicht gefunden wurde bis das erste Blatt erreicht wurde, ist x im
Suchbaum nicht enthalten
Ø
Aufwand im schlechtesten Fall (Durchsuchen der gesamten Höhe des
Baumes) bei n Knoten:
♠ vollständiger Baum: ld(Anzahl Blätter) = ld (n+1) Suchschritte
♠ entarteteter Baum (lineare Liste): n Suchschritte
Implementierung des Baumes
Elemente sind Zahlen
Ø
Struktur:
TYPE
elemtype = CARDINAL;
LINK
= POINTER TO NODE;
NODE
= RECORD
elem
: elemtype;
parent, L, R : LINK
END;
VAR
el
tree, newNode
: elemtype;
: LINK;
22
Ø
Einlesen von Elementen und Generierung von Knoten des Baumes
:
tree := NIL;
el
:= 1;
(* Abbruchwert = 0 *)
WHILE el <> 0 DO
ReadCard(el);
IF el <> 0 THEN
NEW(newNode);
newNode^.elem
:= el;
newNode^.parent := newNode;
newNode^.L
:= NIL;
newNode^.R
:= NIL;
InsertNode(newNode, tree)
END
END;
:
Einfügen eines Knotens
(siehe nächste Folie)
Einfügen eines Knotens
Ø
newNode
In der 1. Suchrunde:
vorläufiger parent = Wurzel
Nicht-rekursiver Algorithmus
gegeben sind: Zeiger x auf neuen Knoten und Zeiger tree auf Baum(wurzel)
♣ der neue Knoten n e w N o d e wurde im letzten Programm erzeugt und mit einem
Element el versehen. Der parent-Zeiger zeigt auf n e w N o d e selbst, L- und R-Zeiger
auf NIL. Der neue Knoten soll mittels InsertNode(n e w N o d e , tree) in den Baum
eingefü gt werden.
♣ If der Baum noch leer ist (tree=NIL), then wird der neue Knoten zur Wurzel (tree:=x),
(fertig!), else zeigt dessen parent-Zeiger vorläufig auf die Wurzel (par := tree) und die
Bool‘sche Variable nodeInsert wird auf F A L S E gesetzt, d.h. der neue Knoten ist noch
nicht im Baum plaziert.
♣ Suche nach der richtigen Position des neuen Knotens im Baum:
♥
Vergleich der Elemente: if Knotenelement < parent-Element then Einbau des
♥
Knotens in den linken Teilbaum des vorläufigen parent (else in dessen rechten Teilbaum).
If der linke Zeiger des vorlä ufigen parent bereits auf einen Knoten zeigt
(par^.L<>NIL), then wird dieser zum neuen vorläufigen parent (par:=par^.L)
und es geht von vorne los mit Elemente-Vergleich (while ( N O T nodeInsert) Schleife), else - Platz ist noch leer - wird der Knoten dort eingebaut:
par^.L:=x; x^.parent:=par; nodeInsert. = T R U E (fertig!)
23
PROCEDURE InsertNode(x : LINK; VAR tree : LINK);
VAR
par
nodeInsert
: LINK;
: BOOLEAN;
W H I L E -Schleife läuft, bis Knoten x eingebaut
BEGIN
Baum leer → Knoten x ist Wurzel
I F tree
= NIL THEN (* leerer Baum *)
tree
:= x
(* parent( x ) = x * )
ELSE
Baum nicht leer → Wurzel ist (vorläufiger) parent
par
: = tree,
nodeInsert := FALSE;
WHILE (NOT nodeInsert) DO
IF (x^.elem < par^.elem) THEN
IF (par^.L <> NIL)
THEN
ELSE
Einbau von x in li. Teilbaum des (vorl.) parent
par
par^.L
x^.parent
nodeInsert
:=
:=
:=
:=
par^.L
x;
par;
TRUE
par
par^.R
x^.parent
nodeInsert
:=
:=
:=
:=
par^.R
x;
par;
TRUE
END
ELSE
IF (par^.R <> NIL)
THEN
ELSE
END
END (* IF *)
END (* WHILE *)
END (* IF *)
END InsertNode;
Ø
Einbau von x in re. Teilbaum des (vorl.) parent
Rekursiver Algorithmus
gegeben sind: Zeiger x auf neuen Knoten und Zeiger tree auf Baum(wurzel)
♣ der neue Knoten n e w N o d e wurde im letzten Programm erzeugt und mit einem
Element el versehen. Der parent-Zeiger zeigt auf n e w N o d e selbst, L- und R-Zeiger
auf NIL. Der neue Knoten soll mittels InsertNode(n e w N o d e , tree) in den Baum
eingefü gt werden (wie beim nicht-rekursiven Algorithmus).
♣ If der Baum noch leer ist (tree=NIL), then wird der neue Knoten zur Wurzel (tree:=x),
(fertig!), else Vergleich der Elemente: if Knotenelement < tree-Element then Einbau
des Knotens in den linken Teilbaum, also Aufruf von InsertNode(x, tree^.L).
Achtung: Die Variable tree bekommt in diesem Aufruf den Zeigerwert von tree^.L zugewiesen!, d.h. der Zeiger tree zeigt im rekursiven Algorithmus immer auf die
Teilbaumwurzel!
Falls Knotenelement nicht kleiner tree-Element (else-Zweig), wir der Knoten in
dessen rechten Teilbaum eingebaut, also Aufruf von InsertNode(x, tree^.R).
♣ Ist die richtige Position des Knotens gefunden, wird sein parent-Zeiger auf die
letzte(!) Teilbaum- Wurzel (das ist ja sein parent exakt im letzten(!) Aufruf von
InsertNode) gesetzt: x^parent := tree.
♣ Der rekursive Algorithmus ist bedeutend eleganter als der nicht-rekursive.
24
PROCEDURE InsertNode(x : LINK; VAR tree : LINK);
Baum leer → Knoten x ist Wurzel
BEGIN
IF tree = NIL THEN (* leerer (Teil-) Baum *)
tree := x
ELSE
Einbau von x in linken Teilbaum von tree
IF (x^.elem < tree^.elem) THEN
InsertNode(x, tree^.L) (*in linken Teilbaum *)
ELSE
InsertNode(x, tree^.R) (*in rechten Teilbaum *)
Einbau von x in rechten Teilbaum von tree
END;
IF (x^.parent = x) THEN
x^.parent := tree
END
END
END InsertNode;
Der parent-Zeiger des Knotens x wird mit
der Adresse des Vorgängers belegt.
tree zeigt im rekursiven Algorithmus nicht immer auf die (Gesamt-)Baumwurzel
sondern auf die Teilbaumwurzel, in die der neue Knoten x eingebaut werden soll.
tree verändert sich bei jedem rekursiven Aufruf.
Ist die richtige Stelle für den neuen Knoten x im Baum gefunden, zeigt tree genau
auf den Vorgänger (parent)
Ø
Beispiel:
Folge: 25
8
32
5
2
6 13
11
19
26
30
31
38
36
41
25
8
32
5
2
13
6
11
26
19
38
30
36
41
31
25
Ø
Beispiel: Dieselben Elemente, diesmal der Größe nach geordnet
Folge: 2
5
6
8
11
13 19
25
26
30
31
32
36
38
41
2
5
6
8
11
13
19
25
26
30
31
32
36
38
41
Löschen eines Knotens
Ø
iterativer Algorithmus
gegeben: Zeiger x auf zu löschenden Knoten und Zeiger tree auf Baum(wurzel)
Es treten 3 Fälle auf:
0 der Knoten besitzt keine Nachfolger: x^.L=NIL und x^.R=NIL
In diesem Fall wird der Vorgä nger (parent) des Knotens modifiziert.
1 der Knoten besitzt einen Nachfolger: entweder x^.L=NIL oder x^.R=NIL
In diesem Fall wird der Knoten herausgeschnitten (siehe Listen).
2 der Knoten besitzt zwei Nachfolger: weder x^.L=NIL n o c h x^.R=NIL
In diesem Fall wird im rechten Teilbaum des Knotens immer links hinuntergegangen
bis zum Knoten, der links ein Blatt hä ngen hat.
Dieser Knoten hat folgende Eigenschaften:
♠ Sein Element ist größer als das Element des zu löschenden Knotens und alle
Knotenelemente im linken Teilbaum des zu löschenden Knotens
♠ Sein Element ist das kleinste im rechten Teilbaum des zu löschenden Knotens
♠ Sein Element folgt also in der Elementordnung dem Element des zu löschenden Knotens
♠ Der Knoten besitzt höchstens einen Nachfolger
Dieser Knoten wird wie im Fall (0 ) oder (1 ) herausgeschnitten und an die Stelle des
zu löschenden Knotens gesetzt.
26
keine Nachfolger:
ein Nachfolger:
5
5
5
5
6
6
löschen
zwei Nachfolger:
löschen
32
26
28
35
25
10
32
24
22
10
löschen
32
24
40
22
35
25
32
40
28
ausschneiden
Ø
29
29
Programm; zuerst zwei Hilfsprozeduren
E r s e t z e K n o t e n old d u r c h K n o t e n n e w
PROCEDURE ReplaceNode(old, new : LINK);
BEGIN
old^.elem := new^.elem;
new^.parent := new;
new^.L := NIL;
Die
new^.R := NIL
END ReplaceNode;
Überschreibe altes Knotenelement mit neuem
Zeiger des neuen Knotens werden „ausgehängt“
F i n d e d e n K n o t e n m i t d e m n ä c h s t g r ö s s e r e n Element
PROCEDURE SuccessorElem(x : LINK) : LINK;
BEGIN
x := x^.R;
IF (x <> NIL) THEN
WHILE (x^.L <> NIL) DO
x := x^.L
END
END;
RETURN x
END SuccessorElem;
x zeigt auf den Knoten, für den der Knoten mit
dem nächstgrößere Element gesucht werden soll.
Ein Schritt nach rechts,
d a n n s o l a n g e S c h r i t t e n a c h l i n k s , bis der Zeiger x
auf einen Knoten mit linkem Blatt zeigt.
Dieser Knoten enthält das nächstgrößere Element
27
Ø
Programm; jetzt die Lösch-Prozedur (1. Teil)
Lösche Knoten x
PROCEDURE DeleteNode(x : LINK; VAR tree : LINK);
VAR
del, subT : LINK;
BEGIN
IF (x^.L = NIL) OR (x^.R = NIL) THEN
del := x;
IF (x^.L = NIL) THEN subT := x^.R
ELSE subT := x^.L
END
ELSE
del := SuccessorElem(x);
subT := del^.R
END;
...
Ø
Fall 0 oder 1: der Knoten x
wird mit Zeiger del markiert,
der Unterbaum von x wird an
seiner Wurzel mit Zeiger s u b T
markiert.
Fall 2: der Knoten mit dem
nächstgrößeren Element wird
mit Zeiger del markiert, des
Knotens Unterbaum wird an
seiner Wurzel mit Zeiger s u b T
markiert.
Programm; die Lösch-Prozedur (cont‘d)
...
IF (x^.parent = x) THEN (* x = root(tree) ? *)
tree := subT;
F a l l s d i e ( G e s a m t -) B a u m w u r z e l g e l ö s c h t w e r d e n
IF (subT <> NIL) THEN
soll, dann wird der B a u m w u r z e l z e i g e r t r e e
subT^.parent := subT u m g e s e t z t a u f d e n K n o t e n , a u f d e n s u b T zeigt
und eine neue Wurzel gebildet.
END
ELSE
IF (del^.parent^.L = del) THEN del^.parent^.L := subT
ELSE del^.parent^.R := subT
L ö s c h e n v o n del (Fall 0,1,2): falls del a m
END;
linken(rechten) Zeiger seines p a r e n t
IF (subT <> NIL) THEN
hing, wird dort jetzt s u b T a n g e h ä n g t
subT^.parent := del^.parent
END;
IF (del <> x) THEN (* x-Knoten ersetzen ? *)
replaceNode(x, del)
del <> x gibt es nur im Fall 2: dort wird der
Knoten mit dem nächstgrößeren Element
END
gelöscht und dessen Element im Knoten x
END;
eingesetzt. (gleichbedeutend mit Löschen
DISPOSE(del)
von x und ersetzen durch Knoten mit dem
END DeleteNode;
nächstgrößeren Element.
28
§ Ein B a u m ist rekursiv definiert: An seiner Wurzel hängen (Teil-)bäume. Er besteht aus
Knoten und Kanten. Jeder Knoten hat einen parent und 0 ... mehrere children.
Anwendungen sind: übersichtliche Darstellung, effiziente Suche, ...
§ Arithmetische Ausdrücke werden gewöhnlich in infix-Notation geschrieben oder als
T e r m b a u m gezeichnet. Für die maschinelle Verarbeitung ist die (klammerlose)
postfix-Notation geeignet. Eine Transformation infix- → postfix-Notation ist mittels
shunting-yard-algorithm mö glich. Die Generierung eines T e r m b a u m s im Rechner
geschieht mittels „Verzeigerung“ von Knoten-Records, die ein Element besitzen, eine
Adresse (auf die ein Zeiger eines anderen Knoten-Records zeigen kann) und drei
eigene Zeiger (parent, left child, right child) zum „Verzeigern“ mit anderen Knoten
(Kanten)
§ Traversieren von Bäumen: Durchlaufen eines gegebenen Baumes entlang der Kanten, wobei jeder Knoten einmal besucht wird, um dort eine Operation durchzuführen
(auslesen, einlesen, vergleichen, markieren, ...). 4 Verfahren zur Traversierung
wurden vorgestellt: pre-order, in-order, post-order, level-order
§ S u c h b ä u m e sind aufgebaut aus Knoten mit Elementen (die sich ordnen lassen).
Einbau eines zusätzlichen Knotens: Dieser wird „über die Baumwurzel eingespeist“
und je nachdem ob sein Element kleiner/größer ist als das Wurzelelement, steigt der
Knoten einen Links-/Rechtsschritt den Baum hinab, usw. Dies geschieht bis zu einem
„leeren Ende“. Dort wird der Knoten „eingehängt“.
Weitere Standard- Operationen: Löschen eines Knotens, Suchen etc.
29
Herunterladen