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