Informatik I 2. Kapitel Elementare Datenstrukturen Rainer Schrader Zentrum für Angewandte Informatik Köln 28. Mai 2008 1/1 2/1 Datenstrukturen Datenstrukturen • bisher haben wir nur Arrays verwendet, • effiziente Algorithmen brauchen geeignete Datenstruktur Gliederung • Einführung • abstrakte Datentypen • Beispiel: Suche im Telefonbuch • gegeben: Name • gesucht: Telefonnummer • Listen • Stacks und queues • Anwendungsbeispiele • Bäume • ; einfach • gegeben: Telefonnummer • gesucht: Name • 3/1 ; praktisch unmöglich 4/1 Datenstrukturen Datenstrukturen • offensichtlich ist das Problem in der Zeit Θ(n) lösbar, • aber es geht besser, wenn man viele Anfragen zu einem festen M hat Nichttriviales Beispiel: Posamp-Problem • gegeben: Informelle Beschreibung einer geeigneten Datenstruktur • n Orte (Postämter) M = {p1 , p2 , . . . , pn } • ihre kartesischen Koordinaten pi = (xi , yi ) • sei VR(pj ) die Menge aller Punkte der Ebene, die näher an pj als an • zusätzlicher Ort p0 = (x0 , y0 ) • also jedem anderen Punkt in M liegen, n o VR(pj ) = q ∈ R2 | d (q, pj ) ≤ d (q, pi ) ∀i ∈ {1, 2, . . . n} . • gesucht: • das zu p0 nächstgelegene Postamt pk , d.h. • d (p0 , pk ) ≤ d (p0 , pi ) für alle i ∈ {1, . . . , n} mit p • d (pi , pj ) = (xi − xj )2 + (yi − yj )2 (Euklidische Distanz) • VR(pj ) ist die Voronoi-Region von pj • das Voronoi-Diagramm ist die Menge VD(M ) = {VR(pj ) | j ∈ {1, 2, . . . , n}} 5/1 6/1 Datenstrukturen Datenstrukturen Beispiel: Idee eines effizienten Verfahrens für eine beliebige Anfrage • (AD) Aufbau der Datenstruktur: bestimme das Voronoi-Diagramm VD(M ). • (BA) Beantwortung einer Anfrage: für eine Anfrage p0 : bestimme VR(pk ) mit p0 ∈ VR(pk ). man kann zeigen: (evtl. Kapitel 10) • (AD) benötigt O(n log n) Zeit. • (BA) benötigt O(log n) Zeit. 7/1 8/1 Datenstrukturen Datenstrukturen • Datenstrukturen sollen Operationen auf Objekten unterstützen • evtl. verschiedene Datenstrukturen für verschiedene Operationen Gliederung • Einführung • abstrakte Datentypen • evtl. Verknüpfung von Objekten und Operationen (Klassen) • Listen • Stacks und queues • Anwendungsbeispiele • Bäume Abstrakter Datentyp (ADT) besteht aus • einer oder mehreren Mengen von Objekten • und darauf definierten Operationen 10 / 1 9/1 Datenstrukturen Datenstrukturen Beispiel Postamtproblem : Beispiel: Formulierung eines Algorithmus mit Hilfe von ADT • Objekte: Aufgabe: • endliche Mengen M von Punkten in der Ebene • gegeben eine endliche Menge M von Punkten in der Ebene • finde ein Paar p0 , q0 von Punkten mit minimalem Abstand d (p0 , q0 ) • ein Punkt p0 in der Ebene • Operation ADT: • „nächster Nachbar“: ordne dem Punkt p0 einen nächstgelegen Punkt aus M zu • Objekt: endliche Punktmenge M • Operationen: • Mögliche zusätzliche Operationen: • füge einen Punkt zu M hinzu • nächster Nachbar • Distanz zweier Punkte • entferne einen Punkt aus M • Kardinalität von M • M := M r {p} Wir haben jedesmal einen anderen ADT. 11 / 1 12 / 1 Datenstrukturen Datenstrukturen Algorithmus nearest_neighbours(M) liefert p0 , q0 ∈ M mit minimaler Distanz d (p0 , q0 ) falls M = ∅ oder |M | = 1, so sind p0 und q0 nicht definiert Implementierung (Realisierung als Computerprogramm) falls |M | ≥ 2: • wähle zwei Punkte p0 , q0 ∈ M • berechne die Distanz dist := d (p0 , q0 ) • Auswahl von Datenstrukturen für die Objektmengen • Auswahl von Algorithmen für die Operationen • for all p ∈ M do • bestimme einen nächsten Nachbarn q ∈ M r {p} • berechne die Distanz d (p, q) • falls d (p, q) < dist, so setze: • p0 := p; q0 := q; dist := d (p, q). • end do 14 / 1 13 / 1 Datenstrukturen Datenstrukturen Lineare Liste • Objekte: Gliederung • Menge aller endlichen Folgen eines gegebenen Grundtyps • die Schreibweise für eine lineare Liste ist • Einführung • abstrakte Datentypen L = ha1 , a2 , . . . , an i, • Listen • Stacks und queues • Anwendungsbeispiele • Bäume • hierbei ist L = hi die „leere Liste“ • verschiedene Operationen • im Folgenden ist L eine lineare Liste, x vom Grundtyp und p eine Position 15 / 1 16 / 1 Datenstrukturen Datenstrukturen Füge_ein(x,p,L) Entferne(p,L) • falls L = ha1 , a2 , . . . , ap−1 , ap , ap+1 , . . . , an i 6= hi : L → ha1 , a2 , . . . , ap−1 , x , ap , ap+1 , . . . , an i L → hx , a1 , a2 , . . . , an i L → ha1 , a2 , . . . , an , x i für L 6= hi und 1 ≤ p ≤ n gilt: für 1 < p ≤ n • aus ha1 , a2 , . . . , ap−1 , ap , ap+1 , . . . an i für p = 1 wird für p = n + 1 ha1 , a2 , . . . , ap−1 , ap+1 , . . . an i. • falls L = hi und p = 1: hi → hx i • undefiniert sonst • undefiniert sonst 18 / 1 17 / 1 Datenstrukturen Datenstrukturen Suche(x,L) Verkette(L,L1,L2) • liefert eine Position p in L = ha1 , a2 , . . . , an i mit ap = x , falls diese existiert, • liefert für • sonst 0. L1 = ha1 , a2 , . . . , an1 i und L2 = hb1 , b2 , . . . , bn2 i die Liste Zugriff(p,L) L = ha1 , a2 , . . . , an1 , b1 , b2 , . . . , bn2 i. • liefert ap in L = ha1 , a2 , . . . , an i, falls 1 ≤ p ≤ n, • sonst undefiniert. 19 / 1 20 / 1 Datenstrukturen Datenstrukturen Gliederung • Einführung • abstrakte Datentypen Darstellungen linearer Listen • sequentielle Darstellung • Listen • zusammenhängende Speicherung als Feld mit direktem Zugriff • sequentielle Listen • verkettete Listen • verkettete Listen • Stacks und queues • Anwendungsbeispiele • verteilte Speicherung, durch Zeiger verknüpft • Bäume 21 / 1 22 / 1 Datenstrukturen Datenstrukturen Füge_ein(x,p,L) Sequentielle Listen \\ fügt x als neues Element an Stelle p ein, falls p zulässig, gibt andernfalls Fehlermeldung aus Eine sequentiell gespeicherte Liste L besteht aus: • einer (hinreichend großen) Konstanten maxelzahl • einem Feld element[0, . . . , maxelzahl] if (L.elzahl = L.maxelzahl or p < 1 or p > L.elzahl + 1) print ”Fehler” else for i = L.elzahl down to p do L.element(i+1) = L.element(i) end do L.element(p) = x L.elzahl = L.elzahl+1 end if • der aktuellen Listengröße elzahl Ist 0 < L.elzahl ≤ L.maxelzahl, so ist in L.element(1), . . . , L.element(elzahl) die aktuelle Liste abgelegt. 23 / 1 24 / 1 Datenstrukturen Datenstrukturen Entferne(p,L) Suche(x,L) \\ entfernt das p. Element, falls p zulässig, gibt andernfalls Fehlermeldung aus \\ liefert die höchste Position, an der x in L vorkommt, 0 sonst if (L.elzahl = 0 or p < 1 or p > L.elzahl) print ”Fehler” else L.elzahl = L.elzahl - 1 for i = p to L.elzahl do L.element(i) = L.element(i+1) end do end if L.element(0) = x i = L.elzahl while (L.element(i) 6= x) do i = i-1 end while return i 26 / 1 25 / 1 Datenstrukturen Datenstrukturen Zugriff(p,L) Verkette(L,L1,L2) \\ gibt das p. Element aus, falls p zulässig, gibt andernfalls Fehlermeldung aus \\ hänge L2 an L1 an, falls L1 ausreichend lang if (L1.elzahl + L2.elzahl > L1.maxelzahl) print ”Fehler” else if (L2.elzahl > 0) then do for i = 1 to L2.elzahl do L1.element(L1.elzahl + i) = L2.element(i) end do end if L1.elzahl = L1.elzahl + L2.elzahl end if if (p < 1 or p > L.elzahl) print ”Fehler” else return L.element(p) end if 27 / 1 28 / 1 Datenstrukturen Datenstrukturen Gliederung Laufzeiten • Einführung • abstrakte Datentypen Sei n die Länge der Liste Operation suche füge_ein entferne Zugriff verkette Zeit • Listen O(n) O(n) O(n) O(1) O(n2 ) • sequentielle Listen • verkettete Listen • Stacks und queues • Anwendungsbeispiele • Bäume 29 / 1 30 / 1 Datenstrukturen Datenstrukturen eine mögliche Implementation von verketteten Listen eine Liste L = ha1 , a2 , . . . , an i lässt sich wie folgt darstellen: • eine verkettete Liste wird „kreuz und quer“ im Speicher abgelegt a1 • eine Liste ist gegeben als eine Folge von Knoten • jeder Knoten besteht aus a2 ... an DUMMY-Elemente head tail (a) einem Listenelement dat (b) einem Zeiger next auf das nächste Listenelement • zwei Dummy-Elemente als „Kopf“ und „Schwanz“ der Liste eine leere Liste sieht dann wie folgt aus: • einem Zeiger head auf das Kopfelement • einem Zeiger tail auf das Schwanzelement • der Zeiger des Schwanzelements zeigt auf das vorangehende Element head 31 / 1 tail 32 / 1 Datenstrukturen Datenstrukturen Füge_ein(x,p,L) Vereinbarung: • Zeiger von x := Zeiger von Element p − 1 • Zeiger von Element p − 1 := Zeiger auf x Die Position p ist gegeben durch einen Zeiger auf den Knoten, dessen Zeiger auf das p.te Listenelement zeigt: Skizze für Füge_ein: ap vorher: ap−1 ap p p nachher: ap−1 ap Die Listenoperationen lassen sich wie folgt durchführen: x Wir überprüfen hier nicht, ob p ein legaler Zeiger ist. 33 / 1 34 / 1 Datenstrukturen Datenstrukturen Entferne(p,L) • Zeiger von p − 1 := Zeiger von Element p • (entferne Knoten p) Suche(x,L) liefert die Position des ersten Listenelements mit dem Schlüssel x , falls es existiert, sonst NULL. Suche(x,L) Skizze für Entferne(p,L): vorher: ap−1 ap • kopiere x in das Datumsfeld des Schwanzes • beginnend mit dem Kopfelement suche nach x ap+1 • wird x in einem Element p gefunden: p nachher: ap−1 • ist p nicht das Schwanzelement: gib Zeiger auf p zurück • andernfalls gib Null zurück ap+1 Wir überprüfen hier nicht, ob p ein legaler Zeiger ist. 35 / 1 36 / 1 Datenstrukturen Datenstrukturen Doppelte Verkettung Verkette(L,L1,L2) • tail1 zeigt auf das Schwanzelement von L1 • der Zeiger dieses Schwanzelements zeigt auf das letzte Element von L1 • biege dessen Zeiger auf den Zeiger des Kopfelements von L2 um • entferne das Schwanzelement von L1 und das Kopfelement von L2 • manchmal ist es hilfreich, eine Kette auch „rückwärts“ durchlaufen zu können • wir verwenden einen zusätzlichen Zeiger auf den jeweiligen Vorgänger • hier können wir die Position p durch einen Zeiger auf den Knoten mit dat-Komponente ap definieren Skizze für Verkette: head1 head ap−1 ... ap ap+1 p tail1 Beispiel für Entferne: tail ... head2 ap−1 ap+1 tail2 37 / 1 38 / 1 Datenstrukturen Datenstrukturen Zusammenfassung: Gliederung Operation suche füge_ein entferne Zugriff verkette sequentiell Θ(n) Θ(n) Θ(n) Θ(1) Θ(n2 ) verkettet Θ(n) Θ(1) Θ(1) Θ(1) Θ(1) • Einführung • abstrakte Datentypen • Listen • Stacks und queues • Anwendungsbeispiele • Bäume 39 / 1 40 / 1 Datenstrukturen Datenstrukturen Stapel Schlange • ist eine spezielle Liste • Einfügen und Entfernen nur am Anfang der Liste erlaubt • ist eine spezielle Liste • Einfügen nur am Ende, Entfernen nur am Anfang der Liste • „LIFO“: last-in-first-out • „FIFO“: first-in-first-out • Operationen: • Operationen: • empty(L): teste, ob L leer ist • pop(L,x): setze x := a1 ; entferne a1 • enqueue(L,x): füge x am Ende von L ein • dequeue(L,x): setze x = a1 , entferne a1 aus L • empty(L): teste, ob L leer ist • top(L,x): setze x := a1 (ohne zu entfernen) • top(L,x): setze x := a1 (ohne zu entfernen) • push(L,x): füge x am Anfang von L ein 42 / 1 41 / 1 Datenstrukturen Datenstrukturen • alle Stapel-, Schlangen- und Doppelschlangen-Operationen können mit konstanter Laufzeit implementieren werden Doppelschlangen • sowohl sequentiell als auch verkettet • beide push- und pop-Operationen sowie • empty(L), top(L,x), bottom(L,x) • Anwendung als Warteschlangen und Vorrangwarteschlangen • sequentiell implementierte Schlangen sollten „zyklisch“ verwaltet werden 43 / 1 44 / 1 Datenstrukturen Datenstrukturen Skizzen für sequentielle Speicherung Stapel (Doppel−)Schlange maxn frei 1111 0000 0000 1111 Stapel 1111 0000 0000 1111 0000 1111 0000 1111 1111111111111111111 0000000000000000000 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 Anfang 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 maxn 1111111111111111111 frei 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 0 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 00000000000000000001 Ende1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 0000000000000000000 1111111111111111111 Gliederung • Einführung • abstrakte Datentypen • Listen • Stacks und queues • Anwendungsbeispiele • Bäume 45 / 1 Datenstrukturen 46 / 1 Datenstrukturen Definition eines wohlgeformten Klammerausdrucks Anwendung eines Stapels: wohlgeformte Klammerausdrücke (1) () ist ein wohlgeformter Klammerausdruck • Test, ob Klammerfolgen wohlgeformt sind • Beispiel: (()()) • Beispiel: ((())( (2) sind w1 und w2 wohlgeformte Klammerausdrücke, so ist auch w1 w2 ein wohlgeformter Klammerausdruck √ (3) mit w ist auch (w ) ein wohlgeformter Klammerausdruck (4) nur die nach (1) bis (3) gebildeten Zeichenketten sind wohlgeformte Klammerausdrücke 47 / 1 48 / 1 Datenstrukturen Datenstrukturen Anwendung eines Stapels: Test von Klammerausdrücken Anwendung von Listen: Datenstruktur für Projektplanungen • durchsuche Zeichenkette von links nach rechts gegeben: n Teilprojekte eines Projekts (Bauprojekt, Kfz-Fertigung, …) mit • Zeitangaben über die Dauer der Teilprojekte • Reihenfolgebedingungen zwischen einzelnen Teilprojekten • wird „(“ gefunden: lege sie auf Stapel • wird „)“ gefunden: bestimme: • ist Stapel leer ; nicht wohlgeformt • Gesamtdauer • Pufferzeiten • andernfalls entferne obere Klammer vom Stapel • am Ende: • kritische Pfade • … • ist Stapel nicht leer ; nicht wohlgeformt • andernfalls ; wohlgeformt 49 / 1 50 / 1 Datenstrukturen Datenstrukturen 12 Beispiel mit 4 Teilprojekten • TP1 : Dauer: 12 • TP2 : Dauer: 6, muss warten auf Beendigung von TP1 , TP3 • TP3 : Dauer: 9, muss warten auf Beendigung von TP1 • TP4 : Dauer: 7, muss warten auf Beendigung von TP2 , TP3 6 1 2 3 4 9 7 Darstellung mit verketteten Listen 12 6 1 3 9 2 4 1 2 2 4 3 2 4 NIL 3 4 7 51 / 1 52 / 1 Datenstrukturen Datenstrukturen gerichteter Graph G = (V , E ) (ungerichteter) Graph G = (V , E ) besteht aus: besteht aus: • einer endlichen Menge V von Knoten • einer endlichen Menge E von gerichteten Kanten • jeder Kante e ∈ E entspricht ein Paar u, v von Knoten • einer endlichen Menge V von Knoten • einer endlichen Menge E von Kanten • jeder Kante e ∈ E entspricht ein Paar u, v von Knoten • u, v sind die Endknoten von e • u Startknoten, v Endknoten von e • wir identifizieren e und das Knotenpaar, d.h. e = (u, v ) • wir identifizieren e und das Knotenpaar, d.h. e = {u, v } • Veranschaulichung wie vorher: • Veranschaulichung: • Knoten als Kreise • Knoten als Kreise • Kanten als „Pfeile“ • Kanten als Verbindungen, Strecken, … • Darstellung z.B. durch „Adjazenzlisten“ • Darstellung z.B. durch „Adjazenzlisten“ 53 / 1 54 / 1 Datenstrukturen 1 2 3 4 Datenstrukturen Gliederung • Einführung • abstrakte Datentypen Darstellung mit verketteten Listen 1 2 3 2 1 3 4 3 1 2 4 4 2 3 • Listen • Stacks und queues • Anwendungsbeispiele • Bäume 55 / 1 56 / 1 Datenstrukturen Datenstrukturen Bäume • wir werden im Laufe der Vorlesung immer wieder auf bestehen aus: graphentheoretische Modelle und Fragestellungen stoßen • einer endlichen Menge V von Knoten • ein Knoten ist als Wurzel ausgezeichnet • jeder Knoten hat eine endliche Menge von direkten • insbesondere kann die nächste Datenstruktur als gerichteter Graph aufgefasst werden Nachfolgern (Söhnen) • sie stellt ebenso eine Verallgemeinerung von Listen dar • die Wurzel hat keinen direkten Vorgänger (Vater) • jeder andere Knoten hat genau einen direkten Vorgänger 57 / 1 58 / 1 Datenstrukturen Datenstrukturen Sprechweisen Bäume (rekursive Definition) Sei T ein Baum mit Wurzel r und Unterbäumen T1 , . . . , Tk . Ferner sei ri die Wurzel des Baumes Ti . bestehen aus einer endlichen Menge V von Knoten, so dass gilt: • es gibt einen ausgezeichneten Knoten r (Wurzel) • die restlichen Knoten sind partitioniert in Teilmengen T1 , . . . , Tk , die • ri ist i-ter Sohn von r , r ist Vater der r1 , . . . rk , • rj ist Bruder von ri , • u ist Nachfolger von ri , falls u im Unterbaum Ti liegt, • ein Knoten ohne Nachfolger heißt Blatt, selbst wieder Bäume sind • r zeigt auf die Wurzeln der Teilbäume r1 , . . . , rk • Knoten von T , die nicht Blatt sind, heißen innere Knoten, 59 / 1 60 / 1 Datenstrukturen Datenstrukturen Bezeichnungen Sprechweisen T1 5 Sei T ein Baum mit Wurzel r und Unterbäumen T1 , . . . , Tk . Wurzel T2 Niveau 2 0 Ferner sei ri die Wurzel des Baumes Ti . 3 7 • eine Folge von Knoten v0 , v1 , . . . , vk heißt Weg, falls vi +1 Nachfolger linker Unterbaum 5 von von vi ist für alle 0 ≤ i ≤ k − 1, 2 • der Weg v0 , v1 , . . . , vk hat die Länge k , • Tiefe(v , T ) = Länge des Weges von Knoten v zur Wurzel r , • Höhe(v , T ) = Länge des längsten Weges von v zu einem Blatt, • Höhe(T ) = Höhe (Wurzel, T ). 5 1 3 Knoten 8 Kanten Tiefe(7)=2 7 5 3 ist Vater von 2 3 ist Vater von 5 2 ist linker Sohn von 3 5 ist rechter Sohn von 3 2 8 3 Blatt 5 4 62 / 1 61 / 1 Datenstrukturen Datenstrukturen Darstellungen von Bäumen In der Informatik werden Bäume stets so dargestellt, dass die Wurzel oben und die Blätter unten liegen. • verkettete Liste mit variabler Zeigerzahl Wurzel Daten Anzahl der Söhne Pointer zu Sohn Bäume können aufgefasst werden als: • spezielle gerichtete Graphen, • verallgemeinerte Listen, in denen ein Knoten mehr als einen Nachfolger Nachteil: variable Zeigerzahl haben kann. 63 / 1 64 / 1 Datenstrukturen Datenstrukturen • verkettete Liste mit 3 Datentypen Beispiel: • jedes Element der Liste besteht aus 3 Teilen: 0 1 1 Datum Indikator 1 Zeiger 2 3 6 0 4 4 0 5 1 2 0 3 0 7 7 1 • Indikator = 0: ; Datum ist eine zu speichernde Größe • Indikator = 1: ; Datum ist ein Zeiger auf eine Liste mit Unterbaum 0 5 0 6 • wir betrachten überwiegend Bäume mit spezieller Struktur • etwa durch Beschränkung der Anzahl der Söhne (; einfachere Speicherung). 65 / 1 66 / 1 Datenstrukturen Datenstrukturen Binäre Bäume Lemma Ein Baum, in dem jeder Knoten höchstens 2 Söhne hat, heißt binär. Ein binärer Baum der Höhe h hat höchstens 2h+1 − 1 Knoten und 2h Blätter. Darstellung binärer Bäume Beweis: (per Induktion): √ • h=0: • durch 2 Felder Leftsoni und Rightsoni • dazu ggf. Informationen über: Inhalt der Knoten, Väter, # Nachfolger in zusätzlichen Feldern 1 2 3 4 5 6 1 2 3 4 5 6 R 4 6 - • sei T ein Baum der Höhe h • T hat höchstens 2 Söhne r1 und r2 • r1 , r2 sind Wurzeln von Unterbäumen der Höhe höchstens h − 1 L 2 3 5 - • per Induktion haben T1 und T2 jeweils höchstens • 2h − 1 Knoten und 2h−1 Blätter • damit hat T höchstens • 2 · (2h − 1) + 1 = 2h+1 − 1 Knoten und 2 · (2h−1 ) = 2h Blätter. 67 / 1 68 / 1 Datenstrukturen Datenstrukturen • per Induktion gilt: Satz mittlere Tiefe(T1 ) ≥ log k1 , Die maximale und die mittlere Tiefe eines Blattes in einem Binärbaum mit k Blättern beträgt mindestens log k . • die Tiefe in T ist um 1 größer als in T1 bzw. T2 • somit: Beweis: • haben alle Blätter die Tiefe höchstens t , so hat T höchstens 2t Blätter • ⇒ die maximale Tiefe ist mindestens log k . ” k “ ” k1 “ 2 mittlere Tiefe(T1 ) + 1 + mittlere Tiefe(T2 ) + 1 k k k1 k2 ≥ (log k1 + 1) + (log k2 + 1) k k 1 = (k1 log 2k1 + k2 log 2k2 ) k := f (k1 , k2 ) mittlere Tiefe(T ) = Beweis des zweiten Teils per Induktion: √ • k =1: . • sei T ein Baum mit k ≥ 2 Blättern • seien T1 , T2 die Teilbäume unter der Wurzel mit k1 bzw. k2 Blättern • per Induktion gilt: mittlere Tiefe(T1 ) ≥ log k1 , mittlere Tiefe(T2 ) ≥ log k2 Die Funktion f (k1 , k2 ) nimmt ihr Minimum unter der Bedingung k1 + k2 = k im Punkt k1 = k2 = k2 an. Somit: mittlere Tiefe(T2 ) ≥ log k2 mittlere Tiefe(T ) ≥ 1 k k k k ( log 2 + log 2 ) = log k . k 2 2 2 2 70 / 1 69 / 1 Datenstrukturen Datenstrukturen Definition Definition Ein binärer Baum der Höhe h heißt voll, wenn er 2h+1 − 1 Knoten hat. Ein binärer Baum mit n Knoten heißt vollständig, wenn seine Knoten den ersten n Knoten eines sequentiell dargestellten vollen binären Baumes entsprechen. Sequentielle Darstellung Die Knoten des vollen binären Baums werden beginnend in der Wurzel auf jedem Tiefenniveau von links nach rechts durchnumeriert. 1 2 3 1 4 2 4 5 6 3 5 6 7 71 / 1 72 / 1 Datenstrukturen Datenstrukturen Lemma Kompakte Darstellung vollständiger binärer Bäume Für einen vollständigen binären Baum T mit n Knoten gilt Vollständige binäre Bäume haben eine kompakte sequentielle Darstellung der Relationen Vater, linker Sohn und rechter Sohn (ohne Zeiger): Höhe(T ) = dlog(n + 1)e − 1. bi /2c : für i > 1 − : für i = 1 (Wurzel) 2i : für 2i ≤ n − : für 2i > n 2i + 1 : für 2i + 1 ≤ n − : für 2i + 1 > n Vater (i ) = Beweis (per Induktion): • sei h die Höhe von T • die Knotenanzahl von T liegt zwischen denen der vollen Bäume der Leftson (i ) = Höhe h und h − 1 • ⇒ 2h − 1 < n ≤ 2h+1 − 1 • ⇒ h < log(n + 1) ≤ h + 1 • ⇒ h < dlog(n + 1)e ≤ h + 1 Rightson (i ) = (da h ∈ N) • ⇒ h + 1 = dlog(n + 1)e. 73 / 1 74 / 1 Datenstrukturen Datenstrukturen bi /2c : für i > 1 − : für i = 1 (Wurzel) • diese Numerierung ist eine spezielle Aufzählung der Knoten eines 2i : für 2i ≤ n − : für 2i > n • in einigen Anwendungen müssen wir die Knoten eines Baumes 2i + 1 : für 2i + 1 ≤ n − : für 2i + 1 > n Vater (i ) = Leftson (i ) = Rightson (i ) = Baums systematisch durchsuchen: • zum Ausgeben der Einträge • zum Aufsummieren der Einträge • zur Bildung eines Mittelwerts über die Einträge,. . . 1 • wir wollen somit die Knoten in einer bestimmten Reihenfolge 2 durchlaufen 3 • die gebräuchlichsten Reihenfolgen: 4 5 6 7 • Präordnung • Postordnung • symmetrisches Durchmustern (nur für Binärbäume) 75 / 1 76 / 1 Datenstrukturen Datenstrukturen Sei T ein Baum mit Wurzel r und Söhnen r1 , . . . , rk . Eigenschaften der Durchmusterungen Präordnung Wir identifizieren Knoten mit ihrer Nummer. (1) Durchmustere die Wurzel Präordnung (2) Durchmustere in Präordnung nacheinander T1 , . . . , Tk (1) Durchmustere die Wurzel (2) Durchmustere in Präordnung nacheinander T1 , . . . , Tk Postordnung Sei r die Wurzel des Teilbaums Tr . Dann ist (1) Durchmustere in Postordnung nacheinander T1 , . . . , Tk v ∈ Tr ⇐⇒ r ≤ v ≤ r + |Tr | − 1. (2) Durchmustere die Wurzel symmetrisches Durchmustern von Binärbäumen Folgerung (1) Durchmustere in symmetrischer Ordnung Tlinks (falls er existiert) Ist |Tr | bekannt, so kann in O(1) Schritten entschieden werden, ob v Nachfolger von r ist. (2) Durchmustere die Wurzel (3) Durchmustere in symmetrischer Ordnung Trechts (falls er existiert) 78 / 1 77 / 1 Datenstrukturen Datenstrukturen Postordnung symmetrisches Durchmustern von Binärbäumen (1) Durchmustere in Postordnung nacheinander T1 , . . . , Tk (1) Durchmustere in symmetrischer Ordnung Tlinks (falls er existiert) (2) Durchmustere die Wurzel (2) Durchmustere die Wurzel (3) Durchmustere in symmetrischer Ordnung Trechts (falls er existiert) Sei r die Wurzel des Teilbaums Tr . Dann ist v ∈ Tr ⇐⇒ r − |Tr | + 1 ≤ v ≤ r . Die Knoten im linken Teilbaum von r tragen kleinere Nummern als r , die im rechten größere. Folgerung Folgerung Ist |Tr | bekannt, so kann in O(1) Schritten entschieden werden, ob v Nachfolger von r ist. Wir können (auch nach dem Löschen von Knoten) in O(Höhe(T )) entscheiden, ob ein Knoten mit der Nummer p vorkommt. 79 / 1 80 / 1 Datenstrukturen Datenstrukturen Nichtrekursiver Algorithmus zur symmetrischen Durchmusterung begin count = 1 v = root Top = 0 left: while (leftson(v) 6= leer) then push (Stack, v) v = leftson(v) end while Center: Number(v) = count count = count + 1 if (rightson(v) 6= leer) then v = rightson(v) goto left end if if (top 6= 0) then Pop(Stack, v) goto center end if end Rekursiver Algorithmus zur symmetrischen Durchmusterung begin count = 1 SymmOrd(Wurzel) end //Hauptprogramm procedure SymmOrd(v) if (leftson(v) 6= leer) then SymmOrd(leftson(v)) Number(v) = count count = count + 1 if (rightson(v) 6= leer) then SymmOrd(rightson(v)) end 81 / 1 82 / 1 Datenstrukturen Datenstrukturen Durch Einfügen und Löschen von Stichworten kann der Suchbaum wie folgt aussehen: Stichwortsuche • gegeben: ein Lexikon mit n Stichworten • Frage: ist ein gegebener Begriff im Lexikon enthalten? g a b d Zwei möglich Ansätze: i c d • verkettete Listen: ; O(n) im worst-case b f h j • binärer Baum als Suchbaum organisiert: j a c e • Wörter im linken Unterbaum sind alphabetisch größer als das Wort in der Wurzel Wir werden versuchen, diese Situation zu verhindern, und balancierte Bäume verwenden. • Wörter im rechten Unterbaum sind alphabetisch kleiner als das Wort in der Wurzel Wir verschieben daher die Laufzeituntersuchungen der Operationen Folgerung Füge_ein, Entferne, Suche und Zugriff auf Bäumen auf ein späte- Wir können in O(Höhe(T )) entscheiden, ob das gesuchte Wort im Lexikon vorkommt. res Kapitel. 83 / 1 84 / 1