an für heute. Seid alle da, um zu laufen. So,
der Plan für heute, nach diesem ganzen PNP und so Kram,
dachte ich, ich gönne uns heute mal erstmal anderthalb Stunden
ein bisschen Entspannung und wir machen binäre Suchbäume.
Das ist definitiv einfach als PN-Team. Mal gucken. Und
in den dritten 45 Minuten, da wollte ich dann nochmal
ein bisschen was zu PNP mit euch machen.
Erstmal Suchbäume.
Das Suchproblem
Achso, das Suchproblem war ja das, wo wir gesagt haben,
wir kriegen so eine Menge von Elementen und suchen irgendwie ein Element da drin.
Und dann hatten wir bisher so als Ansätze irgendwie quasi so eine Brut-Force-Aktion.
Wir gucken uns alles an und sobald wir das Element gefunden haben,
sagen wir, ja, wir haben es.
Oder wenn das Ari schon sortiert ist, irgendwie binäres Suchen.
Solche Sachen irgendwie. Das Problem dabei war,
erstens, das mit dem binären Suchen geht halt nur,
wenn das Ari sortiert ist und es geht auch nur mit dem Array,
das ist also ziemlich eingeschränkt.
Und wir möchten gerne, nee, Moment, genau,
das ist das erste Problem. Und das zweite ist,
wenn man jetzt nicht binär suchen kann,
sondern man macht die Brut-Force-Methode und ist relativ langsam,
dann geht das irgendwie in O von
Und das würden wir gerne schneller machen. Jetzt ist die Idee,
so ähnlich wie beim Sortieren auch,
wie wir das so bei Heepsword gemacht haben,
dass wir versuchen, eine sinnvolle Datenstruktur zu entwickeln,
sodass wir dann schnell suchen können.
Beim Sortieren haben wir das auch gemacht mit dem Heepsword.
Da könnte man jetzt noch sagen, naja,
warum der ganze Aufwand beim Sortieren? Heepsword geht ja auch
nicht schneller als Merch-Sord, also kann ich gleich Merch-Sord nehmen.
Und hier beim Suchen ist es jetzt Anlass,
da kriegt man dann tatsächlich mit sinnvollen Datenstrukturen eine Laufzeitverbesserung hin.
Das ist der eine Grund, warum wir das machen wollen,
dass man wieder sehen kann irgendwie, okay,
wenn ich eine neue Datenstruktur entwickle, kann ich Sachen schneller machen,
die ich vorher nicht konnte.
Und das zweite, warum wir das machen wollen, ist,
bisher waren die Mengen, auf die wir gesucht haben, statisch.
Also wir hatten irgendwie ein Arry der Länge N und haben darauf Dinge gesucht.
Und wir hätten das gerne mit dynamischen Datenstrukturen,
dass man irgendwie, dass die Datenstruktur selbst wachsen und schrumpfen
Mit Aris ist das ja immer so ein bisschen blöd.
Bei Listen kennen wir das schon, aber bei Listen suchen Schnarch langsam,
weil man irgendwie immer nur zum nächsten Element kommt.
Das heißt, im Worst Case muss man die sich alle angucken.
Da kann man zum Beispiel sowas vibinäres suchen nicht machen,
irgendwie in die Mitte der Liste springen und dann in der
linken oder rechten Hälfte weitersuchen, funktioniert bei verketteten Listen nicht.
Also das wollen wir heute machen,
da nochmal zu betonen und bisher hatten wir diese statischen Mengen,
Anzahl der Elemente ändert sich nicht.
Wo wir jetzt hinwollen,
ist dynamische Mengen zu haben und bei denen eben zu suchen und die,
die wir schon kennen,
sind so Stack Queue,
verkettete Liste
Bei denen eben das Problem war, dass die zwar dynamisch sind,
aber man kann sich irgendwie überlegen,
wenn man in denen sucht, dann ist das alles langsam.
Also bei der Q, alles durchgehen. Beim Stack muss
man sogar alle Elemente irgendwie vom Stack nehmen und später wieder
drauf tun, damit er wieder so aussieht wie vorher.
Das ist alles nicht so schön. Kurze Anmerkung, meistens,
hat mich hier bei der verketteten Liste auch schon mal kurz angemerkt.
Meistens stellen wir nur die Daten dar und wenn man die Datenstrukturen tatsächlich implementiert,
hat man am meisten noch irgendwie sogenannte Satellitendaten,
wie beispielsweise bei der Liste ein Wächter am Ende der Liste,
damit man das Ende der Liste finden kann.
Und die sparen wir meistens aus,
nur wenn er irgendwie im Kormen nachliest und an ein,
zwei Stellen wird sowas dann mal erwähnt
Dass ihr wisst, was gemeint ist, wenn da irgendwie Satellitendaten stehen.
Also irgendwas um die Datenstruktur noch ein bisschen klarer anzufangen oder zu beenden oder sowas.
Gut, das sind die Operationen, die wir machen können, wollen.
Wir wollen suchen können. Klar, deswegen machen wir das Ganze.
Wir wollen was einfügen können, wir wollen was löschen können.
Und dann noch so ein paar Sachen, wir hätten gerne das Minimum,
das Maximum, den Nachfolgergrund, den Vorgänger von einem Element.
Wenn man sich das jetzt nochmal bei der verketteten Liste überlegt,
da ist das alles relativ langsam,
weil man immer das richtige Element suchen muss und dann an der Stelle beispielsweise löschen muss,
neu verketten muss oder da einfügen muss, Minimum,
Maximum, wenn die verkettete Liste jetzt gar nicht sortiert ist,
geht alles immer nur,
indem ich da einmal komplett rübergehe.
Ist alles doof
Und selbst wenn die sortiert wäre,
entweder Minimum oder Maximum dauert dann auch lange,
wenn ich bis ans Ende der Liste muss und dafür keinen Pointer habe irgendwie.
Also das wollen wir alles schnell machen können. Und dafür wollen wir
gleich eine Datenstruktur entwickeln. Nachfolger, Vorgänger. Genau,
so das Suchproblem einmal mit der Sequenz von Zahlen und einmal eben allgemein,
wie man das, wie wir das jetzt eigentlich betrachten wollen.
Wir haben so eine Menge von Elementen und wir haben einen
bestimmten WRK oder ein bestimmtes Element, das wir suchen.
Und dann ist eben der Zeiger auf dieses Element gesucht
Anders ist das Problem, was wir lösen wollen. Die Ansätze,
die wir bisher hatten, nehmen einmal dieses Blue Force of Arry,
Laufzeit in Täter von Ende, ist irgendwie klar,
man guckt sich alle Elemente im Worst Case an.
Und dann hatten wir dieses binäre Suchen,
Blue Force auf der Liste noch, genau.
Im Grunde genommen das Gleiche, auch wieder ein Täter-N,
nur dass man eben irgendwie am Kopf der Liste anfängt und
dann eben hier diese Operation von der Liste benutzt,
dass man immer zum Nachfolger geht.
Aber sonst genau das Gleiche wieder vorher. Ich gucke immer,
ist das Element das Gesuchte, falls ja,
bin ich fertig, falls nein, mache ich weiter.
Binäre Suche auch wieder auf dem Arry, lauft halt dann
in Täter-Log-Ende, weil man hier diese Rekurrenzgleichung hat.
Da hatten wir uns ja auch mit beschäftigt,
wie man die dann schnell lösen kann
Und da war ja die Idee irgendwie,
ich gucke in der Mitte, ist es das,
falls nein, suche ich im linken oder rechten Teil weiter.
Problem eben ist zwar ein Täter-Log-N,
geht aber nur mit einer sortierten, mit einem sortierten Arry.
Und wenn man jetzt irgendwie schon end-my-Log-N-Schritte braucht zum Sortieren,
dann ist das halt langsamer als die Brut-Voraussuche sogar.
Ja. Da steht es nochmal zusammen. Da steht es
nochmal zusammengefasst. Genau, und das ist jetzt das Ziel hier.
Wir hätten gerne eine Datenstruktur für dynamische Mengen und dann hätten wir da,
also das ist das einezige, was wir haben,
das dynamisch sein soll, wachsen und schrumpfen können soll und das andere,
dass das in Zeit O-Log en gehen soll.
Jetzt könnt ihr euch mal kurz überlegen,
dass das mit den ganzen Datenstrukturen hier vorher nicht ging.
Also beim Hieb kann man vielleicht relativ dicht dran,
da gingen ja so einige Operationen in Login,
aber zum Beispiel das Suchen im Hieb geht auch nicht sonderlich schnell,
weil ihr immer nur wisst,
wenn ihr im Wurzel oder wenn ihr einen Knoten habt,
wisst ihr nur, dass alle Elemente,
die in den Teilbäumen sind,
kleiner sind.
Also müsst ihr alle Teilbäume springen. Das ist irgendwie doof
, ne? Boah, seid ihr auch wieder in ON
Kann man, jetzt kann man die Idee von diesem Hieb aber ummodeln,
um auf eine Idee zu kommen, für eine Datenstruktur,
mit der das eben doch schnell geht. Ich habe ja eben schon gesagt,
wie beim Hieb war das Problem primär, dass wir gesagt haben,
wenn wir einen Wurzelstuhl oder von Teilbaum die Wurzel haben,
also irgendeinen Knoten im Baum, ist alles da drunter kleiner.
Das ist blöd fürs Suchen,
kann man sich überlegen und das ist genau die Idee beim Binieren-Suchbaum,
dass man sagt, naja, wenn ich so einen Knoten habe,
dann soll links deinem linken Teilbaum, ist alles kleiner,
im rechten Teilbaum ist alles größer.
Dann geht das Suchen genauso wie bei einer Binärensuche,
dann fange ich mit einem Knoten an, gucke,
ist das Element, was ich suche, klein oder größer,
wenn es kleiner suche ich im linken Teilbaum weiter,
wenn es größer ist,
suche ich im rechten Teilbaum weiter und
immer so weiter
Dann habe ich das hingekriegt, dass das Suchen wieder in O-Log-N geht.
Wenn der Baum dann ausgeglichen ist, kommen wir auch gleich noch dazu.
Und jetzt muss ich mir halt überlegen, naja,
dann kann ich natürlich nicht die Einfiguroperation wie beim Hieb machen oder so,
sondern ich muss das jetzt irgendwie anders machen.
Da muss ich alle Operationen durchgehen und die daraufhin anpassen,
was ich jetzt bei dieser Datenstruktur für eine Idee gerade hatte.
Also, der binär gesuchtbaum, den wollen wir jetzt machen.
Datenstruktur, die viele Operationen für dynamische Mengen ermöglicht,
insbesondere eben diese Operation, die wir jetzt machen wollten.
Und das, was er eben ist,
ist ein Binärer, suchbar, ein bisschen binärer Baum.
Jeder Knoten hat bis zu zwei Kindern und der Baum hat noch die spezielle Suchbaum-Eigenschaft,
die sehen wir auch gleich nochmal, aber schon mal gesagt,
alles im linken Teilbaum ist kleiner,
alles im rechten Teil,
warum ist größer
Genau, und anders als der Hieb speichert man den beißt durch
eine verkettete Datenstruktur, sodass ich dann einen Knoten habe.
Und dann habe ich einen Pointer auf sein linkes Kind und einen Pointer auf sein rechtes Kind.
Und wenn ich möchte, vielleicht noch einen Pointer auf den Vater.
Und bei dem Hieb, den haben wir ja im Array abgelegt.
Dann hatte man immer diese Zugriffsoperation mit irgendwie,
wenn ich einen Knoten habe, wenn ich ein Array-Element habe,
dann ist das linke Kind genau bei Element halbe und das
rechte Kind bei Element halbe plus eins oder so.
Und hier habe ich jetzt eben diese Pointer auf linkes Rechtes
und auf linkes, rechtes Kind und den Vaterknoten.
So, da das Beispiel, jetzt könnt ihr sehen hier,
wenn ihr irgendwie einen Knoten habt,
links ist alles kleiner,
rechts ist alles größer
Und hier üblicherweise noch irgendwie die Nilknoten, dass man weiß,
kein linkes Kind, kein rechtes Kind,
der Zwölfer hat jetzt links einen Nilknoten,
rechts hat halt jetzt Kindknoten, die 13. Genau,
dann habe ich die dann nicht alle eingezeichnet, wird das irgendwie unübersichtlich.
Und wenn man möchte,
könnte man jetzt der 10 hier noch als Vaterknoten auch Nil geben,
dann weiß man halt, wann man bei der Wurzel ist,
also wenn man jetzt irgendwie vom Blatt mal nach oben wandern will,
weiß man, wann man bei der Wurzel angekommen ist.
Bitte? Na, dann schaffen wir nicht den Stoff,
den ich heute schaffen will.
Schneller reden. Ja, ich probiere es.
Aber bis jetzt ist klar, oder
Baum, links, kleiner, rechtsgrößer,
das ist so das, was wir bisher hatten.
Was für eine Zusammenfassung der letzten 15 Minuten, Baum,
links, kleiner, rechtsgrößer.
So. Beispiel gut, guter Baum. Beispiel schlecht, schlechter
Baum. Warum? Rechts soll doch alles größer sein.
Ne, ist schlecht. Warum ist der gut
Sehr schlecht. Ja, der erste ist in der Pause.
Genau. Beim Linken hier, bei dem,
würde ich sagen, da habe ich jetzt irgendwie ende Elemente,
ohne die jetzt zählen zu wollen.
Und eine Suchoperation, wenn ich das jetzt so mache wie
beim Minärensuchen, ist grob so ein O-Log-N.
Das ist gut. Wenn ich da drin suche, dann ist
die Suchoperation in ON. Also kann man sich schon vorstellen,
wir werden es wahrscheinlich bei, wenn die Bäume übt,
Entschuldigung, wenn die Bäume üblicherweise so aussehen,
dann werden wir die ganzen Operationen sowie suchen und sowas in Log-N-Zeit hinkriegen.
Wenn die Bäume aber irgendwie quasi entarten, also wenn die so aussehen,
Du hast die Worst-Case-Laufzeit leider doch wieder ON.
Da können wir am Ende der Vorlese nochmal gucken,
ob er da noch was machen kann.
Aber erstmal müssen wir mit dem Nachteil leben. Ja,
der größte Satz von dem Baum ist einfach,
dass er die gleiche maximale Suchlänge hat,
obwohl er nur ein Viertel so viele Elemente hat.
Ja, was passt das nochmal zusammen, genau.
Der Nachteil davon ist halt ja voll in die Länge,
der entartet komplett. Das ist ja eigentlich kein Baum mehr,
das ist ja eigentlich eine Kette. Dass ihr das schon mal gesehen habt,
der Baum hat also auch seine Nachteile,
aber wir werden gleich trotzdem alles, diese ganzen Operationen hinkriegen.
Das heißt, wir kriegen diese dynamische Menge hin, die wir haben wollen.
Das ist schön oder diese dynamische Staatenstruktur. Und man kann sich so überlegen,
im Ganzen in relativ vielen Fällen,
wenn das jetzt nicht so komplett entartet,
dann gehen die ganzen Operationen eben auch irgendwie in O-Log-N,
was schön ist
Wenn er eben unausgeglichen wird, dann ist die Worst-Case-Laufzeit eben
doch irgendwie O von N. Das kann man schon mal so
im Hinterkopf behalten. Und später können wir dann gucken,
wie kann man die Datenstruktur noch tweaken, damit man eine Datenstruktur hinkriegt,
die tatsächlich auch im Worst-Case in O von Ende läuft.
Entschuldigung, in O von Login läuft. So,
jetzt geht's los. Die ganzen Operationen wollen wir haben.
Die oberste in Order Treewalk, die macht nichts anderes,
als quasi den Baum durchzugehen und alle Elemente auszugeben.
Die ist so da, damit man einmal so ein Gefühl dafür kriegt,
wie man das gleich machen kann mit diesem Baum-Offerationen.
Und danach geht's dann halt los mit genau den Sachen,
die wir machen wollen.
Suchen Minimum-Maximum-Nachfolger,
Vorgänger einfügen und löschen
In Order Treebalk, die erste, legen wir los. Also,
der soll seinen Schlüssel ausgeben oder soll alle Schlüssel ausgeben,
wobei eben zuerst die des Linken ausgeben soll,
dann die Wurzel, dann die des rechten Teilbaums.
Das ist eine sehr schöne, rekursive Vorschrift. Ich sage irgendwie,
ich rufe mich selbst auf auf mein linkes Kind,
also die Methode in Order Treebalk rufe ich auf das linke Kind auf,
dann gebe ich meinen eigenen Schlüssel aus und dann rufe ich
in Order Treebalk auf das rechte Kind auf.
Dann müsste ich das ziemlich erreicht haben. Also so sollte das
aussehen. Natürlich rufe es am Anfang mit der Wurzel auf,
solange ich noch nicht Neil bin,
rufe ich das auf dem linken Kind auf,
dann gebe ich meinen Schlüssel aus,
dann rufe ich das auf dem rechten Kind auf
Und dann bin ich fertig, ne? Und genauso kann
man hier so ein Pre-Order-Treebook und so ein Post-Order-Treewalk machen.
Wir bleiben mal bei diesem In-Order-Treewalk. Unten soll kein Arry sein,
sondern das wird die Ausgabe. Welches Element wird als erstes ausgegeben?
Links, Schlüssel, rechts, welches Element wird als erstes ausgegeben?
Zwei, ne? Genau. Ich rufe auf die Zehn auf in Order-Treebook,
dann ruft der auf,
dann ruft er das auf das linke Kind auf,
also auf die Sechs, nämlich bei der Sechs,
die ruft das erst mal auf das linke Kind auf,
also auf die drei, durch meine drei,
die ruft das auf das linke Kind aus,
bin ich bei der zwei.
Genau, ne?
Zwei. So, jetzt ist der
War hier bei dem Knoten 3,
hab In-Order Treewalk auf das linke Kind aufgerufen,
das war die 2, der ruft das auch aufs linke Kind auf,
das ist aber Neil, also passiert da nichts,
dann gibt er sich selbst aus,
dann ruft er auf das rechte Kind auf,
das ist auch Neil, passiert auch nichts,
dann ist der Aufruf fertig, er geht nach oben,
dann ruft der Knoten sich selbst auf und dann geht er an sein rechtes Kind,
ne?
So, dann sechs. Weil dann hat er sich ausgegeben und
jetzt ruft er in-order Treewalk auf den rechten Teilbaum auf,
also auf dem Baum mit der Wurzel 8 und die gibt sich ja nicht gleich aus,
sondern geht auch erstmal ganz links in den Teilbaum.
Dann wird erstmal sieben aufgerufen, 8,
9,
man erkennt das System
10, 12, 13, 15, 16. Also in Order
Treebook gibt die Dinger beim binären Suchbaum insortierte Reihenfolge aus.
Klar. Den Recousin aufruhen so, ja, ne?
Beim In-Order Tree, beim Pineas-Suchbaum werden die Schlüssel in sortierte Reihenfolge ausgegeben.
Beweis machen wir gleich mal ganz kurz, aber erstmal Laufzeit,
was meint ihr, wie lange das dauert?
Laufzeit, Laufzeit, Laufzeit, Laufzeit, gleich.
Was meint ihr, wie lange das dauert
Es gibt ein relativ einfaches Argument, das ihr dann aber
gut verteidigen müsst und es gibt ein kompliziertes Argument,
das machen wir gleich.
Wenn ich jede Aufzeit hat, das.
Nein, die sollten nicht raten. Was meinst du?
Okay, er meint O von N. Was meinst du?
Ja, zwei N wäre ja auch O von N.
Das ist ja auch okay, auch O von N.
Noch so von N.
Ein dritter
Das ist genau das. Das ist das. Das ist das.
Das ist das. Genau, klingt cool. Also ich gebe ja nicht einfach jeden Knoten aus,
also das ist ja keine Vorschleife, wo ich sage,
für 2 bis 16 gibt sie alle aus oder so,
sondern ich bin irgendwie beim Knoten,
jetzt gehe ich da in den ganzen Teilbaum,
dann gehe ich wieder hoch, dann gehe ich hier runter.
ON mal Login. Er sagt jetzt,
er glaubt ihr nicht und ihr auch nicht,
verteidigt euch
Ein Zeug, was am Ende daraus gegeben ist.
Nee, nee, nee. Also ich meine, jein,
wenn du jetzt beim Area-Zeugen zwischendurch Pi berechnest und so,
dann hängst du irgendwie davon ab.
Aber so normalerweise nicht. Also bei dem Source-Code, den wir gesehen haben
, da kann man jetzt die Laufzeit von dran ablesen.
Naja, ablesensübertrieben. Da ist immerhin was mit einfüllen oder ist das
einfach Yabel Marray, was wir langsam zusammensetzen aus Bestandshalten?
Würde auch keinen Unterschied. Eigentlich ist das ja gar nichts.
Das ist einfach nur, da steht nur eine Printausgabe.
Das ist kein Area, das ist einfach nur die Printausgabe.
Das ist ja ganz einfach. Wir müssen ja einfach mal
kurz wegen das einzige, was wir machen,
das Weg, wo sie bauen, für die,
glaube ich,
eine Zeitsage sind
Was? Ich habe einen Print und ich habe zwei Rekursive
Aufrufe. Wieso sind die denn egal?
Weil die dann jeweils mal kurz eingefüllt sind. Oh,
in deinem Tod, macht jetzt keinen einfachen Styles mit raus.
Sie sind in der Million nur die Printargumente, die ja Zeit verbrauchen,
weil sie müssen ja einfach das Grafik-Eleface hinter irgendwas hinschreiben.
Doch, die anderen brauchen auch Zeit.
Die anderen brauchen auch Zeit. Sonst würde ich sagen,
Merch, so heute ist ein O von N. Nee,
die Subroutineaufrufe brauchen auch Zeit.
Doch, wir gucken immer, ob x ungleich Nil ist und sowas.
Und dann haben wir immer drei Aufrufe. Und wir gehen
den Baum runter, wir gehen den Baum hoch.
Was kennt ihr denn schon, um sowas zu analysieren
Mastertheorie. Mastertheorie ist gut, ja. Rekurrenzgleichung kennen wir schon,
ne? Wir können irgendwie bei dem hier... Nee.
Wir können dafür eine Rekurrenzgleichung aufstellen. Und die können wir
analysieren. Und dann können wir uns sicher sein.
Ich glaube, die Abfrage ist immer gleich bleiben.
Das heißt, wir haben eine Konstante und eine Konstante.
Ja, aber du hast auch eine coole Technik.
Warum benutzt du die nicht? Du hast doch eine coole
Technik. Warum benutzt du die nicht? Du hast doch
die Technik Rekurrenzgleichung. Warum benutzt du die nicht?
Deine Argumentation ist schwammig. Deine Argumentation ist so,
dass du sagst, naja, das macht eigentlich keinen Unterschied,
weil ich rufe das ja nur auf.
Du hast doch eine viel coolere Technik.
Du kannst ja mit Rekurrenzgleichung arbeiten.
Mach das doch
Ich meine, das solltest du jetzt nicht im Kopf machen,
aber ich habe mir so... Ja, ja, genau
. Wir haben zweimal... Warte, machen wir gleich.
Okay, also wir arbeiten gleich mit Rekurrenzgleichung, ne?
Damit kann man das irgendwie viel besser analysieren,
also die grobe Idee ist ja cool, dass du sagst,
ich glaube, das läuft in O von N,
damit arbeiten wir auch gleich irgendwie,
dass man das dann als Behauptung aufstellt und dann versucht,
das zu beweisen.
Oder eben, dass man sagt, das läuft in ON
mal locken und ich versuche, das zu beweisen.
Aber so erstmal, um euch darauf zu schulen,
dass ein rekursiver Algorithmus so,
da ist das immer mit so Argumentationen wie,
ich glaube das und so weiter,
wird das
irgendwie schwierig
Deswegen beweisen. Also saubere Rekurrenzgleichung aufstellen und damit arbeiten.
Seid ihr auf der sicheren Seite. Kurz Exkurs noch,
warum ist das Ding korrekt? Kurzer Exkurs noch, warum ist
das Ding korrekt? Also korrekt soll bedeuten, die Behauptung ist,
diese Routine gibt die Elemente im binären Suchbaum insortierter Reihenfolge aus.
Das liegt an den Eigenschaften des Suchbaums. Der hat ja die Strange Eigenschaft,
dass alle Elemente in der Fass links streng kleiner sind,
wie das Element, was sie hält.
Und alle Elemente rechts strengt einer von Priestern, Genau,
er meinte gerade, das liegt an der Eigenschaft vom wenigeren Suchbaum.
Alles, was links ist, ist kleiner, alles,
was rechts ist, ist größer.
Würde jetzt als Beweis noch nicht reichen,
kann ich aber ganz schnell einen schönen Induktionsbeweis draus machen.
Bäume der Größe eins werden sortiert ausgegeben, gebe ich nur die Wurzel aus, fertig.
Reduktionseinnahme gilt für Bäume mit bis zu Endknoten. Jetzt habe ich einen mit mehr Knoten,
dann gebe ich ja zuerst links in einem Teilbaum,
der weniger Knoten hat, alles in sortierter Reihenfolge aus,
dann gebe ich meinen Schlüssel aus und dann gebe ich alles rechts,
wieder in sortierter Reihenfolge aus, links, alles kleine rechts,
alles größer,
ist das Ganze sortiert.
Ganz einfache Induktionsbeweis
Laufzeitanalyse ist deutlich anspruchsvoller. Behauptung, was auch zwei hier gesagt haben,
wir sagen mal, das ist ein Täter von innen.
Jetzt können wir die Rekurrenzgleichung aufstellen und dann gegentesten,
ob das mit der Laufzeitbehauptung hier mit dem Täter von nennt, ob das übereinstimmt.
Also Aufstellen der Rekurrenzgleichung, Lehrerbaum gilt T von 0 gleich C. Und dann der Kram,
wenn ich einen Baum habe mit Entknoten,
dann brauche ich T von K-Schritte für den linken Teilbaum,
T von n-k-einschritte für den rechten Teilbaum und nochmal D-Schritte eben quasi,
meine konstante einzelne Schritte, die ich brauche,
um bei mir das Print auszugeben und für diese FNLs-Geschichten.
Ja, das ist die Rekurrenzgleichung. Mastertheorien geht leider nicht,
aber da können wir ja irgendwie gleich was anderes versuchen.
Und Behauptung ist TVN ist in Täter von Ende.
Das heißt, wir vermuten,
dass TVN dieses ehemaligendes für irgendeine konstante E. Und jetzt versuchen
wir einfach mit Induktion
das zu beweisen
Behauptung Telefon N ist E-Mail N. Wenn wir das jetzt machen,
dann scheitern wir leider gleich im Enduktionsanfang. Wenn das hier die Behauptung ist,
und wir haben vorher hier als Rekurrenzgleichung aufgestellt,
TV 0 ist C, was ja auch irgendwie Sinn macht,
bei einem Baum der größte Null brauche ich trotzdem irgendwie eine konstante Anzahl von Schritten.
Ich teste einmal die Wurzel Null, dann stelle ich fest,
nee, ist sie nicht, und dann breche ich irgendwie ab.
Das heißt, hier habe ich gleich ein Problem,
wenn wir es mit irgend so beweisen,
dann haben wir irgendwie Telefon Null, ist gleich eh mal null.
Es ist null, ist ungleich zäh, ist doof.
Jetzt wollen wir nicht gleich aufgeben und nicht gleich unsere Idee verwerfen,
sondern wir sagen, okay, dann passen wir einfach unsere Behauptung hier,
die passen wir jetzt an.
Dann sagen wir einfach, unsere Vermutung ist jetzt,
dass TV N gleich eh mal in plus C ist
Dann würde er immer noch gelten, wenn das tatsächlich stimmt hier,
dann würde er immer noch die von N in Täter von N sein.
Und jetzt klappt aber der Induktionsanfang. Wenn ich da jetzt irgendwie hier loslege,
dann habe ich bei TV 0 bis C und das ist auch genau das,
was ich eben bei der Rekurrenzgleichung behauptet habe,
dass die von 0C sein sollen.
Problem ist gleich, das klappt immer noch nicht.
Jetzt behaupten wir das und jetzt wollen wir den Induktionsschritt machen.
Hier machen wir den mal von N minus 1 auf N,
sonst machen wir den ja relativ häufig von Ende auf N plus 1,
aber so wird gleich die,
kannst du von N auf N plus 1 machen,
aber ich habe es jetzt hier mal von N minus 1 auf N gemacht.
Wenn man den jetzt machen will,
dann kommt man dazu, dass man sagt,
TV N ist gleich und jetzt nimmt man erstmal
die Rekurrenzgleichung
Dann hatten wir also Telefon-Endes gleich der Kram hier einfach nach Rekurrenzgleich.
Die von den gleich TK plus tn-k minus 1 plus die.
Jetzt kann man die Induktionsannahme benutzen. Das ist ja auf beiden
sowohl das T von K als auch das End-minus-k minus 1
ist ja echt kleiner als das N.
Also kann ich die Induktionsannahme benutzen. Das heißt, T von K,
wenn ich hier oben jetzt in der Behauptung überall vor NK einsetze,
dann habe ich da ehemals K plus C.
Und wenn ich überall für Endes n-k minus 1 einsetze,
dann habe ich eh mal n-k-k minus 1 plus C. Umform
zusammenschmeißen kommt irgendwie das raus.
Darf man jetzt einmal glauben
Dass das rauskommt. Das Problem ist,
das sieht irgendwie nicht so aus wie, ne,
eigentlich will ich ja zu T von N gleich ehemal N plus C kommen.
Ich will ja den Induktionsschritt machen von N minus 1 zu N.
Das heißt, da sollte jetzt auf der rechten Seite gerade ehemal N plus C stehen.
Tut es aber irgendwie nicht. Jetzt kann man wieder einen Schritt zurück machen und kann sagen,
naja gut, dann nehmen wir eh dieses E von vorhin,
das nehmen wir dann einfach nicht als Konstante, sondern wir interpretieren das als Variable,
die wir im Laufe dieses Beweises noch festlegen wollen.
Wenn ich jetzt hier nämlich überall für E,
jetzt kann man sie ein bisschen umformen,
Das Ziel ist ja, dass ich rauskriege,
TVN gleich eh mal N plus C. Das heißt,
ich setze das hier, diese letzte Zeile,
die setze ich einfach gleich eh mal N plus C. Ich gehe jetzt davon aus,
dass eh eine Variable ist,
die ich noch festsetzen will und löst das zu eh auf.
Dann kommt raus, eh gleich C plus D.
Wenn ich hier jetzt überall für EC plus D einsetze,
dann löst sich das wunderschön zu eh mal N plus C auf,
also zum C plus D mal N plus C. Sehen wir auch gleich.
Und wenn ich also meine Behauptung so verfeinere,
wenn meine Behauptung jetzt nicht mehr ist,
TVN gleich ehemaligen plus C,
sondern TVN gleich C plus D mal N plus C,
dann klappt alles
Nur auf die Behauptung kommt man natürlich von Anfang an nicht.
Also wer behauptet schon irgendwie,
als ich vorhin nach der Laufzeit gefragt habe,
stellt sich niemand hin und sagt, hier, ich weiß es.
Die Laufzeit ist C plus D mal N plus C. Kommt mir ja nicht hin.
So, mal nachdem wir das in O von N und O
von N heißt für uns immer, es gibt irgendwie eine Konstante.
Und jetzt, wenn man es dann aber beweisen will,
dann muss man die Konstanten genauer fassen.
Ja, da steht ja jetzt noch meine roten Box zur Nachbereitung.
Und auf dieses E gleich C plus D kommt man,
weil man dahin möchte.
Ja, und jetzt läuft der Beweis einfach durch.
Gegeben ist die Rekurrenzgleichung einfach
direkt aus dem Code
Mit C&D irgendwelche Konstanten eben abhängig von den Interner der Maschine.
Und jetzt behaupte ich einfach TV und Endes,
also gleich C plus D mal N plus C.
Das würde ja bedeuten, TV und Endes sind Täter von
N. C plus D ist eine Konstante. Induktionsanfang,
wenn ich für N 0 einsetze, wenn ich hier null einsetze,
dann ist der vordere Sohn Mann weg und bleibt dann C übrig,
genauso wie hier auch.
Also Induktionsanfang, alles gut. Induktionsannahme,
geltet die überhaupt dann schon für Werte kleiner N. Induktionsschritt von N
minus 1 auf N für Tee von Andy Rekurrenzgleichung einsetzen,
also den Kram,
TV N gleich TK plus tm-kraminus
Und dann, weil die Werte ja kleiner sind, Chaos,
kleiner als N, N-K-1, das kleiner als N,
die Behauptung hier benutzen, die Induktionsannahme quasi.
Dann kriegt man den Kram raus, überall,
wo hier ein N steht, bei dem Tier von N
schreibt man K hin, das ist der Teil.
Und überall, wo hier ein End steht,
schreibt man N-K minus einzeln, das ist der Teil,
Umformen, nochmal Umformen, hat man genau die Behauptung raus,
genau das, was man haben will.
Ergo gilt die Behauptung, also wir wissen,
Tee von N ist gleich C plus D mal N plus C,
also wissen wir Tee von N
ist in Täter von
Das einmal, damit ihr seht, wie man auf solche Behauptungen kommt,
dass man nämlich nicht einfach sagen kann irgendwie,
oder dass man üblicherweise, wenn man jetzt irgendwie mathematisch arbeitet,
dass man nicht einfach die Behauptungen aufstellt,
bis einem irgendwann mal einfällt,
ich behaupte mal C plus D mal N,
sondern dass man halt die ganze Zeit irgendwie mit Variablen und so arbeitet,
ja.
Ja, ich habe das jetzt ganz normal mit Masterdium gemacht und das steckt hier auch.
Das kannst du nicht mit Master-Theorie machen. Das kannst du,
für die Kamera, er sagt ja gerade,
er macht das mit den Master-Theorien,
das kannst du doch gar nicht mit Master-Theorie machen.
Das ist ja nicht das Master-Theorie. Mach mal,
das machst du für den Ball auch für,
wenn man dann sich auch
Ja, aber das Mastertheorium, mit was für Rekorenz da das Mastertheorium gearbeitet?
Kommt Leute, ihr werdet nicht dafür bezahlt, dass ihr
anwesend seid. Also denkt mit, es lohnt sich sonst
noch gar nicht. Boah, das Ende aufgezogen wurde.
Ja, genau, wo das Ende irgendwie aufgeteilt wird.
Sowas wie die Tee von Engleich, Tee von Enhalbe.
Und dann bla bla bla. Das ist doch. Ja,
aber da haben da, da haben die wieder Hand.
Das ist zweimal von links und rechts.
Also zwei Tee von Enhalbe, World Plus.
Würde mal jemand
Er sagt, wenn der Baum balanciert wäre, dann.
Aber ist er ja gar nicht. Oder muss er nicht
sein. Du nimmst gerade einen Algorithmus und sagst,
ich mache eine Laufzeitanalyse unter bestimmten Annahmen der Eingabe.
Wenn die Eingabe so und so aussieht, dann kann ich die und die Laufzeitanalyse machen.
Weiß man, das ist ja der Idealfall, oder?
Ja, das mag sein. Also je nachdem, was
man jetzt machen will. Für den Innenorder Treebalk nicht,
für den Innenorder Treebalk ist es egal gerade,
ob der Baum so balanciert ist oder nicht,
weil der ja Täter von Ende braucht.
Das wird nicht schneller dadurch, dass der nicht ausbalanciert,
dass der ausbalanciert ist. Aber das,
was du gerade gemacht hast, jetzt mal auf Meta-Level,
mal weg von ihrem Beispiel, ist,
dass du gesagt hast,
ich will eine Laufzeitanalyse von einem Stück Sourceboot machen
So, und dann hast du gesagt,
wenn die Eingabe wie folgt aussieht, dann,
und durch dieses Wenn hast du ganz viele Klassen der Eingaben ausgeschlossen und hast dich nur auf ganz,
naja, auch immer noch auf viele,
aber nur auf einen Teil der Eingaben beschränkt.
Das ist keine Worst-Case-Analyse mehr, sondern eine andere Art von Analyse.
Gut. Und für den generellen Fall kannst du
das Master-Theorie nicht benutzen, weil die Rekurrenzgleichung da,
dieses Tee von denen gleich TK plus und so weiter,
nicht in dieses Muster reinpasst.
Da brauchst du ja dieses TVN gleich Amal Tee von N
Plus F&N. So muss die aussehen. Und das tut sich
ja nicht. Und das lässt sich, würde ich
jetzt mal behaupten, auch nicht mit variablen Transformationen hinkriegen.
Also das hatten wir ja auch nochmal,
dass wir irgendwie so ein Wurzel-End durch variable Transformation hingekriegt haben.
Aber das geht hier auch nicht. Ja. Fragen? Eine Sorte,
die mir gerade aufgefallen ist, gewöhnt man denn bitteschön sein?
Warum eigentlich speichern? Warum eigentlich zu sein? Werden wir uns mal so vorstellen,
man müsste ja nicht die Speicherart wie beim Eat mehr machen.
Sondern dadurch, dass wir ja die Vorbedingungen gelten,
dass der Regler eines Baum der Regaren wie kleines,
wie die Wurzel und die sich der Rechte einfach mal kurz ist,
wie die Wurzel, können wir es einfach machen,
dass wir sagen, da baumstraugisch hier präsentiert,
dass wir ein sortiertes Array haben.
Und wir strukturieren im Prinzip die erste, den wir sagen,
ein linkes Element ist der eine Arzt,
da haben wir Wurzel, damals weiß ich noch.
Darf ich kurz? Darf ich kurz? Er meinte gerade,
warum machen wir denn das nicht so wie beim Heat mit dem Array?
Also so grob, ne? Warum machen wir das nicht
? Und Leute, worum geht es in dieser Vorlesung?
Warum nehme ich kein Array? Wir wollen dynamisch werden.
Dankeschön. Wir wollen eine dynamische Datenstruktur. Mit dem Array
machst du alles kaputt, was wir versuchen.
Denn das ist eine statische Datenstruktur
Ist doof. Und um die Anfangsfrage, wie wir ja speichern,
wir speichern das so ähnlich wie verkettete Listen,
Knoten und ein Zeiger auf das linke Kind,
zeig auf das rechte Kind, wenn wir wollen,
noch ein Zeiger auf den Vater.
Kann man, glaube ich, richtig cool als Multiple-Choice-Aufgaben in
der Klausur nehmen. Wie wird ein binärer Suchbaum gespeichert?
Heep-Liste, Graf. Sonst Fragen dazu gerade.
Also das war jetzt gerade die erste Operation,
dieses In-All-Artry Walk
Gut, dann die, die wir jetzt, die primären,
Tree Search 7, haben wir schon so ein bisschen diskutiert,
man macht das genauso wie bei der Binärensuche. Ich fange beim Wurzelknoten an,
auf den habe ich immer einen Pointer, habe eine Wurzel an,
dann gucke ich, ist die 7, die ich suche,
ist die kleiner oder größer als der Knoten, den ich gerade betrachte.
Wenn es kleiner ist, suche ich im linken Teilbaum,
wenn es größer ist, suche ich im rechten.
7 ist kleiner als 10, also suche ich im linken Teilbaum,
dann bin ich irgendwie hier bei der 6, 7 ist größer als 6,
also suche ich im rechten Teilbaum, nämlich bei der 8,
7 ist kleiner als 8, also suche ich im linken Teilbaum,
7 bis 7, also habe ich das Element gefunden.
15 so ähnlich, ich gehe von der 10,
zweimal nach rechts, dann bin ich bei der 15,
und bei der Elf ist größer als 10,
also bin ich bei 14, 11 ist kleiner als 14,
also bin ich ja bei 12, 11 ist kleiner als 12,
also müsste ich im linken Teil bei um der 12 weitersuchen,
da ist keiner, also ist die Elf nicht drin.
Jetzt kann man sich überlegen, wenn der Baum ausgeglichen ist oder grob ausgeglichen,
also wenn der Ende Elemente hat,
die sich relativ gut in so einem binären Baum verteilen,
dann brauche ich so grob O-Log-End-Schritte.
Wenn der Baum unausgeglichen ist, also quasi in so
einen Strang entartet, dann brauche ich einen Schritte.
Also ist die Worst-Case-Laufzeit für diesen Tree-Search sehr wahrscheinlich O von N,
was eigentlich nicht so gut ist wie das Ziel,
wir streben ja eigentlich das Ziel an,
dass das alles in O von Log-End geht,
aber jetzt erstmal so als Idee bringt er binäre Suchbarm zumindest da schon mal,
dass es viele Fälle gibt, bei denen das in besserer Zeit läuft als den Ofen innen.
Da nochmal die Idee bei dem Tree-Search,
wir beginnen mit der Wurzel und suchen dann,
je nachdem, im linken oder rechten Teilbaum weiter.
Entweder wird der Schlüssel gefunden oder wir erreichen so ein Neil Knoten,
wenn er gefunden wird, sagen wir, da ist er,
wenn wir Neil Knoten erreichen, sagen wir,
gibt es nicht.
Da ist es als rekursive Variante
Und da auch die Knoten, die während der Rekursion gesucht werden,
binnen einen Pfad von der Wurzel bis maximal zu einem Blatt.
Korrektheit folgt aus Eigenschaft minäre Suchbäume. Laufzeit ist in O
von H, wobei H die Höhe des Baumes ist.
Und genau, da kann man, wenn man möchte,
jetzt auch wieder mit Rekurrenzgleichung arbeiten.
Moment. Ja, nee, da macht keinen Sinn,
mit Rekurrenzgleichung zu arbeiten. Da habe ich auch immer nur
einen Aufruf. Da brauche ich das gar nicht machen.
Da rufe ich mich zwar rekursiv auf hier,
aber ich habe ja nicht mehr rekursive Aufrufe, die wieder zurückgeliefert werden.
Also ich steige ja einfach nur ab.
Da brauche ich iterativ, kann man das auch machen.
Da sieht man schneller, wie die Laufzeit ist.
Solange X Ungleich Nil ist und Karl noch nicht der Schlüssel,
gucke ich immer, ist der Schlüssel, den ich suche,
gerade kleiner als das, was ich mir angucke.
Falls ja, gehe ich in den linken Baum,
sonst gehe ich in den rechten und fertig und irgendwann gebe ich das X zurück.
Entweder breche ich ab, weil X gleich Nil ist,
dann gebe ich Nil zurück oder weil ich gerade die Position gefunden habe,
wo das gesuchte Element ist und dann liefere ich halt da den Index zurück.
Alles gut. Fragen zum Suchen,
da wir das Peniere suchen gemacht haben und das einfach
nur eine Umformulierung des Penierensuchens auf dem Baum ist,
ist das eigentlich
relativ einfach
Wie kommt man ans Minimum? Also Tree Search 5 ist klar,
5 ist kleiner als 10, 5 ist keiner als 6,
5 ist größer als 3, dann habe ich die 5 gefunden.
Wie kommt man ans Minimum? Genau,
man geht immer nach links so lange, wie es geht,
bis ich irgendwann, bis irgendwann der linke Knoten Nil ist,
dann habe ich das kleinste Element gefunden.
Und Maximum genauso, ich gehe immer nach rechts,
bis ich da das Große gefunden habe.
Also Tree Minimum, solange ich noch nach links gehen kann,
tue ich das, bis ich das irgendwann nicht mehr machen
kann und dann liefere ich mein X zurück
Muss man ein bisschen aufpassen, dass man hier eben als Abbruchbedingung hat,
dass das linke Kind Neil ist und nicht X selbst.
Ansonsten hat man ja nicht das Minimum gefunden,
sondern ist irgendwie da gelandet, dass X jetzt Neil ist und
dann kommen wir auch nicht mal in den Vater rein.
Das ist irgendwie doof. Also muss man gucken,
dass links X X ungleich Neil ist.
Und bei Maximum genauso nur eben mit rechts und Analyse,
sowohl was korrekter als auch Laufzeit angeht, ist wie bei Tree Search.
Kann man auch wieder sagen, naja, irgendwie links ist
das Element immer kleiner. Also wenn ich nach links gehe,
dann muss ich irgendwann das Minimum rankommen und Laufzeit wieder da.
Ich ja maximal von der Wurzel bis zu einem Blatt gehe,
ist das ein O von Haar
Und auch da, Ovan Haar klingt immer gut,
in der Höhe des Baumes, ist aber nur dann gut,
wenn der Baum eben ausgeglichen ist.
Wenn das ohne ausgeglichen ist, ist es wieder ein Ofenen.
So, Herausforderung. Wir wollen den Nachfolger eines Knotens in der sortierten Reihenfolge ausgeben.
Vorgänger ermittelt man analog, können wir gleich mal machen,
aber konzentrieren wir uns mal auf den Nachfolger.
Wenn ich zum Beispiel die sechs habe,
dann würde ich als Nachfolger die sieben haben,
weil die sieben in dem Baum drin ist.
Wenn ich die zehn habe, dann will ich als Nachfolger
die zwölf haben. Elf ist nicht drin,
ist der nächste direkte Nachfolger die zwölf.
Also ich gucke mir quasi die sortierte Reihenfolge an und
dann will ich quasi das nächste Element meines betrachteten Knotens haben
Wie kriegt ihr den? Es gibt einen einfachen Fall,
es gibt einen schweren Fall. Genau. Zusammengefasst,
ich gebe das Minimum des rechten Teilbaums zurück.
Im rechten Teilbaum sind alle Elemente, die größer sind.
Das heißt, den linken Teil bereue ich gar nicht betrachten.
Da sind ja alle Elemente kleiner, ich will ja den Nachfolger haben.
Also gucke ich mir den rechten Teilbaum an und da ich
den nächsten habe, also den direkten Nachfolger haben will,
gucke ich mir vom rechten Teilbaum
das Minimum an
Das ist dann der gesuchte Nachfolger. Das funktioniert bei 10.
Ich gehe in den rechten Teilbaum und da das Minimum,
Minimum im rechten Teilbumm von 10 ist 12. Super.
Das funktioniert auch bei 6. Ich gehe in den rechten Teilbaum
und da das Minimum, Minimum ist 7, passt super.
Und bei 9, bei 9 gehe ich in den rechten Teilbaum,
der ist nicht da, was tue ich jetzt?
9 hat ja auch einen Nachfolger. Wie kriege ich
die Nachfolger? Wie kriege ich die Nachfolger von 2,
5, 7, 9, 13, 16?
Wie kriege ich
die Nachfolger
Ja, er meinte, man geht so lange nach oben,
bis man Knoten gefunden hat, der größer ist.
Dazu müsste man die Schlüssel vergleichen, also es funktioniert,
aber dazu müsste ich einmal die Schlüssel,
dann muss ich bei jedem Schritt einmal die Schlüssel vergleichen.
Man kann das sogar machen, ohne jemals die Schlüssel zu vergleichen.
Wann weiß man, ich habe ein Element gefunden, das größer ist?
Wenn du dir das zum Beispiel anguckst bei der neuen,
wann das bei der neuen immer weiter nach oben,
bis was passiert
Bis du bei der 10 bist. Klar,
aber welche spezielle Eigenheit, was ist da gerade passiert?
Genau, man geht einmal nach rechts quasi, also so bildlich gesprochen.
Das steht da gleich total verklausuliert, aber so,
die Idee ist, ich gehe immer weiter nach oben,
bis ich das erste Mal nach rechts gehe.
Also bis ich quasi das erste Mal im linken Teilbaum eines gefundenen Vaters bin.
Bei der neuen gehe ich immer weiter nach oben, 8,
6 und bei der 10 gehe ich das erste Mal nach rechts,
dann weiß ich, okay, die 10 ist ja die nette Nachfolger.
Bei der 13, 12,
14 bin ich das erste Mal nach rechts gegangen,
14 ist den Erfolger bei der 16,
15,
14,
Kein Nachfolger mehr da, kein Vater mehr da,
also hat 16 gar keinen direkten Nachfolger.
Wissen wir denn das, wenn wir rechts gehen und Algorithmus?
Wir wissen, warum wir das mit Vorbau haben?
Der Knoten selbst weiß nur, dass er einen Vorgänger hat.
Dein Vorgänger weiß aber, ob du in seinem linken oder rechten Teilbaum bist.
Und das kannst du benutzen. So ein bisschen Zeigerspielerei,
weil du dann irgendwie von X ausgehend sagst,
P von, also Paron von X.
Und wenn ich dann Paron von X in Left X bin,
also wenn ich Left X von Paron,
wenn ich Left PX bin quasi, also vom Vater der Linke.
Genau, so kann man die machen.
Also bei der 6, Nachfolger bei der 6,
genau,
komme ich zu
Bei der neuen wandere ich irgendwie nach oben zur Zehn.
Also der Nachfolger eines Quotens X ist der Knoten mit dem kleinsten Schlüssel der Größe,
das ist der Schlüssel X, größer als Schlüssel
X, heißt im rechten Teilbaum und dort am kleinsten,
weil es ja der kleinste Schlüssel ist, heißt das Minimum.
So, jetzt gibt es das Problem, X muss nicht,
muss gar keinen rechten Teilbaum haben. Und jetzt sehr verklausuliert,
dann ist der Nachfolger Y der jüngste Vorfahrer von X,
dessen linkes Kind ebenfalls ein Vorfahrer von X ist.
Also der jüngste Vorfahrer von X,
ich gehe immer weiter nach oben und sobald dieser Vorfahrer dann
einmal linkes Kind ist,
bin ich fertig
Wenn das auch nicht geht, ist X das größte Element
und hat keinen Nachfolger. Ja, genau,
da steht es nochmal, also ganz unten steht es umgangssprachlich,
wir gehen im Baum immer weiter nach oben,
wenn wir das erste Mal nach rechts gehen, haben wir den gesuchten Knoten.
Dann ist der Nachfolger Y der jüngste Vorfahrer von X,
also die 6, dessen linkes Kind, äh Quatsch,
die 10,
weil die 10 ist der jüngste Vorfahrer von X,
dessen linkes Kind ebenfalls ein Vorfahrer ist von der
Bei den anderen war das nicht so,
bei der Acht zum Beispiel ist der linke,
ist das linke Kind eine 7, das ist kein Vorfahre.
Bei der Sechs ist das linke Kind eine 3, das ist auch kein Vorfahre,
das liegt ja nicht auf so einem Pfad zur Wurzel.
Pseudocode. Die ersten drei Zeilen sind einfach,
da gucke ich, gibt es einen rechten Teilbaum,
falls ja, liefere ich vor dem das Minimum zurück.
Das war die einfache Geschichte. Ansonsten,
falls ich keinen rechten Baum habe, dann sage ich jetzt immer,
Y soll mein Vater sein und solange der jetzt noch nicht,
solange der jetzt noch existiert, gucke ich eben immer in dieser Schleife,
ob die eben definierte Bedingungen dazutrifft.
Dann setze ich hier immer X auf rechts Y,
da gucke ich, ob X gerade rechts Y ist.
Falls ja, gehe ich weiter nach oben und falls das nicht der Fall ist,
dann bin ich eben gerade einmal nach rechts gegangen und kann auffüllen.
Und hier den Vorgänger, das macht man so ähnlich,
da gehe ich den Vorgänger,
gehe ich in den linken Teilbaum und bestimme da das Maximum,
statt in den rechten Teilbaum zu gehen und das Minimum zu bestimmten.
Im linken Teilbaum sind all die Elemente,
die kleiner sind und das Maximum davon ist mal
direkter Vorgänger
Und sonst gehe ich eben so wie hier auch nach oben und sobald ich
einmal nach links gegangen bin, liefere ich meinen Vorgänger.
So, jetzt kommt die, das war jetzt alles noch ohne Manipulation, ne?
Ja. Bitte? Ob Haar gleich Login ist,
nur wenn der Baum balanciert ist, heißt die Höhe des Baumes,
wenn der Baum balanciert ist, dann ist das ungefähr Login,
also Ende ist die Anzahl der Knoten.
Wenn der Baum nicht balanciert ist,
also im Worst Case einfach nur so ein Strang ist,
dann ist die Höhe des Baums ja nicht Login,
sondern gerade Ende.
Ja, auch immer noch so grob in Login, ne?
Der ist grob ausgeglichen, so grob in Login.
Also jetzt ist Hahn nicht gleich Login, aber so O-Login, ne?
Also zumindest deutlich dichter ein Login dran als ein N.
So, jetzt wollen wir die Datenstruktur erstmalig manipulieren.
Deswegen machen wir ja den ganzen Kram mit dem dynamischen Datenstrukturen.
Wir wollen einfügen und wir wollen löschen können.
Wenn wir einfügen oder bei beiden, auch beim Löschen,
müssen wir halt darauf achten, dass diese Suchraumeigenschaft immer erhalten bleibt.
Also wir können nicht einfach irgendwo einfügen,
beim Einfügen geht es gleich noch relativ einfach,
wie man das macht,
aber wir können doch nicht einfach
überall löschen
Einfügen ist noch relativ einfach, genauso wie beim Suchen.
Gucken wir quasi nach, ob das Element da ist,
bis wir irgendwann quasi die Stelle finden,
wo Nils steht, wo es halt hinpasst.
Und dann fingen wir es einfach als Blatt ein.
Wenn ich hier die Elf einfügen will, dann mache ich das so wie beim Suchen.
Ich gucke halt, Elf ist größer als zehn,
also muss ich es hier, es ist kleiner als 14,
also muss es hier hin, es ist kleiner als zwölf.
Also müsste es hier stehen oder so,
tut es aber nicht, also setze ich es dahin.
Und bei der Vier so ähnlich, vier ist kleiner als zehn,
ist kleiner als sechs, ist größer als drei,
ist kleiner als fünften, kommt es dahin.
Elf passt noch sehr hübsch dahin,
die vier passt da irgendwie nicht mehr so hübsch hin,
da machen wir
eine neue Ebene auf
Genau. Und das Wichtige ist halt,
man kann jetzt nicht einfach warten, bis man bei mir ist,
sondern man braucht immer einen Zeiger auf den Vorgänger,
damit man dann irgendwann sagen kann, ah okay,
jetzt hat mein Knoten kein Kind,
da müsste das aber hin und dann kann ich es da einsetzen.
Im Kot halten wir einen Zeiger auf X,
da soll das neue Element hin und ein Zeiger Y auf
dem Paren von X. Und irgendwann ist X nämlich Neil,
da soll das Element jetzt ja hin.
Und dann habe ich aber immer noch meinen Zeiger Y auf
dem Parent und genau an die Stelle an Y wird dann das neue Element angehängt.
Also links oder rechts, je nachdem.
Da wird die Stelle gesucht
Das ist der erste Teil, das ist zweiteilig. Da fange ich an,
irgendwie mit X ist Wurzel, solange X noch nicht Neil ist,
ist Y immer gerade X und X wird dann entweder linkes oder rechtes Kind,
je nachdem, ob der Schlüssel klein oder größer ist,
genauso wie beim Suchen.
Und dann nennt man halt diesen Punkt erreicht,
dass nachher Y genau der Vaterknoten ist,
an den ich mein X ranhängen will und X ist dann
eben Neil mit X, kann ich da nichts mehr machen.
Also ich breche hier erst ab, wenn X Neil ist.
Ja, genau. Und jetzt, da wird es halt
angehängt, ne? Paron von Z soll Y sein,
wenn, also Z ist der Knoten hier, Moment,
da Z ist der Knoten, der eingefügt werden soll.
Das ist das Argument, was wir kriegen
Paren von Z wird das Y,
wenn Y gerade Nil ist, das passiert nur,
wenn der Baum ganz am Anfang leer ist,
dann ist der neue Knoten eben die Wurzel des Baumes und ansonsten gucken wir,
ist der Schlüssel da, das, was wir einfügen wollen,
ist das kleiner als gerade der Schlüssel Y,
dann wird das das linke Kind und wenn es größer ist,
wird es das rechte Kind.
Genau. Das ist schon das Einfügen.
Löschen. Einfügen ist einfach, ne, Einflugsitze irgendwie,
ich gehe wie beim Suchen durch und dann tue ich es
halt an die richtige Stelle
Löschen ist schwierig. Löschen hat wieder so ähnlich wie auch
bei den verketteten Listen, so mit Zeigerspielereien zu tun.
Also wenn ja an die verkettete Liste denkt, ihr habt irgendwie drei Elemente,
die sind miteinander verbunden und das in der Mitte löscht ihr.
Dann müsst ihr irgendwie dafür sorgen,
dass diese Zeiger hier jetzt nicht irgendwie ins Nirgendwo zeigen und
der eine zeige aus dem Nirgendwo irgendwo hin,
sondern dass sie die Zeige irgendwie anständig umbiegt.
Und genau das müsst ihr bei diesem Baum auch machen.
Und jetzt gibt es eben drei Fälle, die man unterscheiden kann.
Da kann man sich relativ schnell überlegen,
es gibt einmal den Fall, ich habe keine Kinder,
kann ich mich einfach löschen. Es gibt den Fall,
ich habe genau ein Kind, dann kann ich sagen,
okay, ich kann mich löschen und mein Vater mit dem
Kind verbinden
Und dann gibt es noch den Fall, dass ich zwei Kinder habe.
Da ist es ein bisschen schwierig, weil da kann ich
mich nicht einfach löschen und meine beiden Kinder mit dem Vater verbinden.
Dann hat er plötzlich drei Kinder, das ist irgendwie doof,
das muss ich irgendwie anders machen. Also, ja.
Wie viele Kinder der Vater, das ist ja egal.
Oder worauf beziehst du dich gerade? Schließ alle Kinder weg
. Dann kannst du die noch einfach führen.
Nein, warum nicht? Er meinte gerade,
wenn der Vater bisher nur ein Kind hatte,
Ja, fragen wir trotzdem. Für alles gut. Der
Vater hat nur ein Kind, mich. Und ich habe
zwei Kinder. Warum kann ich mich jetzt nicht löschen?
Und meine beiden Kinder an den Vater ranhängen.
Warum geht das nicht? Also ich bin jetzt der Vater
. Genau. Das ist irgendwie, ich bin entweder entweder
kleiner oder größer als mein Vater und meine Kinder genauso.
Also meine Kinder, wenn ich kleiner bin,
sind meine Kinder auch kleiner, noch kleiner.
Und wenn ich größer bin, sind meine Kinder noch größer.
Und dann kann ich mich nicht links und rechts an meinen Vater ranhängen.
Das mache ich kaputt dann. Also die drei Fälle,
Z hat keine Kinder, kann man den einfach löschen
Z hat genau ein Kind, dann wird der Vater von Z und wahlweise,
entweder habe ich ein linkes oder ein rechtes Kind,
die beiden werden verbunden und damit wird Z dann eben ausgeschnitten.
Und wenn ich zwei Kinder habe, das ist der spannende Fall,
dafür brauchten wir auch die Nachfolgeoperation,
dann bestimme ich meinen Nachfolger, der hat kein linkes Kind,
sonst wäre es ja nicht mein Nachfolger,
sonst würde ich ja weiter in den Baum entlang gehen.
Das heißt, mein Nachfolger hat nur ein Kind,
den schneide ich aus und also der werde ich quasi,
sehen wir gleich ein Beispiel.
Einfaches Beispiel, die zwei, kein Kind löschen.
Vater von 15 und Kind von 15, je nachdem,
ob link ist oder rechtes Kind in dem Fall,
das rechte Kind, die beiden miteinander verbinden.
Also hier, der da wird umgebogen und dann wird die
15 gelöscht. Fertig. Schwieriger Fall, ah nee,
der nochmal drei, auch wieder ein Kind umbiegen.
Fertig. Und jetzt der schwierige Fall,
wenn ich die Zehen löschen will, die hat zwei Kinder
und in dem Fall ist sie sogar noch die Wurzel.
Jetzt bestimme ich den direkten Nachfolger, das heißt,
ich gehe in den rechten Teilbaum und bestimme da das Minimum,
also bestimmt die 12,
So, und dann habe ich die zwölf. Und jetzt ersetze ich
die zwölf oder jetzt ersetze ich die zehn durch die zwölf.
Das kommt dann nach oben. Und die zwölf hat ja nur ein Kind,
sonst wäre es ja nicht der direkte Nachfolger der zehn.
Die kann kein linkes Kind haben. Und da kann ich nur,
die schneide ich jetzt ganz normal aus. Sprich,
ich hänge alles das, was hier jetzt noch kommt an die 14 rein.
Also erstmal schneide ich die zwölf aus.
Und dann wandert die zwölf dann nach oben.
Zack, fertig. Wenn man sich das anguckt,
ist immer noch ein wenigerer Suchbaum, ne?
Alles gut. Wenn man das machen möchte,
ist das Zeigerspielerei
Warum kann die das nicht haben? Ja, warum kann die zwölf nicht
einfallsgehend haben? Das habe ich doch dreimal gesagt, oder?
Genau, was wäre dann die Elf? Nicht der... Ja,
wenn die zwölf noch ein Kind hätte,
dann wäre die zwölf nicht der direkte Nachfolger der Zehn,
sondern dieses Kind.
Also dann, wenn hier noch die Elf wäre,
dann bestimme ich die Elf als direkten Nachfolger.
Das hätte dann den Vorteil tatsächlich,
dass die dann vielleicht keine Kinder mehr hat und dann kann ich
direkt einfach die Elf an die Stelle der Zehn schieben und bin fertig.
Da muss ich nicht
noch ausschneiden
Ich also auch nochmal, nachdem ich den nachfolge,
den direkten Nachfolge der 10 bestimmt habe,
muss ich gucken, hat die ein oder zwei Kinder,
ein oder null Kinder, jetzt sage ich selbst schon,
ein oder null Kinder.
Wenn die null Kinder hat, kann ich einfach diese Sätze machen,
wenn sie ein Kind hat, muss ich noch diese Verbiegung hier machen.
Aber da ich schon die Routinen habe für ausschneiden und so,
kann ich die einfach benutzen. Gut. Das war dieser Inorder Treewalk,
das Minimum, Maximum suchen, löschen, einfügen.
Will ich noch was vergessen? Vorgänger-Nachfolger. Ja. Da habe ich das wirklich verstanden,
also wenn man jetzt mit zwei Kindern hat,
dann ist die Lösung im Prinzip immer so,
dass man am Ende nur noch mit einem Künstler zu tun hat,
je nachdem, ob es der Satz wurde oder nicht,
kann das sogar keine 30er verloren.
Jein, wenn du zwei Kinder hast,
so wie bei der Zehn, da, die zwei Kinder.
Jetzt bestimmst du den direkten Nachfolger der Zehn.
Dieser direkte Nachfolger ist das Minimum im rechten Teilbaum.
Das hat entweder ein Kind oder null. Wenn es null Kinder hat,
wenn hier jetzt noch die Elf dran wäre,
hätte die Elf hätte null Kinder.
Dann würde ich einfach die Elf nehmen und an die Stelle
der Zehn packen,
bin fertig
Wenn sie zwei Kinder hat, so wie hier,
äh, wenn sie ein Kind hat,
so wie hier, dann geht das ja nicht,
weil jetzt kann ich die ja nicht einfach rausnehmen.
So, dann mache ich das, was ich auch gemacht hätte,
wenn ich sie ganz normal gelöscht hätte.
Also jetzt führe ich es quasi auf diesen Fall zurück,
dass ich mit Knoten umgehe, die nur ein Kind haben.
Und einen Knoten löschen, der nur ein Kind hat,
da haben wir gesagt, okay,
dann kann ich den Vater dieses Knotens mit seinem Kind verbinden.
Also die 14 Mitte 13. Jetzt ist sie 12 isoliert.
Und jetzt kann ich sie nehmen,
jetzt ist sie quasi so wie so ein Blatt,
so von der Idee her,
jetzt kann ich sie nehmen und an die Stelle der Zehen
setzen.
Fertig. Soll ich
noch fragen
Dann einmal zusammengefasst, In-Order Treebalk haben wir gemacht.
Gibt die Elemente in sortierter Reihenfolge aus. Wenn man Pre-Order Treebalk haben will,
dann schiebt man halt das Ausgeben des Elements ganz an Anfang.
Und dann hat man die beiden rekursiven Aufrufe,
wenn man Post-Order Treebalk haben will, dann macht man erst
die beiden rekursiven Aufrufe und gibt dann das Element aus.
Tree-Search, genauso wie Binäres Suchen, entweder im linken oder rechten
Teilbaum weitersuchen, je nachdem, was gerade größer ist.
Je nachdem, ob ich gerade kleineres oder größtes Element suche.
Minimum-Element ganz links, Maximum-Element ganz rechts, Nachfolger,
der Knoten mit dem kleinsten Schlüssel,
der größer ist
Also Minimum mit dem rechten Teilbaum oder jüngster Vorfahrer von X
, sodass ich das erste Mal nach rechts gehen muss.
Vorgänger der Knoten mit dem größten Schlüssel, der kleiner ist,
also Maximum im linken Teilbaum, der größte Schlüssel,
also Maximum, der kleiner ist, also im linken Teilbaum.
Oder eben der jüngste Vorfahrer, dessen Reche das Kind ebenfalls
in Vorfahre ist. Einfügen, ähnlich wie beim Suchen,
die geeignete Position suchen und das neue Element anders als neues Blatt einfügen.
Das ist einfach löschen die drei Fälle, die wir eben hatten.
Entweder habe ich kein Kind oder ich habe ein Kind oder ich habe zwei Kinder.
Kein Kind geht einfach, einfach rausschmeißen. Ein Kind,
Vater und Kind verbinden, zwei Kinder,
Nachfolger bestimmen und mich durch den Nachfolger setzen und dann
eben unter Umständen durch die Nachfolger jetzt ja rausgenommen habe.
Nochmal eins oder zwei machen, je nachdem. Hier stimmt das,
wo hier ganz unten in der vorletzten Zeile steht,
siehe zweitens oben, das stimmt nicht ganz.
Siehe zweitens oder erstens oben. Erstens ist es halt nur,
erstens würde er eintreffen, wenn der Nachfolger kein Kind hat.
Das kann ich
mal korrigieren
Genau. So, und jetzt, die ganzen Operationen da,
alle korrekt, folgt fast immer recht einfach aus diesem vinären Suchbaum-Eigenschaften.
Nochmal mit Luktionen untermauern, dann ist man fertig. Und all die Operationen,
wenn ich einen vinären Suchbaum mit Endknoten der Höhe habe,
laufen all diese Operationen in O von H,
außer dieses In-Order-Tree-Walk, das ist in O von N,
das ist auch nicht zu erwarten, dass das irgendwie besser geht.
Ich will ja alle Knoten ausgeben. Genau, also in
O beziehungsweise Täter sogar. Und es ist das Problem,
das sieht zwar gut aus, so,
ist ja auch schön, aber Wenn der ungefähr ausgeglichen ist,
dann ist Haar halt ungefähr Lock von Ende, dann ist das schön.
Aber die müssen halt nicht ausgeglichen sein. Als Beispiel,
wenn ich die Zahlen von 1 bis 10 in diesem binären
Suchbaum ablegen will und ich mache das auch tatsächlich so,
dass ich erst 1 einfüge, dann zwei, drei,
vier, fünf, sechs, sieben, acht, neun,
zehn, dann werden die genau in dieser Reihenfolge abgelegt.
Eins, zwei, drei, vier,
fünf und immer so weiter,
das ist irgendwie doof
Genau, jetzt sagt er gerade, kann ich so einen Baum nicht irgendwie annähern?
Also sprich, kann ich nicht beim Einfügen dafür sorgen,
dass der ausgeglichen ist. Sowohl beim,
also alle Opa, die war, die einzigen Operationen,
die was mit Manipulation zu tun haben, sind ja Einfügen und löschen.
Das heißt, ich muss dafür sorgen, dass sowohl beim
Einfügen als auch beim Löschen der Baum möglichst ausgeglichen bleibt.
So komplett werde ich das nicht hinkriegen,
aber das kann man sich ja schon überlegen,
wenn ich zwei Elemente einfüge, eins und zwei,
dann ist die Höhe halt gerade gleich der Anzahl der Elemente.
Da gibt es auch keine Möglichkeit, das irgendwie besser zu machen.
Also das ist okay. Aber so,
dass das grob ausgeglichen ist,
das hätte man gerne
Ja, das ist das Ziel. Also ich möchte jetzt
diese binärere Suchbäume behalten. Oder ich möchte diese Idee der binären Suchbäume behalten.
Das soll quasi minäre Suchbäume im Hintergrund sein. Ich möchte den aber so anreichern,
dass ich noch mit irgendwelchen Zusatzinformationen arbeiten kann und dafür sorgen kann,
dann mit Nutzung dieser Zusatzinformation, dass ich einfügen und löschen,
so realisieren kann, dass der Baum ausgeglichen bleibt.
Da kann man sich schon überlegen, da muss ich beim Einfügen
immer irgendwie komisch rummanipulieren. Also du sagst ja selbst,
wenn ich eins einfüge, jetzt zwei und jetzt drei zum Beispiel,
spätestens bei der drei muss ich vielleicht anfangen und sagen,
naja, statt eins, zwei,
drei hätte ich vielleicht gerne im Org, eins,
zwei,
Muss ich irgendwie so, das nennt man nachher auch
eine Rotation, da muss ich irgendwie komischen Kram machen.
Und das wird natürlich schlimmer, wenn da noch Elemente dranhängen.
Also wenn das schon irgendwie ein großer Baum ist,
dann habe ich wieder mit Zeigerspieler reinzutun.
Auftritt rot-schwarz-bäume. Rot-schwarz-bäume, binärer Suchbaum. Das heißt,
ich weiß, ich habe einen binären Suchbaum,
also all diese ganzen Zugriffsoperationen, wie suchen Minimum,
Maximum und so, die gehen weiterhin ganz genauso wie bei binären Suchbäumen.
Aber die kriegen zusätzlich alle Knoten, kriegen zusätzlich eine Farbe,
rot oder schwarz. Und die benutze ich nachher,
um bestimmte Eigenschaften sicherzustellen, mit denen ich dann zeigen kann,
dass der Baum immer ungefähr balanciert ist
Und dann weiß ich, okay,
dann klappt das alles auch super in O-Log-in. Problem,
insbesondere einfügen und löschen so zu implementieren,
dass diese rot-schwarzeigenschaft erhalten bleibt,
die wir gleich sehen werden und sie dann eben trotzdem noch in O-Log-in bleiben.
Also wenn ich beim Einfügen und Löschen nachher aus diesem O-Log-in
rausfalle, dann ist es auch wieder doof.
Ich will die gerne alle in O-Log-in haben.
Die rot-schwarzeigenschaften sind die folgenden. Jeder Knoten ist entweder rot
oder schwarz. Okay. Wurzel ist schwarz. Auch okay
. Blatt ist schwarz.
Auch okay
Kann man ja aber so definieren. Und jetzt kommen die
spannenden Eigenschaften, so die, die schwierig sind, sicherzustellen.
Also die Eigenschaften sind alle einfach sicherzustellen,
wenn ich eine Wurzel einfüge, dann weiß ich,
okay, ich mache die schwarz,
wenn ich einen Knoten einfüge und das ist ein Blatt,
dann weiß ich auch, ich mache das schwarz.
Also das könnte ich alles ziemlich einfach sicherstellen.
Schwierig wird das sicherzustellen, wenn ein Knoten rot ist,
dann sind seine beiden Kinder schwarz, das möchte ich gerne sicherstellen.
Und ich möchte sicherstellen, das,
für jeden Knoten enthalten alle Pfade,
die an diesem Knoten starten und in einem Blatt enden,
die gleiche Anzahl schwarzer Knoten
Das wird schwierig, das sicherzustellen, mich jetzt irgendwie einen Knoten einfüge.
Da muss ich irgendwie rumdoktoren, damit das weiterhin sichergestellt ist.
Deswegen sind auch nicht zwingend alle Knoten, die ich neu einfüge,
schwarz, sonst würde ich jetzt immer einfügen und sagen,
jeder neu eingefüllte Knoten ist ein Blatt,
also mache ich den schwarz.
Aber das ist blöd, dann kriege ich das nicht hin,
dass die Eigenschaften erhalten bleiben.
Das muss ich also irgendwie anders machen. Und wenn ich
das alles machen kann, dann gilt tatsächlich dieser Satz.
Ein rot-schwarzbaum mit en inneren Knoten, hat höchstens die Höhe
zweimal Log-in plus 1. Und dann laufen alle Operationen in O-Log-in,
weil wenn die Höhe das da ist,
das ist in O-Log-in und dann ist die Höhe,
wenn das die Höhe ist,
oh Log-in,
dann laufen die da alle drin
Ja, muss ich also sicherstellen,
dass die Einführoperationen in Oloc-N läuft und diese Eigenschaften erhalten bleiben,
dass die Löschoperationen in Oloc-N bleibt und diese Eigenschaften erhalten bleiben.
Und dann bin ich fertig. Dann gibt es noch AVL-Bäume, das sind solche.
Die wollte ich nur mal kurz hier an die Folien schmeißen,
damit ihr seht, so, ja, die gibt es auch.
AVL-Baum ist ein binärer Suchbaum, der Höhen balanciert ist.
Höhe des linken und rechten Teilbaums unterscheiden sich maximal um eins.
Und dann wird da eben auch dadurch sichergestellt,
dass die Höhen nicht zu doll auseinanderklaffen,
also dass die nicht zu unbalanciert wird und dann Hat man eben genauso über den rot-schwarz-bäumen,
das erreicht, dass all die Operationen in O-Log-N laufen,
aber eben mit einer anderen Idee.
Hier ist die Idee quasi, dass die irgendwie,
dass sie diese Höhenbalancierung habe.
Genau, und Rotation spielt eine wichtige Rolle,
können wir uns gleich noch mal kurz ansehen.
Das haben wir heute gemacht. Fragen dazu. Also rot-schwarz-bäume,
AVL-Bäume gerade nur, dass ihr das mal gesehen habt und
mal kennt, dass ihr wisst, okay,
man kann jetzt irgendwie noch ordentlich Gehirnschmalz da reinstecken.
Diese Datenstruktur aufzupimpen, damit dann eine bestimmte Operation,
die man haben will,
damit das alles
richtig schön schnell geht
Und O-Log Ende ist im Vergleich zu O-N super.
Also wenn ihr euch vorstellt, irgendwie, was weiß ich,
ihr habt irgendwie eine Datenbank mit einer Million Elementen.
Und ihr wollt da jetzt irgendwie drin suchen und das läuft
in O-Forne Ende. Dann habt ihr irgendwie eine Million Schritte,
wenn das in O-Login läuft, habt ihr 20 Schritte.
Super. Und eine Datenbank mit einer Million Elemente zu haben,
ist jetzt nicht so unwahrscheinlich. Fragen. Zum ersten,
zum zweiten Leuten. Dann, weil wir noch ein bisschen Zeit haben,
springen wir noch mal kurz hier in den Anhang mit den Rot-Schwarz-Bäumen,
dass ihr das nochmal seht.
Und dann machen wir irgendwie um Ich würde mal sagen,
um fünf nach halb oder so können wir Pause machen,
bis elf dann oder so und dann machen wir nochmal 45 Minuten PNP.
Aber einmal, dass ihr das nochmal seht, rot-schwarzeigen schafft,
wie viel da jetzt gleich noch so hinter steckt.
Das sind diese rot-schwarzeigenschaften. Und wenn man jetzt,
das war dieser binäre Suchbaum, wie wir den auch vorher schon gesehen haben,
der ist jetzt noch ohne Färbung.
Jetzt macht man das wie folgt, dass man,
das eine ist so ein bisschen so ein Kniff,
dass man quasi sagt, ich nenne nicht zwei,
fünf, sieben,
neun und so,
nicht das sind die Blätter,
sondern die Nilknoten sind die Blätter
Das ist so ein Ding, das hilft einem nachher bei der Implementierung,
weil man dann halt immer irgendwie auf Nil prüfen kann,
ob das Blätter sind und so.
Und das hilft einem auch ein bisschen beim Zählen.
Das macht man dann einfach so einmal, dass man sagt,
okay, all die Knoten haben halt zwei,
hat zwei Pointer auf Nil, weil sowohl der linke als auch der rechte.
Das linke, als ob das rechte Kind leer ist.
Zwölf hat halt nur einen Pointer auf Nil,
weil nur das linke Kind Nil ist.
Und das ist jetzt quasi das, weiß ich nicht, das Superblatt.
So, und zehn zeigt auch noch dahin. Nicht,
weil es ein Blatt ist, sondern weil die Wurzel von zehn eben,
also der Paron von zehn, nicht die Wurzel,
der Paron von zehn, auch Nil ist.
So,
und jetzt färbt man Wurzel und Blätter schwarz
Jetzt wohl gemerkt, das hier soll jetzt nicht auch zusätzlich noch die Wurzel sein.
Das ist jetzt quasi einfach nur das Blatt.
Als Wurzel wird schwarz gefärbt, Blatt wird schwarz gefärbt.
Das ist einfach so eine Konvention, die man da so hat.
Und jetzt kann man sich eben überlegen, wie kann ich
all die anderen Knoten färben, damit diese schwarz-roteigenschaft erfüllt ist?
Wenn ich die jetzt alle schwarz färbe, funktioniert das nicht.
Dann habe ich Hirnpfad von der Zehn mit 1,
2, 3, 4, 5 Knoten, also 10,
14, 15, 16, Neil, so 5 Knoten.
Und ich habe bei 10, 14, 12 Neil
vier Knoten
Die schwarz sind. Ich will aber für jeden Knoten enthalten,
alle Pfade, die an diesem Knoten starten und in einem Blatt
des Teilbaums dieses Knotens enden, die gleiche Anzahl schwarzer Knoten.
Also egal wo ich starte, will ich auf dem Weg
zum Blatt gleich viele schwarze Knoten haben.
Damit das funktioniert, muss ich jetzt irgendwie rote Knoten einfügen.
Und dann muss ich aber darauf achten, wenn der Knoten
rot ist, dann sollen beide Kinder schwarz sein.
Kann man so lösen, Frage. Jetzt alle Nil. Alle Blattknoten,
jetzt würde ich ein Nil ersetzen oder ein Nil sind,
haben wir
ein Blatt
Der Weg. Der die rotliche 10 ist, nicht auch die rotliche.
Nee, die Frage war nochmal auf dieser Neilknoten,
wenn Zehn denn da hin zeigt,
ob das ist nicht dann auch die Wurzel ist.
Nee, die Wurzel ist die Zehn, nicht der Neilknoten.
Dass man das nur noch macht, das macht man nur,
weil ja auch die Zehn als Parent nichts hat,
also auch Neil, aber die Wurzel bleibt weiterhin die Zehn.
Es macht auch keinen Sinn, den Neilknoten zur Wurzel zu machen,
weil Neilknoten ein kleines linkes und rechtes Kind hat.
Das heißt, wenn du das Ding als Wurzel hättest,
kommst du nirgendwo hin.
Du brauchst die Zehn als Wurzel. Wie wir
da bis jetzt im Baum
Nochmal die Frage, warum man die Nilknoten da unten als Blätter macht,
weil es einfacher nachher ist bei der Implementierung,
weil es auch einfacher ist bei den 10. Trust me.
Du kannst es auch nicht machen,
aber so ist es einfacher bei der Implementierung.
Fertig. Du hast ja sowieso die Nilknoten, ne?
Also du hast sowieso von jedem Knoten noch linkes und rechtes Kind Nil,
sonst wüsstest du nicht, dass es ein Blatt ist.
Also du brauchst das Ding sowieso. Jetzt kannst du überlegen,
entweder du färbst oder du färbst es nicht.
Und bei der Implementierung ist es leichter, dann zu sagen,
alles klar, es gibt diesen Knoten ja theoretisch irgendwie.
Also sage ich, es ist wirklich ein Knoten und der
ist dann
das Blatt
So, das wäre eine korrekte Färbung. Da wäre jeder
rote Knoten hat zwei schwarze Kinder und auf allen Faden,
egal wo ihr jetzt anfangt zu zielen,
ihr könnt bei irgendeinem Knoten anfangen,
dann sind auf dem Pfad von diesem Knoten zum Blatt gleich viele schwarze Knoten jeweils.
Und so schreibt man den üblicherweise, weil man ja weiß,
dass da unten noch diese ganzen Blätter sind.
So, das war die Rot-Schwarze-Eigenschaft und ich will eigentlich
noch schnell zu dieser Rotation kommen
Das beides brauchen wir nur. Also den Satz,
da kann man auch beweisen, wie man möchte.
Und aus dem Satz, wenn man das jetzt zeigen würde,
dass ein Rot-Schwarzbaum mit End-inneren Knoten höchstens diese Höhe hat,
das folgt im Grunde genommen aus diesen Rot-Schwarzeigenschaften.
Ich kann mir jetzt das angucken und jetzt weiß ich halt,
es kann nicht sein, dass ich Einfahrt habe,
der länger ist als diese zweimal Log-in,
weil ich dann eben ein Problem kriege mit der Abfolge von schwarzen und roten Knoten.
Da müsste ich da mehr rote Knoten reinmachen. Also ich kann ja
maximal hier einen Pfad haben, der nur schwarzen Knoten besteht.
Jetzt kann der hier höchstens doppelt so lang werden,
weil ich da ja so eine Abfolge von schwarz-rot-Knoten einbauen müsste.
Wenn ich dann nur schwarz-knoten einbaue,
habe ich da mehr schwarz-knoten als hier,
das funktioniert nicht
Ganz genau. Die inneren Knoten sind ja alle Knoten,
die kein Blatt sind. Und da wir jetzt hier praktischerweise
Neil als Blatt haben, kann man das ganz gut so formulieren.
Genau, den Satz oben wollen wir mal nicht beweisen.
Das ist einfach ein bisschen kombinatorische Spielerei. Der Satz da drunter
folgt direkt daraus. Wenn der rote Schwarzbaum diese Höhe hat,
dann weiß ich, dass diese ganzen Operationen hier suchen,
Minimum, Maximum, Vorgänger, Nachfolger, dass die alle in Oloc-Endzeit gehen.
Und jetzt die Operation, die man gleich braucht,
ist diese Rotation. So, jetzt kommt sie.
Also auch diese Operation für die Insultshie Delete gehen in Zeit
UN,
äh ULOGN
Aber die kann man jetzt eben nicht so machen wie beim normalen binären Suchbaum.
Wenn ich ganz normal einfügen würde, geht die Rot-Schwarzeigenschaft kaputt.
Wenn ich ganz normal löschen würde, geht die hohe Schwarzeigenschaft kaputt.
Also muss ich irgendwie die beiden Operationen nochmal detailliert anfassen,
um dafür zu sorgen, dass immer die Rot-Schwarzeigenschaft erhalten bleibt.
Wichtiges Hilfsmittel ist diese Rotation, die ändert die Zeigerstruktur.
Von der Idee her, wenn ich 1-1 füge,
2, Einfüge, 3 einfüge,
jetzt habe ich hier Platz in Vaterlänge 3,
das ist irgendwie doof, das ist nicht mehr ausgeglichen.
Also will ich irgendwie so rotieren,
dass die 2 in die Mitte kommt,
die 1 links ist und die 3 rechts ist.
Sowas will ich
irgendwie machen
So sieht so eine Rotation aus, hier,
wenn ihr irgendwie hier so ein Knoten-X habt,
da einen Knoten-Y. Hier hängt noch so ein Teilbaum A dran,
Teilbaum B, Teilbaum C.
Dann wollt ihr durch eine Linksrotation diesen Zustand erzeugen und durch
eine Rechtsrotation quasi aus dem Rechten, das Linke.
Genau. Sehen wir mal, im Detail, das ist die Linksrotation.
Das rechte Kind Y von X soll nicht Neil sein.
Also hier soll tatsächlich was dranstehen, deswegen ist der irgendwie größer gemalt.
Dieser Taibaum A, der könnte auch leer sein,
aber wichtig ist, der, da ist da.
Sonst macht diese Rotation keinen Sinn,
weil sonst hier plötzlich Neil stehen würde,
das ist irgendwie doof
Und dann ist die Idee, dass ich quasi,
wenn ihr euch überlegt, so hier an dieser Stelle,
also quasi da, wo sich dieser rote Pfeil biegt,
wenn da so ein Flock ist und ihr zieht quasi wie an so einer Perlenkette jetzt da dran,
dann wandert irgendwie das X nach unten,
das Y wandert nach oben und zieht das C so mit.
Und jetzt das Problem, das B bimmelt dann da irgendwie
so in der Gegend rum. Weil wenn ihr das so macht,
dann hat Y das linkes Kind X und als rechtes Kind C,
dann hat das keinen Platz mehr für das B,
das B muss also irgendwo hin.
Dann kann man sich überlegen, naja,
B soll größer sein als X und kleiner als Y.
Das erreiche ich hier auch kleiner als X und Quatsch größer
als X und kleiner als
Das passt also. Und hier war bisher gesagt,
Y ist größer als X, sprich X ist kleiner als Y,
das ist genau das, was ihr hier auch habt.
Also so die Suchbaumeigenschaft wird erhalten, ne? Hier steht es
nochmal mit dieser Idee von dem Ziehen an der Schnur.
Und dass das die Binäre Suchbaumeigenschaft erhält,
kann man sich eben, wie gesagt, eben,
wie ich das eben kurz gesagt habe, so überlegen,
dass man sagt, okay, B, kleiner Y,
größer X, das ist auf der rechten Seite genau auch erreicht.
Das ist die eine Rotation,
und das ist halt üble Zeigerspielerei,
also da muss ich
ordentlich Zeiger umbiegen
Plötzlich ist der Paron von Y nicht mehr X,
sondern der Paron von XS Y. Rechtes Kind von X
ist nicht mehr Y, sondern rechtes Kind von XSB.
Dafür ist das linke Kind von Y jetzt X,
all solche Zeiger muss ich irgendwie umbiegen.
Recht man aber hin. Und Rechtsrotation im Grunde genommen das Gleiche.
Ich ziehe an diesem A XYC, wie an so einer Schnur,
dann wandert das X nach oben. Und dann,
weil Y dann ja plötzlich rechtes Kind von X ist,
bimmelt das B wieder irgendwie in der Gegend rum und
der einzige sinnvolle Platz ist eben links von dem Y.
Das ist auch der einzige Platz, der frei wird.
Also auch da wieder ziehen wir an so einer Schnur
und dann hat man das
Er hält die biinäre Suchbaum-Eigenschaft. Und jetzt müsste man sich,
ach so, Rotation einmal ein Beispiel,
wenn man das da machen will, drei wandert nach unten,
sechs wandert nach unten, acht wandert nach oben.
Drei nach unten, sechs nach unten, acht nach oben.
Und die neuen wird mitgezogen. So,
jetzt ist die Frage, die sieben,
also bisher war die sieben, ja linkes Kind von acht,
kann es jetzt nicht mehr sein,
ist kein Platz mehr frei bei der acht.
Das heißt, ich muss mir irgendwie überlegen,
wo soll die sieben hin? Und die Idee war eben,
die dann zum rechten Kind der sechs zu machen,
das ist ja auch die Position,
die hier frei ist
Da macht es auch Sinn. Ja, implementieren ist halt
ganz viel mit Zeigerspielen. Laufzeit ist in O1, warum?
Ich biege ja nur so eine konstante Anzahl irgendwie von Zeigern um.
Alles wunderbar. So, beim Einfügen passiert zunächst das Gleiche
wie über normalen Suchbäumen. Das heißt, ich suche mir
die richtige Position, ich füge das Element da ein.
Die Farbe des neu eingefügten Knotens setzt sich auf rot.
Nicht auf schwarz, ich setze sie halt auf rot.
Und dann wird eine Routine aufgerufen, die eben sicherstellen soll,
dass diese rot-schwarzeigenschaft erhalten bleibt. Und die Genau, die
mit dieser Rotation von eben arbeitet. Und ein anderes,
zwei andere ganz wichtige Elemente jetzt bei der Rotation sind der Großvater,
also nicht mein Vater, sondern der Vater von meinem Vater.
Und mein Onkel, also wenn ich quasi zu meinem Vater gehe,
nee, zu meinem Großvater gehe und da den anderen Teilbaum,
das ist mein Onkel.
Hier steht er auch. Eine wichtige Rolle im zweiten Absatz
. Eine wichtige Rolle spielt der Onkel Y. Das ist rechts
PPPPZ. Oder eben links PPZ, je nachdem,
wo ich gerade bin
Okay, gucken wir uns das doch nochmal einmal ganz schnell an,
sonst hätte sich das gar nicht gelohnt, die ganzen Bilder zu machen.
Also, es gibt drei Fälle, die unterschieden werden.
Es ist jetzt einfach nur so, dass ihr einmal seht,
wie viel Gehirnschmalz man da so reinstecken kann,
jetzt diese Routinen sinnvoll zu implementieren.
Und das ist ja nicht nur auf die Routinen zu kommen,
sondern tatsächlich dann auch zu zeigen, okay,
die funktionieren auch wirklich so, also es ist irgendwie korrekt alles.
Hier ist Z-Rot,
das ist quasi mein
neu eingefügter Knoten
Und diese anderen hier a, b, c.de,
die stehen da nur deswegen schon, weil dieses,
diese Routine nachher im Baum nach oben wandert.
Also am Anfang habe ich ja ein Blatt eingefügt,
aber später wandert das Ding irgendwie nach oben und dann kann es eben auch sein,
dass der Knoten, den ich gerade festhalte, mein Z,
das deren linken und rechten Teilbaum als Kinder hat.
Deswegen steht das B, C da schon. Was ich mache,
ist, ich gucke mir meinen Onkel an, den hier.
Der ist jetzt rot. Dann mache ich folgendes,
ich färbe um und setze Z neu.
Ich mache die beiden hier schwarz, gehe und ha,
das F mache ich rot und das ist mein
neues Z. Also da seht ihr schon,
dass ich irgendwie jetzt einen Baum nach oben gewandert bin
Der andere Fall, analog, ich bin jetzt,
also die Fälle sind eigentlich identisch, hier bin ich jetzt im linken,
teilbar um das Kind, da passiert genau das Gleiche.
Also die beiden Fälle sind komplett identisch. Ah,
genau, das ist ja noch wichtig, ne?
In einer Weihschleife, in der immer geprüft wird,
ob der Vater von Z Ruth ist,
nur dann muss man fortfahren, werden nun drei Fälle unterschieden.
Also wenn der Vater hier gar nicht rot wäre,
dann müsste ich
gar nichts machen
Und wenn der rot ist, dann muss der hier schwarz sein,
weil wenn der rot wäre, hätte er zwei schwarze Kinder.
Nee, nee, nee, das F ist nicht der Wurzelklon.
Das ist jetzt quasi ein Snapshot irgendwie von mittendrin.
Irgendwie im Algorithmus wandert jetzt das Z nach oben.
Z ist ja am Anfang auch ein Blatt. Bis jetzt passt,
also ne, so den Snapshot guckst du dir an und dann daran arbeitest du.
Was jetzt mit dem Vater von den F ist, ist egal.
Wir wandern ja jetzt weiter nach oben, ne?
Wir kommen jetzt zu diesem Fall und jetzt gucken wir uns
als nächstes wieder den Vater von F an
Und wenn der wieder rot ist, müssen wir jetzt gleich weitermachen.
Wenn der schwarz ist, wären wir fertig.
Was macht so die Weichschleife? Das ist der gleiche Fall
. Jetzt gibt es solche Fälle. Jetzt ist der Onkel
von Z schwarz, eben war der ja noch rot.
Und wichtig, Z ist rechtes Kind seines Vaters.
Dann passiert Folgendes. Ich setze Z auf PZ,
also auf G, mache eine Linksrotation.
Und jetzt habe ich diesen Fall hier. Und jetzt mache ich
mit dem dritten Fall weiter. Der sieht so an,
da sieht man ja das und das ist das Gleiche.
Jetzt wird mein Onkel auf schwarz gesetzt,
also das Y hier
Nee, Moment. Ach, der Onkel ist schon schwarz, Entschuldigung.
Den Fall gibt es entweder als Nachfolger von Fall 2 oder
schon so alleine. Dann mache ich folgendes. Ich mache
eine Rechtsrotation hier um dieses GIF. Dann ist der wieder
nach oben gewandert und jetzt sieht er schon ziemlich gut aus.
Jetzt habe ich hier einen schwarzen Knoten, Vater ist schwarz,
ich habe zwei rote Kinder, alles super.
Und ausgeglichen ist es auch. Die beiden Fälle hintereinander würden
dann so aussehen. Einmal in der Linksrotation,
dann eine Rechtsrotation
Einmal am Beispiel der Knoten 4 wird neu eingefügt.
Also ich hatte vorher den Baum da,
also ohne die 4 und die 4 wird neu eingefügt.
Jetzt sieht man irgendwie Rot-Schwarzeigenschaft verletzt, weil Blatt ist,
weil hier zwei rote Knoten hintereinander habe, ist doof.
Jetzt gucke ich mir den Onkel von Z an,
das ist 17 und der ist rot.
Fall 1, glaube ich. Genau, Verein steht da
vorne hier. Umfärben und Z auf seinen Großvater setzen.
Zack. Das ist okay, jetzt ist hier unten irgendwie
die Rot-Schwarzeigenschaft nicht mehr verletzt, da sieht alles gut aus.
Problem ist, sein Vater hier ist wieder rot,
soll nicht
so sein
Zweiter Fall. Onkel des Schwarz, der hier.
Z ist rechtes Kind seines Vaters. Jetzt gibt es diese Linksrotation
um 1, 2, 7. Wie sieht das so aus?
Also ich mache hier eine Linksrotation um 1, 2,
7. Das heißt, die 1, 200 nach unten,
dann kommt ja die 2, 7 kommt nach oben.
Die 8 zieht sie mit und der Teilbaum 5,
4, der hängt irgendwie lose in der Gegend rum,
weil das linke Kind von sieben mehr als die 2 ist.
Und dann ist der einzige sinnvolle Platz für 5,
4 das rechte Kind von 2 zu werden.
Also so. Also an der Stelle hier 1,
2, 7, wie an so einer Schnur ziehen.
2 nach oben 5, 4 da einfügen. Und jetzt
war ja die Idee, jetzt trifft gleich der dritte Fall zu.
Jetzt habe ich hier wieder einen roten Knoten als Papi
Onkel ist weiterhin schwarz. Z ist linkes Kind,
also mache ich eine Rechtsrotation hier um diese 7, 11, 14.
Und färbt nochmal um. Also um die 11 wandert hier nach unten,
die 7 wandert nach oben, die 2 zieht sie mit,
die 8 ist plötzlich frei und wird dann linkes Kind der
14. Jetzt sind wir fertig.
Genau, jetzt sieht es wieder schön aus.
Es ist die Welt wieder in Ordnung.
Ja, das ist das.
Da nochmal zusammengefasst
Da sieht man schon,
dass da ziemlich viel Arbeit drinsteckt bei der Implementierung,
vor allem auch ziemlich viel, was man irgendwie falsch machen kann,
wegen diesen Großvater- und Onkelgeschichten und so.
Aber diese ganzen Vergleiche mit Großvater, Onkel und so,
das geht alles in konstanter Zeit.
Und ich wandere ja maximal bis zur Wurzel nach oben. Das heißt,
im Worst Case habe ich irgendwie eine Laufzeit in der Höhe,
die war bisher vor dem Einfügen ja in O-Log-in,
also bleibe ich beim Einfügen in O-Log-in.
Genau, oh Gott, jetzt könnte man
eine Schleifenvariante aufstellen
Und dann müsste man, könnte man, den Pseudocode ausführlich schreiben.
Also bis jetzt habe ich ja nur die Idee so ein bisschen,
mal kurz illustriert, was euch auch tatsächlich nur zeigen soll,
wie wild das werden kann.
Und dann müsste man jetzt irgendwie diese Schleifen in Variante zeigen und zeigen,
dass das korrekt ist. Und dann müsste man noch,
was wir noch gar nicht gemacht haben, sich die Löschoperationen angucken,
wo man das auch wieder alles machen muss.
Genau, wer das machen möchte,
findet das im Kormen zum Nachlesen.
Letzte,
letzte Zusammenfassung,
ja. Fragen
Da gibt es zu einem Beginn, der mit Lehren kommt,
zu einem roten Schmerzwert. Ob es zu einem gegebenen,
binären Baum nur eine rote Schwarzfärbung gibt, nö.
Kannst du dir am einfachsten, glaube ich, überlegen,
am vollständigen, wenn du hast, so eins, zwei,
naja, 2-1-3 und machst die alle schwarz.
Dann ist gut, oder du machst die Wurzel schwarz und
die beiden Kinder rot, ist auch gut.
Aber der Algorithmus sollte immer was Eindeutiges liefern,
also wenn du die nach und nach einfügst,
liefert er immer das Gleiche natürlich.
Noch mehr Fragen
Gut, dann machen wir Pause bis,
ich muss mal ganz schnell in die Chemie rüber.
Ich bin mir nicht ganz sicher, wie schnell ich wieder da bin.
So bis 11, 5 nach 11 machen wir Pause,
ja? Und dann machen wir noch ein bisschen P,
NP-Aufgabenzettel und so.
Okay. Gut. Pause, Schleich