Gut, okay, also,
wir haben uns in der letzten Vorlesung mit den selbstorganisierenden Bäumen beschäftigt,
haben dann die Splay-Trees als ein Beispiel gesehen.
Ein Beispiel, wo wir eine Datenstruktur haben mit relativ einfachen Algorithmen,
aber wir haben eine sehr aufwendige, komplizierte Analyse benötigt,
um dann die Laufzeitschranken zu zeigen.
Wir werden jetzt wieder ein bisschen zurückgehen zu den Dingen,
wo interessante Algorithmen vorkommen und auch die müssen wir natürlich analysieren
und werden uns jetzt von den Bäumen wegbewegen zu den Heeps
Und ja, ich werde jetzt in der nächsten Viertelstunde nochmal
so ganz grob schnell zusammenfassen, was die Heeps sind.
Das werden sie aber sicherlich alle noch ganz gut im Kopf haben.
Das ist ja eine relativ leicht zu verstehende Datenschutzstruktur.
Warum guckt man sich die Heeps an? Die Heeps lernt man
normalerweise im Informatikstudium kennen, ends mal um Sortieren geht.
Dann kommt der Heepsort und der Heepsort arbeitet mit einem Heep.
Das ist natürlich die eine Anwendung,
aber die viel wichtigere Anwendung von Heeps ist die Nutzung
dieser Struktur
als Prioritätswarteschlange
Und Prioritätswarteschlangen sind ganz essentielle Datenstrukturen in vielen Algorithmen,
insbesondere bei der kürzesten Wegesuche und bei der Spannbaumberechnung,
wo wir mal Heeps benötigen.
Und wenn man also effiziente Datenstrukturen für die Heeps entwickelt,
dann besteht natürlich die Chancen, da bestehen Chancen,
dass man genau diese sehr wichtigen Algorithmen für die Informatik in der Laufzeit auch beschleunigt.
Ja, weil wenn man zum Beispiel die Tiefen,
die den Dixtra-Algorithmus anschaut, dann wird der dominiert durch die
Anzahl der Operationen auf dem Heep.
Okay, das also als Motivation,
warum es sich lohnt mit den Heeps
zu arbeiten
Zunächst noch mal, was so ein Hieb ist.
Wir haben es mit einem Baum zu tun. Der Warm hat halt,
es ist ein binärer Baum mit Knoten und jeweils zwei Kindern und einen Vorgänger.
Die bezeichne ich jetzt im folgenden Mal mit Left Ride und Parent.
Das ist hier absichtlich nicht in der Notation einer Operation oder eines Zeigers,
sondern es sieht so aus wie so eine Funktion.
Und ich werde denen gleich zeigen, warum das so ist.
Unser kompletter Baum wird in einem Array abgebildet.
Deswegen wird ja auch diese Array-Notation A von E verwendet.
Und in der Baumstruktur haben wir die Hieb-Eigenschaft realisiert.
Das bedeutet an den Elementen hier stehen natürlich Objekte,
Werte mit einer Ordnungsfunktion. Und wenn wir einen sogenannten Max-Hieb haben,
dann ist der Hieb so aufgebaut,
dass für alle Knoten, außer der Wurzel gilt,
dass der Wert, der in einem Knoten drinsteht,
immer kleiner gleich den Wert des darüber liegenden Elternknotens ist.
Und für die Wurzel kann das natürlich nicht mehr gelten.
Andersrum formuliert kann man auch sagen,
der Wert eines Knotens ist immer größer als der seiner Kinder.
So, das ist die Maxi-Eigenschaft. Wir können natürlich auch
einen Min-Hieb bauen. Das ist eigentlich alles dann synonym.
Dann haben wir halt das kleinste Element hier oben
Und die Werte der Kinder sind entsprechend größer. Okay,
das ist die Hieb-Eigenschaft in einem Baum, diese Hieb-Eigenschaft ermöglicht uns
zu jedem Zeitpunkt. Ich bleibe jetzt mal beim Max-Hieb,
denn das Maximum effizient zu finden, ja,
das muss in der Wurzel stehen,
weil der Wert ist ja größer als der seiner Kinder und
der Kinder sind größer als die der Kinder und so weiter.
Deswegen steht hier oben natürlich das Maximum.
Ja, das können wir leicht finden. Ansonsten haben wir
allerdings keine Sortierung in diesem Baum. Das bedeutet,
dass das Identifizieren eines beliebigen Objektes das als in einem Suchbaum,
in einem Hieb, nicht so ohne weiteres möglich ist.
Ja, wenn Sie sagen wollen,
wo steht denn das Element mit Schlüssel 4 oder so,
dann können Sie nicht ohne weiteres hier den Pfad finden,
um das Objekt zu finden.
Es unterstützt eben genau diese Eigenschaft, die man in einer Prioritätswarteschlange braucht,
dass man eben das eine Element, was eine extremale Eigenschaft hat,
also das Maximum oder das Minimum ist, schnell identifizieren kann.
So, die Baumstruktur wird, wie gesagt, nicht explizit
durch Pointer oder Zeiger realisiert, sondern durch mathematische Operationen.
Wir haben die Objekte in einem Array liegen und wenn wir
die folgenden Umrechenoperationen auf den Indizes machen,
dann können wir uns leicht den Baum bewegen.
Und zwar funktioniert das wie folgt,
wenn wir eine ganzzahlige Division durch zwei machen auf dem Index,
dann kommen wir von einem Element zu seinem Vorgänger, zum Elternknoten.
Wenn wir den Index mit zweien multifizieren, kommen wir zum
linken Kind und wenn wir mit zwei multiplizieren und eins addieren,
kommen wir zum rechten Kind.
Und hier sehen Sie einfach mal die Indizes,
also die Wurzel steht hier also an Position 1 in dem Array und die nächsten Knoten an zwei, drei.
Das ist ja also zeilenweise durchnummeriert und dann können Sie gucken,
wenn Sie zum Beispiel vom Knoten 3 sind,
wenn Sie das ganzteilig durch zwei divisieren, kommen Sie zum Elternknoten,
das wäre dann eins, wenn sie es mit zwei multiplizieren,
zum linken Kind, wenn sie es mit zwei multiplizieren,
eins hat die dann zum rechten Kind.
So, das heißt, wir brauchen gar keine Zeigerstrukturen,
wir brauchen nur das Array, da stehen die Elemente drin
und über den Index navigieren wir in dem Array.
Okay, so, so ein Hieb hat eine Höhe,
das ist einfach nur wie in Bäumen üblich,
die Anzahl der Ebenen, die wir brauchen,
um zu einem von der Wurzel zu einem Blatt zu kommen
und dann brauchen wir natürlich die Anzahl der Elemente,
die wir in dem Hieb speichern.
Es gibt eine Eigenschaft in Hiebs,
die man schnell übersieht und zwar ist das die Eigenschaft,
dass so ein Hieb immer Ein linksvollständiger, binärer Baum ist.
Das bedeutet, der hat nicht wie bei einem Suchbaum vielleicht hier
mal so Äste wohl einen Ast endet oder nur ein Kind da ist,
sondern das ist ein vollständiger, binärer Baum, bis auf die letzte Ebene,
weil wir haben natürlich nicht immer nur zwei Hochi, minus eins viele Knoten.
Und in der letzten Ebene gilt die Regel,
dass die Knoten von links nach rechts aufgefüllt werden müssen.
Das heißt, der Hieb als Baum sieht immer so aus,
ist vollständig und dann in der letzten Ebene beginnt auf der
linken Seite noch Knoten und dann hören die irgendwann mal auf,
weil wir nicht immer eine volle Reihe von Knoten haben.
Das ist deswegen so wichtig, einem Hieb,
weil wir das ja in einem Array abbilden und sie sehen das hier schon an den Indizes,
wenn jetzt hier noch irgendwie ein Blatt wäre,
dann bräuchten wir ein viel größeres Array.
Deswegen ist das immer links vollständig. Und der andere Grund
ist, wir brauchen das auch in den Algorithmen.
Also wenn wir auf diese Hiebs zugreifen,
dann setzen wir diese Linksvollständigkeit eigentlich immer wieder voraus.
Okay, gut. Das ist also der Hieb,
die wesentliche Operation darauf, ist das sogenannte Hiebify.
Ein Hiebify ist in der Lage, eine Hiebify-Operation,
den Hieb
Der, bei dem an einer Stelle die Hieb-Eigenschaft verletzt ist,
wieder zu reparieren. Das sieht dann wie folgt aus.
Hier ist nochmal die Hieb-Eigenschaft, dass also jedes Element AI
immer größer gleich dem linken und dem rechten Kind sein muss.
Und das hipify, was man am besten mit Versickern
übersetzen kann, macht Folgendes. Wir gehen davon aus,
dass wir eine Hieb-Struktur haben, die ist komplett korrekt.
Ja, an jedem Knoten wird die Hieb-Eigenschaft erfüllt
Außer an einem Knoten. Und das ist genau der,
den wir mit Position I markieren werden.
Das ist also das, was wir an die Funktion übergeben.
Der Baum ist insbesondere auch schon linksvollständig. Ja, der hier rauskam. So,
und wenn diese Hieb-Eigenschaft nur an einer Stelle verletzt ist,
in meinem Fall ist es hier verletzt an der Wurzel.
Wir haben ja wieder noch ein Maxip und hier sehen wir den Wert 2.
Der Wert ist kleiner, als der Wert des linken und rechten Kindes.
Da ist also die Hieb-Eigenschaft verletzt. Hier müssen wir das
jetzt reparieren. Und die Funktion heißt Hipify oder Versickern,
weil sie dieses kleinere Element sukzessive durch ein Element der Kinder
tauscht und dadurch das kleinere Element quasi nach unten in dem Baum versickern lässt.
Die Operation funktioniert so,
dass wir uns die beiden Kinder angucken und wir wissen ja,
die Hieb-Eigenschaft ist nur dann wieder hergestellt,
wenn hier das ein Element ist, was größer oder gleich beider Kinder ist.
Deswegen müssen wir das Maximum der beiden Kinder nehmen.
In diesem Fall das Kind hier auf der rechten Seite mit dem Wert acht.
Ja, und die beiden können wir jetzt vertauschen,
dann ist die Hieb-Eigenschaft an dem Wurzelknoten hier erfüllt.
Allerdings ist jetzt die Hieb-Eigenschaft hier an dem rechten Kind verletzt.
Das macht aber nichts, wir rufen dann die Funktion rekursiv auf
und verschieben damit quasi dieses Element mit dem fehlerhaften Hiebwert schrittweise nach unten.
Ja, und spätestens, wenn dieser Knoten dann an ein Blatt angekommen ist,
so wie hier rechts, dann haben wir die Situation erreicht,
dass die Hieb-Eigenschaft nicht mehr verletzt sein kann,
weil hier ja keine Kinder mehr vorliegen.
Okay, gut. Auf diese Art und Weise können wir
eine Hieb-Eigenschaft reparieren. Das ist der Pseudo-Code der Hibi-Five-Funktion.
Das ist, glaube ich, relativ offensichtlich.
Wir schauen, ob es ein linkes Kind gibt.
Das ist die erste Eigenschaft, die wir überprüfen müssen.
Das ist dann der Fall, wenn die Position,
die wir berechnen, kleiner gleich der Größe des Arrays ist.
Und dann schauen wir noch, ob der Wert dann größer ist als der aktuelle Wert.
Wenn ja, merken wir uns in der variablen C,
diesen Index des linken Kindes. Anderenfalls merken wir uns in C,
den Index des Elementes, was wir gerade noch haben.
Das machen wir einfach nur, weil wir jetzt das noch
mit dem rechten Kind vergleichen müssen. Da machen wir jetzt
das Gleiche. Wir gucken uns das rechte Kind an, schauen,
ob der Index, die kleiner gleich der Größe des Arrays ist,
also ob das rechte Kind überhaupt noch gibt.
Und wenn ja, dann gucken wir,
ob der Array-Wert an dem rechten Kind größer ist,
als der Array-Wert an der Position C,
was ja jetzt schon das Maximum ist von der Position I und dem linken Kind.
Ja, das haben wir ja hier oben schon bestimmt.
Und wenn das noch größer ist,
dann setzen wir C auf den Index des rechten Kindes.
Wenn I dann ungleich dem Wert I ist, dann bedeutet das,
dass die Hieb-Eigenschaft an der Position I noch verletzt ist.
Das heißt, das linke oder das rechte Kind hat es also einen größeren Wert,
dann vertauschen wir AI mit AC und rufen die Funktion rekusiv
auf dem Kind See auf.
Ja, das ist also einfach
das Versickern
Die Laufzeit von Maxipify, naja, diese Funktion hat in sich erstmal konstante Laufzeit,
hat dann allerdings hier einen rekursiven Aufruf. Das heißt,
wenn wir davon ausgehen, dass wir N-Elemente haben,
dann gibt es die Rekurrenzgleichung, Thibify von N,
gleich die Hipify von 2 N Drittel plus Tetter von 1.
Das Tetter von 1 steht sozusagen für diese IF-Anweisungen,
die insgesamt konstante Zeit benötigen.
Das einzig Spannende daran ist, die Frage,
woher kommen diese 2 N Drittel
Ja, das müssen wir noch mal gerade schauen.
Und wenn wir aber diese Rekurrenzgleichung haben,
können wir das Mastertheorien verwenden und sehen sofort,
dass da lugorithmische Zeit rauskommt,
weil wir hier einen konstanten Aufwand pro Aufruf haben und hier die Eingabe immer um einen prozentualen Wert verringern.
Ich habe gerade konstant gesagt, nicht lugorithmische Zeit. Okay,
gut, so, wie kommen diese zwei Drittel hier zustande?
Das kann man sich ganz gut bildlich erklären oder sehen,
wenn wir mal einen Baum hier aufzeichnen.
Dann haben wir hier oben die Wurzel und dann sind hier
die ganzen Knoten
Der Baum ist links vollständig, das heißt,
er sieht dann ungefähr so aus. Und hier dieses Rote,
das sind Knoten, die fehlen, ja, die fehlen.
Und irgendwann haben wir so einen Schnitt und dann haben wir die letzte,
die letzte Zeit, die letzte Ebene des Baums eben mit Knoten gefüllt, ja.
Also wir füllen das hier von links nach rechts und irgendwo
hier hört das mal auf. So, was ist jetzt
der ungünstigste Fall? Wir wissen schon, aufgrund dieser Struktur,
dass der linke Teilbaum immer größer sein muss als der rechte Teilbaum,
weil der Baum ist ja links vollständig, ja.
So, was ist der ungünstigste Fall?
Der ungünstigste Fall wäre, dass diese unterste Ebene genau bis
zur Hälfte gefüllt ist
Dann hätten wir nämlich die Situation, dass der linke Teilbaum,
der hat hier überall noch Kinder,
der ist wesentlich größer als der rechte Teilbaum,
wo hier dann keine Kinder mehr sind.
Okay, wie viel ist der größer? Das kann man
relativ leicht berechnen, weil wir einen vollständigen Baum eigentlich haben.
Wir wissen, dass unter dieser Wurzel der linke und der rechte Teilbaum
zunächst mal bei einem vollständigen Baum jeweils N-minus eins halbe viele Knoten hat.
Also wenn jetzt mal mit Großenden bezeichne ich jetzt mal die
Knoten des vollständigen Baums. Ja, Kleinen ist die Inputgröße,
also die Größe unseres Heeps Und Großen wäre der nächstgrößere vollständige Baum.
Und dann wissen wir, endminus eins,
halbe sind auf der linken Seite und endminus eins,
halbe sind auf der rechten Seite, weil es einfach ein vollständiger Baum ist.
Und wenn man die addieren würde, würde jetzt noch ein Knoten fehlen.
Das ist auch korrekt so, weil wir haben ja noch die Wurzelknoten,
den Wurzelknoten. Das heißt, linke Seite plus rechte Seite
plus eins sind dann die Endknoten des vollständigen Vinärenbaums.
Wir können den Baum auch anders trennen,
wir können mal die Blattebene trennen von den Rest des Baums.
Und dann sehen wir, dass das auch einigermaßen symmetrisch ist.
Wir haben N minus eins, halbe Knoten,
die wir inklusive der Wurzel als innere Knoten bezeichnen können.
Und N plus eins, halbe Knoten,
die alle Blätter sind
Das ist das, was an diesen Skizzen von Bäumen übrigens immer total irritiert.
Die haben ja von Ebene zu Ebene ein exponentielles Wachstum und
wir malen meistens solche Dreiecke, wie ich hier auch.
Und dann sieht es immer so aus,
als wenn es die Anzahl der Blätter im Verhältnis zur Anzahl
der Knoten gar nicht so viele sind.
Aber wir sehen an dieser Formel,
es gibt in einem vollständigen Baum mehr Blätter als innere Knoten.
Okay, gut. So, und von denen fehlen jetzt
quasi die Hälfte. Das sind die. Und um diese
Anzahl ist die rechte Seite kleiner als die linke Seite.
Und dadurch haben wir so eine Asymmetrie und die führt eben dazu,
dass wir in dem Heapify-Aufruf die Eingabe nicht halbieren,
sondern eben nur auf zwei Drittel reduzieren.
Weil wir machen den rekursiven Aufruf mit dem linken Teilbank und der
ist eben dann nicht halb so groß wie vorher,
sondern der ist dann nur zwei Drittel so groß.
Dass das zwei Drittel ist, kann man ganz leicht jetzt
nachrechnen, wenn sie diese Werte hier betrachten.
Dann können wir uns überlegen, wie groß ist denn im Worst Case die linke Seite?
Dann sehen wir, dass es N minus eins halbe,
so wie in dem Diagramm hier gezeigt.
Dann können sie sich überlegen, wie groß ist im Worst Case
die rechte Seite? Das wäre natürlich so klein wie möglich.
Das wäre, wenn die Roten hier alle fehlen,
das wäre dann Enmy Minus 1 halbe, das wäre der vollständige Baum.
Minus, diese Blatt-Ebene, das ist N plus 1,
Viertel, das ist die Hälfte aller Blätter, ja, macht zusammen N-3 Viertel. Die Gesamtzahl der Knoten in unserem Hieb,
also alles Weise hier, der Bereich hier,
wäre also eins plus die Knoten auf der linken Seite,
plus die Knoten auf der rechten Seite,
das können wir also addieren, wäre also eins plus N-1 halbe plus,
und dann haben wir N-1 halbe, minus N plus 1 Viertel,
das war der Therm hier, für die rechte Seite,
und wenn wir das zusammen addieren,
kommt da gerade 3N minus 1 Viertel raus,
Es ist so, wie wir es erwarten würden.
Es ist drei Viertel der Anzahl der Knoten des vollständigen minären Baums,
weil das ist hier ein Viertel und das fehlt uns in etwa.
Okay, das können wir umformen und dann sehen wir,
wie groß das Großen, also der vollständige binäre Baum,
in diesem ungünstigsten Fall relativ zur Größe des Heebs ist.
Das wäre dann endgleich 4N plus 1 Drittel und damit können wir die Größe der linken Seite ausrechnen,
indem wir das hier oben wieder für N einsetzen und dann sehen wir das N links,
war N minus 1 halbe und das N haben wir gerade ausgerechnet 4N plus 1 Drittel,
das Fass wir wieder zusammen,
dann kommt da zwei Drittel N,
minus ein Drittel raus und das Drittel können wir hier vernachlässigen und dann ist das Kleiner gleich zwei Drittel N.
Und das ist genau das, was wir dann
in unserer rekursive Gleichung hier einsetzen können. Das heißt,
in einem rekursiven Aufruf wird aus der Hiebgröße N eine Hiebgröße,
die im ungünstigsten Fall noch zwei Drittel N ist,
weil wir dann in diesen größtmöglichen linken Teilbaum verzweigen.
Ist natürlich, wie häufig im Okalkühn und eine Abschätzung,
weil in der nächsten Iteration würde dann natürlich dieser ungünstigste Fall
natürlich nicht auftreten
Okay, aber damit kommen wir zu unserer Log-An-Laufzeitschranke für die Maxi-Pify-Operation.
Hier ist noch ein Beispiel, wenn Sie es nochmal nachrechnen wollen.
Okay, und auf der Basis können wir jetzt
eine ganze Reihe von Operationen auf dem Hieb realisieren.
Wir können insbesondere die Extract-Funktion realisieren. Also das heißt,
die, die das Maximum aus dem Hieb entfernt,
das machen wir ganz einfach so.
Wir wissen schon, das Maximum ist die Wurzel des Baums.
Wir nehmen also die Wurzel raus und jetzt haben wir natürlich ein Loch,
da muss irgendwie ein Knoten rein,
ein Objekt und da nehmen wir einfach das letzte Objekt aus dem Ray.
Hier ist es gerade In dem Beispiel nur ein Knoten in
der letzten Zeile hier des Baums, also in der letzten Ebene des Baums.
Es könnten natürlich jetzt auch mehrere sein. Wir nehmen dann einfach
den letzten hier raus. Damit sichern wir schon mal,
dass der Baum wieder links vollständig ist. Und den Objektwert,
den fügen wir jetzt hier oben ein,
wovor er die Wurzel stand, dann kriegen wir also diesen Baum hier.
Die Größe ist jetzt um eins verringert worden,
weil wir haben ja ein Element herausgenommen.
Und unsere Liebeigenschaft ist an der Wurzel potenziell verletzt.
Und das können wir reparieren mit der Maxipify-Operation.
Er würde also die Eins jetzt versickern,
da würde das Maximum eine Ebene nach oben
Hierhin rutschen, die Eins würde dann an diese Position gehen und
dann würde man hier die Einzelnen, die Position der Fünf schieben,
die Fünf geht hoch und dann sind wir in einem Blatt und dann ist wieder alles gut.
Dieser Schritt, dass man das letzte Element hier oben hinschiebt und dann versickert,
das ist etwas, was man, was manchmal irgendwie so was,
oder wozu man tendiert es einfach zu vergessen und sagen,
ja, ist ja ganz einfach,
wenn ich das Maximum mir rausnehme, nehme ich den nächstgrößeren,
schiebe den hier oben hin und dann habe ich ja wieder den nächstgrößeren und schiebe den dahin.
Also wir rutschen, schieben das alles so nach oben.
Ist ja genauso wie das Hipify. Das ist natürlich fast so,
wie das Hipify,
es ist aber dann doch nicht das Gleiche, nur dadurch,
dass sie das letzte Element erst hier oben hin kopieren,
sie sicherstellen, dass der Baum nachher wieder links vollständig ist.
Wenn sie einfach sagen würden, ich schiebe immer das Maximum
eine Ebene hoch, dann hätten sie irgendwie nachher hier eine Lücke.
Und dann wäre der nicht links vollständig. Deswegen macht man
das, dass man mit dem letzten Element anfängt,
hier oben hin kopieren und dann versickern lässt.
So, jetzt können wir Prioritätswarteschlangen bauen mit dem Hipify und
die wichtigsten Operationen hier mal zusammengefasst.
Also Heap Insert würde ein Element in die Warteschlange einfügen
Mit im Extract. Max, entnehmen wir das Element mit maximaler Priorität.
Wir haben also nach wie vor ein Maxib. Und dann,
was nicht untypisch ist, für eine Prioritätswarteschlange, ist,
dass sie während die Warteschlange existiert und Elemente da drin sind,
sind die Prioritäten anpassen müssen, dass Elemente in der Priorität rauf oder runter gehen.
Dafür brauchen sie eine Increase-Key oder eine D-Crease-Key-Operation,
die also entsprechend dann den Knoten neu einsortiert,
wenn sie ihm eine andere Priorität geben.
Das können sie machen. Die Funktionen sind asymmetrisch,
weil der Hieb natürlich zum Maximum hin aufgebaut ist und das was anderes ist,
wenn man einen Wert entsprechend erhöht, als wenn man ihn erniedrigt.
So, das Increase Key wird also den Wert erhöhen und
damit würde so ein Knoten potenziell sich weiter Richtung Wurzel bewegen und
ein D-Crease Key würde den Wert verringern und das Element würde sich potenziell Richtung Blätter bewegen.
Die kann man aber beide relativ problemlos realisieren. Beachten Sie,
dass ich hier ganz bewusst etwas die ich mal anders gemacht habe als im Kormen.
Das steht hier unten auch nochmal. Ich habe es auch
nochmal nachgeguckt. Das ist ja auch im aktuellen Kormen noch so.
Und zwar wird hier ein X verwendet und das X,
das liegt so ein bisschen nah,
als wenn man an dieser Stelle das Objekt angeben könnte.
Ja, also wenn man hier sagen könnte, ja,
mach mal ein D-Crease auf dem Element mit dem Wert 7.
Das geht nicht. Ich meine, es geht natürlich,
aber dann haben sie ein Laufzeitproblem, weil diese Inquise-Key-Operation,
die müsste ja dann erstmal das Objekt im Baum identifizieren,
was den Schlüssel 7 speichert
Und diese klassische Suchbaumanfrage unterstützt einen Hieb nicht optimal.
Da müssten sie quasi sich das Array durchlaufen, sich das angucken.
Wenn sie also hier nur den Objektschlüssel angeben würden,
dann müssten sie, wenn sie nicht noch eine zusätzliche Datenstruktur verwenden,
zunächst mal im ungünstigsten Fall Ovon Endside investieren, um das Objekt zu finden.
Deswegen hier ganz bewusst ein I für den Index.
Also das heißt, wir geben hier den Index an und
dann können wir auch in lugerithmischer Zeit das reparieren.
Also wenn sie an dieser Stelle sagen,
verringere den Schlüsselwert, der an der Indexposition 6 steht,
dann ist es
kein Problem
Wenn sie aber sagen, verringern, verringere den Schlüsselwert von dem Schlüssel,
der vorher fünf war, dann haben sie ein Problem,
weil sie die fünf erst suchen müssen.
Okay, deswegen steht hier das I. Gut,
all diese Operationen können wir relativ leicht durch einen Hieb
realisieren und alle Operationen resultieren dann auch in Logo-Whitmischer Laufzeit.
Solange sie nicht ein Objekt suchen müssen. Ja,
hier ist zum Beispiel, wie das Increase-Key aussehen würde,
das ist so ein bisschen wie so ein umgekehrtes Hiebipify.
Sie hätten jetzt zum Beispiel ein Increase-Key auf den Knoten 5 angewendet.
Da stand vorher eine 4 und da würden sie das jetzt auf eine neue neuen ändern.
Also die Priorität würde jetzt steigen. Was machen Sie?
Sie sehen dann an dieser Stelle, ist potenziell die Hieb-Eigenschaft verletzt nach oben hin.
Nach unten kann ich, weil die 9 ist ja größer geworden.
Das heißt, an den Parent-Knoten hier könnte die Hieb-Eigenschaft verletzt sein. Die lässt sich aber leicht reparieren,
indem man einfach die beiden Elemente in diesem Fall vertauscht,
weil das Element schon größer ist als, also weil die bisherige,
der bisherige Parent-Knoten schon größer ist als der Wert in dem anderen Kind,
weil da war ja vorher die Hieb-Eigenschaft nicht erfüllt,
können wir das einfach vertauschen und dann haben wir die Hieb-Eigenschaft korrigiert und wissen dann weitere Richtung Wurzel fortbewegen.
Es geht also einfach nur in umgekehrter Richtung und schiebt den
größer gewordenen Wert einfach nach oben. Okay, das steht hier,
wie das geht, wenn also Karl kleiner ist als AVI,
dann ist es die falsche Funktion, dann hätten wir eigentlich ein D-Crease gemacht,
dann haben wir den Wert ja verringert, müssen wir eine andere Funktion verwenden.
Gibt es eine Federmeldung,
anderenfalls setzen wir den Schlüssel an der Position I auf K,
solange wir nicht an der Wurzel angekommen ist und der Wert,
der im Parent steht, also im Elternknoten steht,
kleiner ist als der von AI,
vertauschen wir einfach diese beiden Werte und gehen dann zum Elternknoten.
Hier ist es jetzt mal iterativ formuliert,
ist auch rekursiv formuliert
So, und die Neuen würde jetzt in unserem Beispiel hier
langsam nach oben wandern und in unserem Beispiel wäre es auch das neue Maximum,
würde also dann bis zur Wurzel hochwandeln.
Ja, wie fügt man ein in so einen Hieb, was man macht ist?
Auch da gibt es verschiedene Möglichkeiten. Hier kommt wieder diese Linksvollständigkeit zum Tragen. Ja,
der Hieb wird jetzt ein Element größer und wir können ihn nicht irgendwo hinschreiben,
den Wert, sondern wir müssen in eine ganz spezielle Position schreiben,
nämlich als nächstes Blatt, ja, als an die nächste freie Position,
wenn der Hieb eben so aussieht, ja,
dann Ist das genau hier die Position,
wo der Insert stattfinden muss?
Also da muss das Element rein, damit er wieder links
vollständig ist. Was wir machen, ist einfach,
wir fügen dort ein Element ein mit einem Schlüssel, minus unendlich.
Also der kleinstmögliche Wert in einem Max-Hieb haben wir dann einen korrekten Hieb,
weil der Knoten, der hier drüber sitzt,
hat auf alle Fälle einen Wert, der größer ist als minus unendlich.
Die Hieb-Eigenschaft ist also nicht verletzt. Und danach wenden wir
einfach die Ingress Key-Operation an mit dem richtigen Schlüsselwert hier.
Und dadurch würde der Knoten dann langsam hier nach oben wandern
an die Position,
wo der denn jetzt hingehört
Okay, also mit unserem einfachen,
binären Hieb können wir eine Prioritätswarteschlange mit Laufzeit,
Login für alle Operationen, also für Einfügen, für Löschen,
für Increase, für Decrease, für Xtract Max realisieren.
Hier nochmal der Hinweis mit dem Increase Key,
das ist eine Situation, die in praktischen Anwendungen nicht selten ist,
dass wir das Objekt kennen und möchten,
dass das Objekt ein anderer, eine andere Priorität kennt,
bekommt,
dann brauchen wir eine zusätzliche Datenstruktur,
um ein Objekt zu identieren
Um es zu finden. Dazu gibt es verschiedene Möglichkeiten,
das zu machen, wenn sie richtig viel Aufwand betreiben,
dann bauen sie noch einen Suchbaum dazu,
weil wenn sie weniger Aufwand betreiben,
dann geben sie diesem Objekt einen Verweis auf seine Indexposition im Hieb und aktualisieren die mit.
Das ist der pragmatischere Weg, das zu tun. So,
okay, so, das war erstmal der Rückblick auf die Hiebs,
soweit sie sie hoffentlich schon kennengelernt haben.
Und auf der Basis wollen wir nun,
wollen wir nun gucken,
ob wir noch etwas verbessern können
Es ist schon, wie bei dem Baum, auch klar,
dass es nicht so einfach ist, da was zu verbessern,
wie bei dem, bei unserem Wörterbuch-Problem,
weil wir kennen die untere Schranke für Sortieren durch Vergleichen in Log-in.
Das heißt, wir wissen schon, wenn wir N,
Insert und N X-Track-Min-Operation hier machen,
dann werden die in der Summe auch amortisiert,
nicht in weniger Zeit als N-Log-End-Zeit realisierbar sein, ja,
weil sonst hätten wir ein Sortierverfahren,
was vergleichend weniger als N-Log-End-Zeit realisieren
Laufen würde. Das geht also nicht. Das heißt,
es ist klar, wir sind eigentlich schon relativ dicht dran an einer optimalen Struktur.
Allerdings interessieren uns ja jetzt primär auch gar nicht so sehr,
diese Inside- und Extract-Min-Operation, sondern was uns besonders interessiert,
ist, dass Decrease hier, ja, das interessiert uns im Folgenden.
Wir werden jetzt zu einem, also ich werde Ihnen zwei
neue Hiebstrukturen vorstellen. Das erste, was wir heute machen,
ist der Binomeale Hieb und dann in der nächsten Vorlesung der Fibonacci Yi.
Eigentlich ist der Fibonacci Hieb die wirklich interessante Datenstruktur weil die
schafft es nämlich, die Decrease-Laufzeit zu reduzieren.
Das schafft dieser Venomiale Hieb noch nicht. Allerdings ist es, ja,
es ist didaktisch einfach besser, sich den mal anzuschauen.
Weil wenn man den verstanden hat, ist es für mein
Empfinden viel leichter, den Fibunachi-Hieb zu verstehen.
Leider, das bezieht sich jetzt auf diesen Satz hier oben.
Leider ist der Venomiale Hieb als Kapitel aus dem aktuellen Kormen rausgefallen.
Weil man gesagt hat, naja, der wird so selten benutzt,
braucht man eigentlich nicht mehr.
Man hat ja den Fibunachi-Hieb. Das ist zwar richtig,
das heißt, verwendet wird er dann eigentlich eher weniger
War für das Verständnis erleichtert ist den Weg, meiner Meinung nach sehr.
Also deswegen habe ich dieses Kapitel hier drinnen gelassen.
An einer oder anderen Stelle haben sie dann eine Abbildung,
die sich auf den alten Kormen bezieht.
Das können sie aber im Kopf sicherlich schnell umdenken.
Okay, so dieser Binomeale-Heap hat trotzdem einen Vorteil.
Das ist nicht nur reine Didaktik hier, sondern er erführt
eine neue Operation ein und das ist das Union.
Union würde bedeutet hier, dass wir zwei Heeps haben,
also zwei Heeps mit vielen Elementen,
die wir gerne zu einem Heep zusammenbauen wollen
Das ist also so eine Fusionsoperation, wenn sie so wollen.
Alle anderen Operationen haben wir hier eigentlich schon gesehen, bis auf das Union.
So, warum könnte so ein Union eine Rolle spielen?
Also wir haben es ja so in den klassischen Algorithmen bisher
noch nicht gesehen. Das Union könnte wichtig werden,
wenn sie zum Beispiel parallele Sachen machen,
wenn sie parallele Algorithmik betreiben,
wenn sie irgendwie Sachen in verschiedenen Sweats laufen haben und dann haben sie plötzlich die Situation,
oh, jetzt haben sie hier einen Hieb und da einen Hieb und dann müssen sie die irgendwie zusammenbauen.
Ja, dann haben sie so eine Union-Operation.
Und die Frage stellt sich dann natürlich,
wie kann man so eine Union-Operation
eigentlich realisieren
Ja, wir wissen schon, unser klassischer Hieb,
so wie wir den gerade gesehen haben,
der realisiert alle Operationen hier in logorithmischer Zeit und das Minimum
hier finden wir in konstanter Zeit,
weil das ist ja einfach nur das Wurzelelement.
So, wie geht jetzt das Union? Wir haben also
jetzt zwei Heebs und wollen gerne einen daraus machen.
Und da könnte man sich jetzt überlegen, okay,
wie soll das jetzt, könnte das gehen?
Das ist jetzt so der eine Heep hier und dann haben wir noch einen.
Ja, der ist vielleicht ein bisschen kleiner, keine Ahnung.
Ja, es ist H1 und H2 und wie können wir
daraus jetzt einen großen Heeb machen
Und ja, das Erste, was wir machen können,
wir gehen mal davon aus, im ungünstigsten Fall werden die ungefähr gleich groß.
Ja, wenn einer viel, viel kleiner wäre,
dann macht es die Sache eher einfacher. Wir können das ja,
das ist ein symmetrisches Problem, ist ja egal,
ob wir H2 in der H1 integrieren oder H1 in H2,
also im ungünstigsten Fall für die Operation sind die beide ungefähr n-halbe hier groß.
So, was könnten wir machen? Also die erste Geschichte wäre zu sagen,
naja, wir nehmen einfach alle Elemente des kleineren Heeps,
also in diesem Fall hätte er natürlich trotzdem
eine halbe und machen Inside-Operation
Also wir nehmen hier ein Element raus,
das können wir sogar in x-beliebiger Reihenfolge tun,
das ist eigentlich völlig egal,
weil wir nehmen jedes Element und machen einen Inside und fügen dann
ein Element nach dem anderen hier in diesen Hieb ein.
Wäre eine Möglichkeit, das zu tun,
dann hätten wir aber n-halbe viele Inside-Operationen auf diesem Hieb
und die würden ja alle Log-and-Side benötigen, mach N-Log-and-Laufzeit.
Das ist unangenehm viel, ja, für so eine Operation. Das ist ja,
das ist sogar noch mehr Zeit, als wir benötigen,
wenn wir die ganze Strukturinformation wegwerfen und einfach einen neuen Hieb bauen.
Weil das Bauen eines neuen Hiebs aus einer Datenmenge mit N-Objekten geht in den näherer Zeit.
Also wir können den Zettel von N einen komplett neuen Heat bauen.
Dann können wir sagen, okay, forget it,
alle Hieb-Eigenschaften hier, das ignorieren wir alles,
wir legen alles wieder in den Array und fangen wieder von vorne an.
Gut, wäre eine Möglichkeit, das zu tun. Wenn man das anschaut,
hat man noch so den Verdacht, man könnte auch sowas machen wie,
das sind ja jetzt schon zwei so Bäume und warum können wir nicht einfach irgendwie da eine neue Wurzel irgendwie konstruieren und dann hängen wir die so da drunter.
Könnte man ja machen. Und dann würde man hier zum Beispiel an diese Position,
ein neues Maximum reinschreiben, unendlich, ja,
ist ein Maxib, ne, würde da drin stehen,
dann wäre die hieb eigentlich schafft,
und da müsste man ja nur, dass,
das unendlich dann extrahieren, und dann wäre alles super,
ja, das geht aber nicht,
und zwar geht das nicht, aufgrund der Linksvollständigkeit,
das ist der Grund, warum wir das nicht machen können,
weil die beiden hiebs hier für sich genommen links vollständig sind,
und diese neue Struktur, die wir kriegen,
würden diese Eigenschaft eben nicht hätte, ja,
deswegen können wir das nicht machen.
Der andere Grund ist natürlich der,
dass wir die hiebs bisher immer in den Array realisiert haben,
müssen wir sowieso alle Elemente verschieben.
Also das geht definitiv nicht. Ja, können wir nicht machen.
Okay, das heißt, wir haben also das Problem,
dass diese Union-Operation so teuer ist, wie der Neubau des kompletten Hiebs.
Und das ist irgendwie ungünstig und die Hoffnung wäre,
dass man dieses Union vielleicht auch in luggerithmischer Zeit realisieren kann.
Okay, so, das kann der Binumiale Hieb das erreichen.
Hier sind die Laufzeiten für die verschiedenen Operationen
Das Make-Keep, wenn sie sich jetzt gerade wundern,
bezieht sich jetzt quasi auf ein initiales Erstellen eines Heebs mit einem Element.
Ja, sozusagen die Initialoperation. Deswegen ist das hier Tetter von 1.
Sie können es auch beziehen als die Laufzeit pro Element,
die in dem Heeb schon drin ist.
Dann kommen sie auch auf das Tetter von N, so wie das sein müsste.
Also wenn sie hier einen leeren Heeb der Größe Null oder Eins bauen,
dann brauchen sie eben konstante Zeit. Okay, so,
dann haben wir Insert, wissen wir, Login,
konstante Zeit, um das Minimum zu finden, Login-Site,
um das Extract-Min zu machen, Tetter von Endzeit für das Union,
indem wir den Heeb einfach komplett neu aufbauen,
Lock-End-Zeit für die Crease-Lock-End-Zeit für die Lead.
Der Velomiale Hieb, der schafft auf der Union-Operation tatsächlich die Lock-End-Laufzeit,
das ist super, ja, aber wir können das also von N nach Lock-End reduzieren.
Wir bezahlen dummerweise aber auch dafür, weil das Finden des Minimums dauert jetzt erst mal Lock-End-Zeit. Das ist allerdings nicht so dramatisch,
wenn man da mal ganz genau hinschaut,
dann kann man durch einfach einen zusätzlichen Zeiger,
der immer auf das Minimum zeigt,
diese Zeit natürlich wieder auf Ofon 1 drücken und den Zeiger
muss man dann bei einem Ex-Stract oder bei einem Decrease korrigieren und dann verlagert sich diese Lock-End-Zeit einfach nur nach Extract Min oder nach die Kris Key,
indem ich einfach einen Zeiger habe,
der zeigt mir immer auf das Minimum.
Kann man also ganz leicht reparieren. Das ist also
kein echter Nachteil hier, dass das Login da steht.
Okay, gut, das macht der Venomiale Hieb. Und Sie sehen,
der große Sprung, der passiert hier zum Fibunachi Hieb,
wo sie ein Objekt in konstanter Zeit einfügen,
in konstanter Zeit das Minimum finden können.
Sie haben immer noch Login-Zeit für das X-Tract-Min.
Das Union geht in konstanter Zeit,
dass die Kris Key geht in konstanter Zeit und das Dileed
kostet eben
auch Login-Zeit
Zu fragen. Wir haben ja da mal von Maxi zu gesprochen,
so du sagst, was immer noch Maxi zu sagen,
alles und das ist mir nicht so.
Das ist jetzt, hier steht Xtract Min,
das ist jetzt ein Minhieb, ja, da haben sie recht.
Ja, ja. Da habe ich gar nicht gewohnt,
warum das Minimum jetzt plötzlich konstant ist.
Ja, ja, ja, nee, das stimmt.
Also jetzt ist es ein Minhieb. Irgendwann wechselt das mal
. Also man braucht bei dem klassischen,
der Heef kommt ja aus dem,
der kommt ja aus dem Heep Sword und beim Heep Sword braucht man immer das Maximum,
weil sie immer das Maximum extrahieren und hinten an das Aray schreiben.
Ja, das ist irgendwie so traditionell so. Irgendwie,
wenn man über die Prioritätswarteschlange dann irgendwann redet,
dann braucht man tendenziell, hat man dann eher immer was,
wo man das Minimum vorne stehen hat.
Aber ja, also wir schwenken jetzt um,
ab jetzt haben wir
einen Minhieb
Haben sie richtig bemerkt. Okay, so,
das sieht hier richtig dramatisch aus,
als wenn das plötzlich super duper viel besser ist.
Alles ist hier in konstanter Zeit möglich. Wie geht das?
Wie ist das gemacht? Bei Datenstrukturen muss man immer ein bisschen aufpassen,
was hier passiert, ist, dass sich Laufzeit verlagert.
Sie müssen ja, bei einer Datenstruktur haben sie immer
die Situation, dass sie verschiedene Operationen aufrufen.
Und sie können einige Operationen beschleunigen, indem sie Rechenaufwand in
eine andere Operation verlagern
Gerade beim Hieb ist es eben so, dass immer dann,
wenn sie den Hieb verändern, zum Beispiel,
wenn sie etwas herausnehmen, oder wenn sie ein Element löschen,
dann müssen sie den Hieb umbauen, dann entsteht ein hoher Aufwand.
Und sie können natürlich die Situation so machen, dass sie,
wenn sie den Hieb verändern, einen hohen Aufwand betreiben,
dafür aber die anderen Operationen schneller machen.
Also was hier passiert, ist quasi,
dass die Laufzeit verschoben wird in einige Operationen.
Und das macht dann durchaus Sinn, das zu tun,
weil wenn wir nachher in der Anwendung wissen von der Datenstruktur,
dass diese Operation selten benutzt wird und eine andere
häufig,
dann ist es geschickt
Gleiche ist das, was ich gerade schon mal erzählt habe mit dem Minimum.
Also wir halten hier ein Minimum-Aufwand von Login. Wenn sie jetzt,
wenn sie jetzt dauernd das Minimum anfragen, dann ist das ungünstig.
Ja, wenn sie selten überhaupt die Struktur verändern,
dann wäre es viel geschickter zu sagen,
ich merke mir einfach, das Minimum mit einem zusätzlichen Zeiger,
dann geht diese Operation in den OVN 1.
Die Laufzeit, um das Minimum zu bestimmen,
ist natürlich nicht verschwunden. Sie brauchen die immer noch,
aber sie machen es nur einmal an einer anderen Stelle,
nämlich in dem die Crease Key zum Beispiel.
Das passiert ja auch ein bisschen beim Fibonachi-Hieb.
Also die Laufzeit verlagert sich wird verschoben in diese Operation,
ja, und das Delied braucht einfach das Xtract Min.
Und das Xtract Min wird die teure Operation sein, ja,
und alles andere, die anderen Operationen machen so ein bisschen Bookie-Pingore, ja.
Okay, das andere, was hier zu sagen ist,
ist, das sind amortisierte Kosten, das heißt,
diese Kosten erreicht man nicht im Worst Case für jede Operation,
sondern nur in der Sequenz von Endoperation, ja.
Gut, also, der Venuriale Hieb erlaubt jetzt also erstmal die effiziente Fusion,
also das ist das Junior, deswegen schauen wir uns das an,
der Fibunachi Hieb dann, die Laufzeit,
die amortisierte Laufzeit, Täter von 1 für die D-Crease-Key-Operation.
Und das ist genau das Entscheidende,
warum der Figur Nachihieb so eine hohe Bedeutung hat für den Deikstra-Algorithmus,
weil der Deikstra-Algorithmus ja für jeden Knoten in dem Grafen hier ein X-Track-Win macht.
Und für jede Kante in dem Grafen, ein D-Crease. Ja,
und weil natürlich in dem Grafen das M immer größer ist als das N,
ist es natürlich entscheidend, wenn sie es schaffen, die Decrease-Site zu reduzieren.
Und dadurch kommt die reduzierte Laufzeit nachher im Dixtra zustande,
weil wenn das nach wie vor Log-in-Zeit braucht,
und das geht aber in konstanter Zeit,
dann haben sie in dem Dixtra nachher natürlich die Laufzeit N,
Log-in, N, mal die Decrease-Operation, plus N,
und nicht mehr M-Log-in.
Und das ist natürlich ein substanzieller Unterschied.
Für dich, der Grafen, ist es
ein substanzieller Unterschied
Ja, wie so häufig betreiben wir jetzt natürlich jetzt einen hohen Aufwand,
um diese Werte zu erreichen im Operkül und die resultieren in hohen Konstanten.
Ich sage das gleiche am Anfang,
ich werde es auch am Ende nochmal sagen,
also bevor sich ein Fibonacci-Heep in einem Deixtra lohnt,
müssen sie einen verdammt großen Grafen produzieren,
weil einfach hier viel,
viel größere Konstanten an diesen Operationen hängen
Nur gut. Also, wir gucken, was der binomeale
Hieb macht und wieder aufgebaut ist. Die Idee ist toll,
das ist eine ganz tolle Struktur, die sich dahinter verwirkt.
Und zwar die eines sogenannten binomealen Baums. So ein binomealer Baum,
der sieht wie folgt aus, der ist rekusiv definiert.
Und zwar haben wir binomeale Bäume, beliebiger Ordnung K und der heißt dann BK.
Und so ein binomealer Baum, der Ordnung K,
der besteht aus zwei Bäumen, der Ordnung K minus eins.
Und von denen einer, das Kind, ein Kind des anderen wird.
Also wir haben hier zwei Bäume, der in Ordnung kam
minus eins und der eine wird jetzt so ein Kind des anderen.
Das sind keine binären Bäume mehr. Wir haben also jetzt
mehr als zwei Kinder. Und der binomoyale Warm der Ordnung Null
ist einfach ein Knoten. Und rekursiv haben wir so beliebig
große binomeale Bäume definiert. Hier ist mal so eine Serie,
hier ist der B0, der hat also nur diesen einen Knoten.
B1, würde jetzt aus zweimal B0 bestehen und einer von den
beiden B0 würde Kind des anderen werden und dann haben sie diesen Baum.
Wenn sie den B2 bauen, dann haben sie zweimal B1. Das
ist der eine B1 hier und das ist der zweite B1.
Und der eine wird Kind des anderen
Wenn sie den B3 bauen, dann haben sie zweimal ein B2,
also hier ist der eine, hier ist der andere.
Und der eine wird Kind des anderen. Und dann machen sie das so weiter.
Hier haben sie den B4 und dann wird der entsprechend immer größer.
Eine sehr schicke Struktur, die da entsteht.
Und die hat auch ein paar ganz faszinierende Eigenschaften,
wie wir gleich sehen werden. Die eine Sache,
die man jetzt noch wissen muss, ist,
dass wir sehen ja die Anzahl der Kinder und die Tiefe der Bäume,
die ist ja sehr unterschiedlich hier von jedem Knoten,
von den Knoten, von den Kindern.
Also der hat hier das erste Kind hier vor vorne oder das Letzte,
wenn sie so wollen, hat immer keine weiteren Kinder oder hier,
der hat noch eins und der hat dann zwei und es
wird dann immer mehr und die Tiefe wächst auch immer weiter.
Deswegen gibt es eine Ordnung. Und die Kinder sind hier geordnet,
das heißt, es gibt eine feste Reihenfolge,
so wie das hier dargestellt ist,
von den großen Bäumen hin zu den kleinen Bäumen.
Also wenn sie dann sich überlegen, ein Baum BK,
der sieht dann eigentlich so aus,
wir haben dann hier die Wurzel und das linke Kind ist ein Baum BK minus eins.
Das Erste Kind hier ist BK minus eins und das nächste Kind,
was sie hier sehen,
ist ein Baum BK minus zwei und dann kommt BK
minus drei und
so weiter bis
0. Wird das dann hier entsprechend horroren.
Das ist ja nur noch der einzelne Knoten. Okay,
die sind also jetzt so geordnet in dieser Reihenfolge, die ein binumialer Baum,
der größte Karl, hat in der Wurzel K-Kinder.
Also wir sehen hier, der Vierer,
der hat hier ein, zwei, drei, vier Kinder.
Und in den nächsten Iterationen werden das dann immer weniger Kinder.
Okay. Gut, so ist das hier erstmal konstruiert.
Und jetzt muss man sich natürlich erstmal ein paar Dinge,
müsste man erstmal klären, zum Beispiel,
wie viele Knoten hat so ein Baum,
wie hoch ist der oder wie tief ist der
Denkst, was sind die längsten Pfade, die hier vorkommen.
Und das kann man alles relativ schnell ausrechnen.
Das ist hier in diesem Lämmer zusammengefasst. Wenn wir also
einen binomealen Baum der Ordnung K haben, dann gilt,
erstens, BK besteht aus zwei hochk-fielen Knoten.
Dann zweitens, BK besitzt eine Höhe HVK gleich K,
hat also eine maximale Höhe von K. Dann können wir drittens,
wenn wir die Knoten in Ebenen einteilen und sagen mal,
null ist die Wurzel und dann bezeichnen wir die Ebene mit der Tiefe.
Also null ist die Wurzelebene, dann kommt die
Das erste eben die zweite und so weiter.
Da gibt es auf der Eden-Ebene des Binumialen Baums der Größe K
, genau K über I, viele Knoten.
Und diese Tatsache hier gibt dem Baum seinen Namen.
Deswegen heißt er Binumialbaum. Oder Binumialaborm. Dann haben wir schon gesehen,
die Wurzel hat einen maximalen Grad von K. Ja,
also es ist, dahin verbergen sich zwei Aussagen.
Erstens, die Wurzel hat den Grad K und die Wurzel
hat den maximalen Grad im Knoten.
Das sind die beiden Eigenschaften hier. Und fünftens werden
die Kinder der Wurzel von links nach rechts nummeriert,
mit K minus 1, K minus 2 und so weiter,
also gemäß dieser Ordnung.
So ist das Ite-Kind-Wurzel des Binumialen Baums BI
Okay, die meisten Eigenschaften kann man ja an dem Beispiel
sofort sehen, dass die stimmen müssen.
Bei der Anzahl der Knoten muss man jetzt noch ein bisschen rechnen,
aber dass sie zum Beispiel der B4, vier Kinder hat,
ist etwas, was man sofort sieht.
Man sieht ja auch sofort, dass der B4 eins,
zwei, drei hier diese Tiefe von K hat.
Ja, all das kann man eigentlich an dem Beispiel sofort erkennen.
Und sie können sich wahrscheinlich auch vorstellen,
dass man es relativ leicht nachweisen kann,
indem man diese rekusive Definition nimmt und einfach jetzt einen Induktionsbeweis macht und sagt dann,
wenn das für Kagel,
gilt es auch für Carpus 1. Oder wenn es für K
minus eins geht,
gilt es
auch für
Und das kann man ganz schnell ausrechnen. Also wenn,
wir können die Eigenschaften alle für B0 prüfen, da sind die alle wahr.
Das ist, man kann sie ganz schnell nachrechnen.
Und dann macht man den Schritt von K minus 1 nach K.
Und dann kann man zum Beispiel sagen,
wenn jetzt S von K die Anzahl der Knoten ist,
dann sehen wir, dass ein Binumialer Baum der Größe K.
Der besteht ja aus zwei Binumialen Bäumen, der Größe K
minus 1. Also haben wir zweimal die Größe von K minus 1.
Und wenn das jetzt im Induktionsbeweis,
können wir jetzt natürlich die Induktionsannahme einsetzen, Der Baum,
der Größe Kaminus 1, die Größe 2 hoch Kaminus 1 hat.
Das ist einfach die Behauptung hier, bekam minus eins.
Und dann kommt da 2 hoch K raus.
Also es sind genau zwei hoch K, viele Knoten drin.
Für die Höhe sehen wir auch, was passiert.
Also der Baum, der Größe K.,
der besteht ja aus zwei Bäumen der Größe Kaminus 1.
Der eine wird Kind des anderen. Deswegen ist die Höhe
auf des Baumes K genau um eins größer als die Höhe des Baumes von K-Eis.
Also hier, wenn wir die beiden haben,
die haben hier eine Höhe, das ist die beiden.
Und wenn wir den neuen Baum-BK konstruieren, dann wird der
eine Baumkind und dadurch steigt die Höhe um eins.
Also der Gesamtbaum-WK ist um eins höher als die beiden anderen Bäume.
Und dann haben wir halt gezeigt, dass HK gleich K ist.
Das ist die Höhe Markal entspricht. Vierte Eigenschaft,
die Wurzel hat den maximalen Grad mit dem Wert K.
Kann man auch sofort zeigen. Also die Wurzel des Bindumialbaums
mit K-Fielden der Ordnung K ist natürlich der Grad,
den wir haben
An dem Vinomealbaum der Größe Kaminus 1. Und dann kommt eben
der eine, das eine zusätzliche Kind dazu,
weil wir zwei Bäume der Größe Kaminus 1 miteinander verknüpfen.
Kommt also einer dazu und dann haben wir Kaminus 1 plus 1 macht K.
So, die Eigenschaft 3 hier, da muss man
ein klein bisschen rechnen. Wenn man mal schaut,
wie viele Knoten eigentlich auf einer speziellen Höhe sind,
dann haben wir jetzt genau diesen Versatz in den Bäumen drin
Also wenn wir jetzt den Baum konstruieren, den BK,
ja, dann haben wir quasi, hier ist der,
das ist der BK minus 1. Hier kommt diese eine Kante,
ja, und dann kommt hier der entsprechende Baum nochmal, der BK minus 1.
Und wenn wir jetzt, wenn wir jetzt überlegen,
wie viele Knoten sind jetzt auf einer Ebene,
wenn wir jetzt mal hier so eine Ebene immer betrachten,
ja, dann sehen wir,
dass der eine Baum jetzt um eine Ebene nach unten gerutscht ist,
weil der ja Kind geworden ist von der Wurzel.
Und jetzt müssen wir die entsprechenden Werte addieren. Und zwar
haben wir auf der Ebene I für den Binumialbaum der Größe K,
die Anzahl der Knoten,
die wir auf der Ebene I haben für den Binumialbaum,
der Größe K minus 1. Das sind nämlich die,
ja, plus die Anzahl der Knoten,
die wir jetzt in dem anderen haben,
aber da hat sich die Höhe um eins verringert.
Das heißt, plus SI minus eins von K
minus eins. Das sind, die auf der,
also auf dieser Seite hier haben wir die SI von K
minus 1. Und hier dieses Stückchen hier,
das ist das SI minus 1 von Kaminus 1.
So, bei die können wir addieren. Und jetzt setzen
wir da unsere Illusionsvoraussetzungen ein. Das ist dann K minus
1 über I und K minus 1 über I minus 1.
Das können Sie jetzt auch ausrechnen,
aber das ist auch eine der ganz bekannten Rechenregeln für Binomialkoeffizienten,
dass das natürlich genau K über I entspricht.
Ja, und dann haben Sie das gezeigt.
Und deswegen, ja, deswegen heißen die auch Binomiale Bäume.
Ja, und dieses fünfte Kriterium,
das sieht man natürlich eigentlich schon an den Bildern.
Also hier, dass wir hier jeweils Car-Bäume haben und
die jeweils die Indie-CSB-K minus 1 bis B-0 durchlaufen.
Das sieht man schon. Also BK wird gebildet,
indem eine Instanz von BK minus alles als erstes Kind einer
weiteren Instanz von BK-Minus 1 verkettet wird.
Das ist die Art, wie wir das bauen.
Und damit ist das erste Kind auf alle Fälle Wurzel eines
minumialen Baumes BK minus 1. Also das erste Kind hier.
Und für die anderen haben wir die Eigenschaft eigentlich durch
die Induktionsannahme schon gezeigt
Ja, okay, das sind sozusagen so ein paar Grundwerte,
so ein paar Eckdaten von unserem Baum hier.
Und wir sehen schon, dass für uns typischerweise so wichtige
Verhältnisse von Höhe zu Größe in den ersten beiden Eigenschaften,
wir haben eine Höhe von K und wir haben zwei hochk viele Knoten.
Ja, das ist super,
weil wenn wir dieses Verhältnis von Höhe zu Größe haben,
dann wissen wir, dass wir zwei hochk viele Werte speichern
können und den Baum von der Wurzel zu einem Blatt in K-Zeit durchlaufen können.
Oder wenn wir das Ganze logaritieren,
können wir auch sagen,
wir können en viele Werte in einem Baum speichern und könnt
in Log-End-Zeit von der Wurzel
zu einem Blatt laufen
Gut, das ist das nächste. Beschränkung von Knoten,
Grad und Tiefe für den Venue Jan B Baum B,
K mit Endknoten gilt, dass die Höhe von K in O
von Log-in liegt und der maximale Knotengrad ist durch die Wurzel bestimmt,
der ist auch Log-in.
Beim binären Baum haben wir zwei Kinder, hier haben wir
mehr Kinder, hier haben wir bis zur Log-in-Kinder.
Wenn Sie sich mal für einen Augenblick vorstellen würden,
so ein binärer Baum, so ein biinumialer Baum, würden Sie als Suchbaum verwenden?
Hätte man ja machen irgendwie, dann müsste man dafür sorgen,
dass die hier entsprechend immer die Objekte, die da gespeichert sind,
dann sortiert sind nach der Größe und dann weiß man,
in welchen Ast man hier reinlaufen muss und so.
Und dann würden sie dadurch, dass sie hier Login-Kinder haben,
hätten sie die Situation, dass sie,
um von dem einen Knoten auf die nächste Ebene zu gehen,
müssen sie ja alle Kinder einmal betrachten.
Das heißt, wir bräuchten Login-Zeit,
um von der einen Ebene in die nächste zu gehen.
Das heißt, wenn sie sowas als Suchbaum verwenden würden,
hätten sie eine Laufzeit von Login-Quadrat, Login-Tiefe und Login-Zeit,
weil sie hier die Kinder betrachten müssten.
Nur mal so, machen wir natürlich nicht. Okay,
aber der Knotengrad wird noch wichtig für uns,
deswegen haben wir hier den auch schon abgeschätzt,
dass der eben
auch Logorithmisch ist
Also lugorithmisch, viele Kinder, lugorithmische Tiefe und das alles
ohne irgendeine Art von Balance. Okay, so,
jetzt haben wir diese Bäume und aus diesen Bäumen bauen wir jetzt einen Hieb.
Jetzt können wir uns natürlich sofort vorstellen, dass jeder von diesen Bäumen für sich genommen eine Hieb-Eigenschaft erfüllen kann. Also so einer,
da könnte ich dann sagen, gut, super,
hier steht dann eben bei einem Minhieb das Minimum drin und dann müsste das größer sein als der Wert von seinen Kindern und da,
der hat wieder Kinder, da muss der Wert wieder nächsten Minimumhieb,
muss kleiner sein als der Wert seiner Kinder und entsprechend hier,
die Kinder haben dann wiederum Wert,
die größer sind als die von dem Vorgängerknoten.
Und dann hätten wir quasi die Hieb-Eigenschaft übertragen von dem binären Baum auf diesem Biomialbaum.
Das ist noch relativ einfach. Problem ist allerdings,
dieser Baum hat ja eine feste Anzahl Knoten.
Ich kann jetzt nicht einfach irgendwie was weglassen.
Und wir wollen natürlich in unserem Hieb eine beliebige Anzahl von Elementen
speichern können und nicht gerade nur zufällig hier so ein 2-Hoch-K-Wert.
Das wäre ja irgendwie ungünstig
Deswegen müssen wir jetzt uns überlegen, wie wir aus diesem
binomealen Bäumen Hiebs machen. Und was wir machen ist,
wir nehmen nicht einen von diesen Bäumen,
sondern wir nehmen eine Menge von solchen Bäumen.
Und zwar machen wir das so,
wir nehmen also eine Menge von binomealen Bäumen und setzen voraus,
erstens, jeder Baum erfüllt die Minhieb-Eigenschaft.
Also damit ist jeder Baum für sich sozusagen ein Hieb.
Und weil wir, wie gesagt, nicht wissen,
wie viele Elemente wir haben und das eben keine Zweierprozents ist,
erlauben wir halt mehrere Bäume, aber wir schränken das ein.
Und zwar schränken wir das so ein,
dass wir sagen, dass es jedem Baum BK
nur einmal geben darf
Also wir dürfen nicht jetzt irgendwie eng mal dem Baumb 0 nehmen,
sondern wir dürfen maximal einmal den Wert, den Baumb 0 haben, ja.
Und da die Bäume für sich genommen, ja, Zweierpotenzen als Knotenmengen haben,
können wir damit jede beliebige Anzahl von Knoten speichern. Also wir wissen,
der B-0 kann ein Element speichern und der B1,
wenn das hier das BK ist,
dann kann der halt ein Element speichern,
das wäre dann das S von K und der kann halt zwei speichern und der Zweier kann dann vier speichern und so weiter.
Und dann können sie natürlich jede beliebige Anzahl von Knoten speichern,
indem sie jetzt, wenn sie jetzt sagen, die Wochen elf,
dann nehmen sie halt den Baum und den Baum und den Baum.
Ja, und dann haben sie elf Elemente, die
sie speichern können. Ja, und sie brauchen nicht,
wir brauchen tatsächlich von jedem Baum nur einen, um das zusammenzusuchen.
Okay, so, wie viele Bäume brauchen wir jetzt insgesamt,
um die Anzahl der Elemente zu speichern?
Das sieht man auch relativ schnell. Und zwar können wir ihr sehen,
dass so ein B-Hieb zur Speicherung von N-Objekten maximal Log-in plus 1,
also locker nach unten abgerundet plus 1, binomiale Bäume verwenden muss.
So, wie kann man das sehen? Naja,
Das wird eigentlich aus dieser Konstruktion ja schon klar.
Wir wissen, dass jeder Baum nur einmal vorkommt. Das heißt,
so ein AI, das wäre null oder eins, also null,
wenn der Itebaum nicht vorkommt und eins, wenn er vorkommt.
Und dann wissen wir, die Gesamtzahl aller Objekte,
die wir haben, ist dann die Summe für alle I-Werte,
AI mal zwei Hochi, weil so ein Baum,
der ist dabei oder der ist nicht dabei.
Ja, auch wenn er dann dabei ist, dann werden dort zwei Hochi viele Werte gespeichert.
Und jetzt wissen wir schon, der größte Baum,
der vorkommen kann, der ist beschränkt durch Lock 2N,
weil ja, also wenn wir nur N-Elemente speichern,
dann können wir gar keinen größeren Baum nehmen,
der hätte ja dann noch mehr Elemente,
als wir überhaupt nur insgesamt Elemente zur Verfügung haben.
Und da wissen wir, wir haben hier eine Summe,
dass die Summe über alle Elemente, I gleich 0 bis
Lock 2 N, AI, mal zwei Hochi ist.
Das ist genau die Konstruktion. Jeder Baum kommt maximal einmal vor.
Und wenn er dann vorkommt, hat er zwei hochi viele Elemente. So,
wenn wir jetzt nur wissen wollen, wie viele Bäume wir haben,
brauchen wir natürlich nicht die Summe A, I, 2, Hochi.
Das ist ja die Anzahl der Elemente, sondern nur die Summe über die AIs.
Das ist ja die Anzahl der Bäume, die wir haben. Das heißt,
wir können die Summe über die AIs berechnen und weil wir wissen,
dass der größte Baum durch Lock 2 N beschränkt ist,
wissen wir, und das AI immer nur 0 oder 1,
jetzt wissen wir,
dass das natürlich höchstens Log 2N plus 1 sein kann.
Ja, also wir haben nur lugorithmisch viele Bäume,
die hier auftreten können. Aber das ist etwas,
was absolut klar ist, weil wir ja wissen,
dass wir auch in der Binärkodierung natürlich immer nur lugorithmisch viele Bits brauchen,
um eine Zahl der Größe hinzuspeichern.
So, wie machen wir denn das jetzt?
Bisher haben wir eigentlich typischerweise nur binäre Bäume gesehen.
Da haben wir immer zwei Kinder gehabt. Das war datenstrukturell
ganz einfach. Da hatten wir dann Knoten und dann gab
es dann irgendwie Left und Ride oder irgendwie Child One,
Child 2 oder wie auch immer sie das machen wollen.
Jetzt haben wir eine variable Anzahl von Kindern
In einem Baum und da müssen wir uns überlegen,
wie wir das machen, wie wir das umsetzen wollen,
um dann effizient diese Strukturen manipulieren zu können.
Ja, wir könnten natürlich irgendwie Arrays bauen, wo dann alle Kinder irgendwie drin sind.
Das ist aber recht ungünstig, weil wir ja diese Kinder,
die Liste in der Kinder ständig verändern, ja.
Deswegen verwendet man die folgende, die folgende Datenstruktur,
die typisch ist für Bäume mit variablen Knotengrad.
Das ist die linkes Kind rechter Bruderdarstellung oder Left Child Right Sibling,
Ausstellung und was man macht, ist, wenn wir also einen Baum,
also so eine Konstruktion hätten wie diese hier,
dann hätten wir hier Knoten und solche Knoten haben einen Parent,
also einen Verweis auf den Vorgänger.
Sie haben dann den Schlüsselwert, also das Objekt,
was wir eigentlich speichern wollen. Wir werden hier auch den Grad speichern,
der Grad heißt, wie viele Kinder habe ich eigentlich?
Und dann gibt es einen Verweis, aber nur einen Verweis,
und zwar auf das erste Kind.
Und jeder Knoten hat dann noch einen weiteren Zeiger auf das nächste Kind.
Das heißt, wir haben sozusagen eine Liste für die Kinder.
Das ist dann also der Zeiger zum Bruder oder zum Sibbling im Englischen
, zum Geschwister. Das ist ein bisschen neutraler hier.
Und auf die Art und Weise können wir jetzt den Baum realisieren.
Das heißt, wir haben wieder Knoten mit einer,
ja, mit einer konstanten Ansage von Objekten, von Verweisen,
mit der wir die Struktur dann vollständig konstruieren können.
Ja, so bauen wir unsere Datenstruktur. Also das ist
die Anzahl der Zeiger, die wir im Folgenden immer verwenden werden,
um in unserem Baum zu navigieren.
Und was wir jetzt noch machen, ist,
wenn wir jetzt also eine Menge von Bäumen haben,
dann werden wir einfach diesen Bruder-Zeiger, diesen Sibling hier,
den werden wir einfach benutzen, um daraus auch eine Liste zu machen.
Also die werden ja bei der,
eine Wurzel hat ja keinen Bruder hier normalerweise,
aber wenn wir eine Menge von Bäumen haben,
dann benutzen wir diesen Zeiger einfach, um die zu verketten.
Okay, das ist also die Zeigerstruktur, mit der wir arbeiten.
Okay, so, wir beginnen, wir gucken uns mal an,
wie die Operationen jetzt laufen auf dem bindomealen Hieb.
Das erste, was wir machen müssen, ist das Minimum zu bestimmen.
Wenn wir das Minimum bestimmen, dann wissen wir, dass jeder,
der Bäume hier, das Minimum in der Wurzel stehen hat,
weil jeder Baum für sich die Hieb-Eigenschaft erfüllt.
Deswegen muss hier jeweils das Minimum drinstehen. Wir haben aber
eine Menge von Bäumen und hier gibt es keine Regeln mehr,
dass jetzt irgendwie das Minimum der Bäume hier auf der linken Seite stehen müsste oder so.
Da gibt es keine Regeln mehr. Deswegen müssen wir uns alle Bäume angucken,
jeweils die Wurzel betrachten und davon das Minimum bilden.
Und das ist der Grund, warum wir eine Laufzeit von Login bekommen,
denn wir haben gerade gesehen, wir brauchen Login viele Bäume.
Und wenn wir jetzt das Minimum finden wollen,
laufen wir einmal über diese Liste drüber und suchen da das Minimum aus.
Also von dem Bynomeal hiebt das Minimum zu finden,
bedeutet, wir haben hier zwei Zeiger, Y und X,
das Y wird Nil, das X ist erstmal der Head, der Kopf.
Und mit dem Y, da werden wir uns das Objekt
merken oder einen Verweis auf das Objekt merken mit dem Minimum.
Und dann haben wir hier, setzen wir eine variable Minimum zunächst auf unendlich.
Dann haben wir die Wildschleife, die durchläuft,
quasi die Wurzeln aller Bäume. Und solange der Schlüsselwert hier,
wenn der Schlüsselwert kleiner ist als das bisherige Minimum,
speichern wir in Min das neue Minimum und in Y das entsprechende, den entsprechenden Knoten.
Und dann gehen wir hier zu dem entsprechenden Bruder,
also zum nächsten Baum und wenn wir die alle abgearbeitet haben,
kennen wir das Minimum. Ja und wir sehen sie sofort,
dass man die Minimum-Funktion auch in konstanter Zeit realisieren kann,
wenn man nämlich nur diesen Zeiger Y, der auf das Minimum verweist,
eben einmal berechnet und dann entsprechend speichert in der Datenstruktur.
Okay, Minimum gibt also einen Zeiger auf das kleinste Element
zurück und aufgrund der Minhieb-Eigenschaft müssen wir uns immer nur die Wurzeln angucken.
Wir wissen schon, es gibt Login viele Wurzeln im Worst
Case, das heißt die Minimum-Operation braucht Login-Zeit.
Bevor wir uns jetzt daran machen, die anderen Operationen zu betrachten,
wir müssen ja jetzt diese Baumstrukturen verändern und jetzt wird es ein bisschen knifflig,
weil wir ja in den,
weil die Venomialen Bäume für sich genommen eine feste Struktur haben bezüglich der Knoten.
Also wenn wir jetzt irgendwas entfernen würden,
wenn wir jetzt sagen würden, wir entfernen jetzt ja das Minimum,
das wäre der, dann hätten wir ein Problem,
weil jetzt wird in diesem Baum ja ein Knoten fehlen.
Und die Frage ist, was machen wir jetzt hier mit
diesem Knoten? Wo holen wir den her? Ja,
den können wir nicht so ohne weiteres holen.
Und der Trick wird sein, dass wir diesen kompletten Baum,
wo wir den Knoten entfernt haben, quasi auflösen.
Und dann mit dann vermischen werden mit dem Rest der Struktur.
Okay, dazu brauchen wir eine Linkfunktion,
die zwei Bäume nimmt und einen neuen Baum macht.
Die macht also diese klassische Operation, die wir immer gesehen haben,
aus zwei Bäumen der Größe BK minus eins mit einem Baum BK produziert.
Und die Linkfunktion ist immer so, wie hier auch dargestellt.
Also hier wäre, das wäre Y und das wäre Z. Das heißt,
der erste Parameter beschreibt den Baum, der zum Kindknoten wird.
Das werden wir gleich implizit voraussetzen. Der zweite hier ist derjenige,
der quasi die Wurzel behält. Okay, das heißt,
Y kriegt einen neuen Parent, das ist Z,
also der verzeigt jetzt dann hier oben da drauf.
Der Y kriegt einen neuen Bruder, das ist das erste Kind von Z.
Also hier war irgendwie das erste Kind und hier kommt jetzt der Bruder,
das ist dann, das ist ja ein der hier.
Und dann wissen wir, das Z kriegt ein neues erstes Kind,
das ist Y, das heißt,
der zeigt dir dann hier runter und der Knotengrad von Z wird um 1 erhöht.
Das sind die Sachen, die wir machen müssen Und dann haben wir
die beiden Bäume miteinander verknüpft und haben einen neuen Baum, der Größe BK.
Und die Wurzel dieses neuen Baums ist die Wurzel des zweiten Parameters,
des Baums, der im zweiten Parameter steht.
Okay. So, jetzt haben wir also,
sind wir also in der Lage, zwei Bäume zu verknüpfen.
Wenn wir jetzt,
wir werden jetzt erstmal auf die Union-Operation hinarbeiten und die später dann häufiger verwenden,
wenn wir jetzt mal uns ein Union vorstellen,
dann haben wir jetzt also zwei solcher binomealen Heeps,
sind also zwei solcher Strukturen
Die wir jeweils durch so Listen angegeben sind.
Und jetzt wollen wir daraus einen Hieb machen. Naja,
was wir erst mal tun, ist einfach zu sagen,
ja, wir haben zwei Listen mit Wurzeln und die
bauen wir zusammen zu einer großen Liste mit Wurzeln.
Das macht Benormial Heap Merch. Und dabei sorgen wir dafür,
dass die Baumgröße von links nach rechts sortiert ist.
Dass also weiter links die kleinen Bäume stehen. Das ist
genau das, was sie vielleicht noch aus dem Merch-Sort kennen.
Wir haben also zwei Listen, die sind sortiert.
Das sind die Wurzeln der minumialen Bäume.
Und zwar sortiert nach der Größe. Vorne stehen die Kleinen
hinten die großen Bäume. Und aus der machen wir jetzt
eine große Liste
Was also eine klassische Merch-Funktion, wir haben also so zwei Listen,
wir haben eine Wild-Stleife, die läuft so lange,
bis die eine oder die andere Liste leer ist.
Nee, sie läuft so lange, wie in
der einen oder der anderen Liste noch Elemente drin sind.
Wenn die erste Liste leer ist oder wenn der Headknoten einen kleineren Grad hat,
als der Headknoten der Liste G, der zweiten Liste,
oder wenn der Knotengrad gleich ist und der Schlüsselwert an G,
ist größer als der an H,
dann nehmen wir jetzt das Element von H und hängen das
an die neue Liste an
Wir erzeugen hier so eine neue Liste,
da hängen wir das an, das macht diese Zeile hier.
Anderenfalls nehmen wir den Kopf der Liste G und hängen die
an die neue Liste an. Und mit der letzten Operation
löschen wir quasi diesen Baum aus der Liste raus.
Okay, also das ist nichts weiter als ein klassisches Merch
aus dem Merch-Sort, wenn sie so wollen.
Und da wird quasi eine neue Liste erzeugt.
Jetzt haben wir nur ein Problem,
also wir hatten ursprünglich halt unsere Haarliste,
da waren hier
die Wurzeln
Ja, und die Wurzeln hatten dann potenziell,
waren das hier halt solche Bäume und so weiter.
Und die G-Liste war genauso. Und jetzt haben wir die zusammengemischt
. Und jetzt haben wir natürlich die eine Eigenschaft verletzt,
der vinumialen Heeps, nämlich die Tatsache,
hier kommt ja vielleicht das Gleiche vor.
Und der hier, ja, lasse ich mal weg,
da muss ich so viel malen.
Wenn ich das jetzt merge, dann habe ich schon erreicht,
also die waren so ein paar Kettet.
Wenn ich jetzt den, das ist dann der G plus H,
was ich jetzt schon erreicht habe,
ist,
dass ich so
eine Struktur habe
Scheiße. Ja, also ich habe die jetzt schon zusammengebaut
und der Größe nach sortiert. Ja, das habe ich
schon erreicht mit der Funktion, die da steht.
Aber ich habe eine Eigenschaft verletzt, nämlich die Eigenschaft,
dass jeder, jeder binomeale Baum nur einmal vorkommen darf.
Ja, weil jetzt in G und H kamen die zwar immer nur einmal vor,
aber durch das Zusammenmischen können die jetzt natürlich zweimal vorkommen.
Okay, das muss ich noch reparieren. Ja,
und das ist genau das, was die Union-Operation jetzt macht.
Die macht also jetzt diese Merch-Operation als ersten Schritt und danach
repariert die diese Eigenschaft, ihr das,
die Bäume
nicht doppelt vorkommt
Ja, und wie kann die das machen? Da nutzt sie genau diese rekusive Definition der bindomialen Bäume. Wenn ich nämlich sowas sehe hier,
ja, Sonne, dass zwei Bäume doppelt vorkommen,
dann kann ich das natürlich reparieren,
indem ich aus diesen beiden Bäumen einen Baum mache, ja,
das sind einen, das ist den einen Baum dann weg,
ja, und mache den einfach zu einem Kind,
das dahinter liegenden Baum ist.
Das wandert da runter, ja. Und jetzt habe ich natürlich das Problem,
dass hier vielleicht der Baum der nächsten Größe Dreimal vorkommt,
aber das ist auch nicht so schlimm,
dann kann ich mich durch die Liste weiter nach rechts wandern,
wenn ich da weiter durchwandern und dann kann ich wieder zwei Bäume nehmen,
der nächstgrößeren Stufe und die wieder zusammenbauen und einen Baum der nächsthöheren Ordnung konstruieren.
Durch die rekrusive Art, wie das definiert ist.
Das ist also die Strategie, also wir erzeugen eine geordnete
Wurzelliste durch das Mischen der Wurzellisten von H1 und H2,
das haben wir gerade schon gesehen.
Diese neue Wurzelliste enthält eben alle Bäume von H1 und H2
und einige der Bäume treten potenziell doppelt auf,
also der größte K treten dann doppelt auf.
Jetzt laufen wir von links nach rechts da durch,
vor einigen jeweils die doppelt tretenden Bäume, BK,
zu dann einem neuen Baum-BK plus 1.
Und das ist dann wie beim Addieren mit Übertrag.
Dann kann dann zu dem Zeitpunkt sein, dass tatsächlich drei Bäume auftreten,
der größte BK. Zwei gab es vorher schon,
der eine, der durch das Addieren von zwei Bäume,
der größte Carbon minus eins entstand.
Ja, und da muss ich die drei Bäume,
kann ich dann aber auch wieder addieren,
indem ich zwei davon zu einem größeren mache.
Und danach gibt es dann eben noch einen,
der größte Carminus eins, der dann übrig bleibt.
Wenn da so BK dreimal auftritt,
dann kann ich zwei davon vereinen,
dann bleibt eben noch ein Baum der Größe Carminus eins übrig
Und dann habe ich entsprechend Zielerreiche. Ja? Der ist nicht schlau,
das ist nicht von Winz durchzugehen, sondern von Winz.
Weil wenn wir jetzt bei den Linken überfinden,
dann hätten wir da um 8.000 rechts davon während Vierer,
dann wären sie nicht mehr sortiert.
Aber wenn wir die rechten beiden Vierer nehmen, wie sie es angeschrieben haben,
die wir aber nicht zuerst als zwei gleichgroße finden,
wenn wir vor links durchgehen, dann gut ein kleines Saisontier.
Also der, ja, genau, das macht der Algorithmus tatsächlich auch.
Also in dem Fall, wo wir drei,
das werden wir jetzt gleich sofort sehen,
also in dem Fall, wo drei Enge,
also wir laufen von grundsätzlich von links nach rechts Und wenn wir drei sehen,
dann werden wir nicht in Vornrennen, sondern die hinteren beiden zusammenfügen.
Das ist tatsächlich, um genau diese Sortierung zu erhalten.
Okay, so, hier ist der Pseudo-Code, der das tut.
Hier, da rechts daneben sehen Sie die Kommentare,
weil das ist jetzt so viel Geschiebe mit dem Pointer.
Da kommt man schnell durcheinander. Also hier ist also
die große Wildschleife und wir schauen uns zu jedem Zeitpunkt immer
einen Knoten X an und seinen Vorgänger und seinen Nachfolger.
Wir haben also drei so Zeiger, mit denen wir uns dadurch bewegen.
Wir haben also, also die Wurzeln. Und wir haben
also immer drei Stück. Wir haben das X und das
hier ist dann der Vorgänger und das ist der Nachfolger.
Die drei schauen wir uns immer an. Solange wir noch
einen Nachfolger haben, läuft halt diese Schleife und guckt immer,
ob Bäume doppelt vorkommen. Wenn der Knoten X,
bei dem wir uns befinden und der Nachfolger den gleichen Grad haben,
also die beiden, dann wissen wir,
wir müssen an dieser Stelle nichts tun,
sondern können einfach zum nächsten Knoten gehen.
Weil wir müssen ja nur gucken,
ob irgendwo zwei Bäume mit gleichem Grad sind
Wenn wir sehen, dass X den gleichen Grad hat,
wie der Nachfolger von X und wenn es noch einen Nachnachfolger gibt,
den kriege ich hier jetzt nicht auf die Tafel drauf.
Also das wäre sozusagen dann der Nachfolger von X und davon der Sibling.
Sie kriegen gleich noch ein schönes Bild auf der Folie.
Wenn es den auch noch gibt und wenn die alle drei
den gleichen Grad haben, dann tue ich auch nichts,
dann gehe ich einen Schritt weiter.
Und das macht genau das, was Sie gerade gesagt haben.
Wenn ich dreimal das Gleiche habe,
dann will ich nicht den mit dem mergen,
sondern dann gehe ich eins weiter und merche die hinteren beiden
Okay, so, ja. Ich stehe da drauf ungleich
. X The Green Ungleich Successor von Extinction. Ja,
genau. Wir sind zwei, die ungleichen Blatt haben.
Oder wenn 30... Oder wenn 30 genau mehr. Okay,
so, und hier, die Zeile 11 und 12
ist nur das Weiterschalten. Dann wird Pratt X,
wird X und X wird Successor von X. Also da gehen
wir jetzt dann hier so einfach eins weiter.
So, anderenfalls, ja, jetzt kommt etwas,
also andernfalls gucke ich mir die Schlüssel an
Und gucke, ob der X-Knoten den kleineren Schlüssel hat,
als der von dem Nachfolger. Also jetzt möchte ich im
nächsten Schritt, möchte ich diese beiden jetzt vereinigen.
Das ist jetzt, keine Ahnung, zum Beispiel sind das
zwei solche Bäume, die haben den gleichen Grad.
Die möchte ich jetzt vereinigen. So, jetzt gucke ich,
ob der Schlüsselwert, der in X gespeichert ist,
kleiner ist, als der, in dem Knoten,
ja, in dem Nachfolgeknoten. Wenn dem so ist,
dann entferne ich den Nachfolgeknoten aus der Liste,
indem ich einfach den Verweis,
diesen simplen Verweis auf das übernächste Element verweisen lassen,
ja,
dann ist der ja raus aus der Liste
Und dann rufe ich Benno mir den Link auf und
der Successor von X steht vorne. Das heißt,
dieser Baum, der wird jetzt hier vorne das erste Kind.
Das ist der, der wandert den nach links rüber.
Okay. Seid ihr 16-2-3,
soll ich einfach vorne gut sein.
Nee, der gehört zu dem If Year.
Wenn das nicht der Fall ist, also wenn der,
dann ist sozusagen der Successor der kleinere Wert,
dann müssen wir den Baum unter unter den zweiten hängen.
Das passiert genau hier. Da müssen wir den Knoten X entfernen.
Das könnte jetzt aber dummerweise der Anfang der Liste sein.
Deswegen müssen wir noch mal gucken, ob der Vorgänger existiert,
wenn nicht, dann haben wir einen neuen Listenkopf.
Anderenfalls nehmen wir das Element X aus der Liste,
indem wir von Prat X auf den Nachfolger zeigen.
Und wir machen Binomir Link, aber jetzt in der anderen Reihenfolge.
Jetzt hängen wir das X unter den Nachfolger von X. So,
und dann kriegt das X noch den neuen Wert und dann
können wir sozusagen und der Successor von X kriegt auch einen
neuen Wert und dann können wir die nächste Iteration der Schleife machen
Hier ist eine Stelle drin im Code,
die ist streng genommen nicht nötig ist in der Art und Weise,
wie ich ihn dem Pseudocode präsentiere. Und zwar ist es diese Geschichte,
die ich eigentlich unschön fand und dann der Vollständigkeit halber drin gelassen habe,
diese Fallunterscheidung, wer von den beiden hier den kleineren Schlüssel hat,
der wäre nicht nötig, weil,
weil ich bei Benomial Heap Merch den Fall hier eingebaut habe,
dass wenn der Knoten gerade gleich ist,
dann kommt der mit dem Größenschlüssel nach vorne.
Das ist in der Art und Weise,
wie es im Korm gibt es diesem Pseudocode überhaupt nicht,
ja, der fehlte einfach.
Da wird gesagt, ja,
das wird gemerged
Habe ich den aufgeschrieben und dann habe ich das so aufgeschrieben,
weil ich es besser fand.
Und wenn man es so macht, dann wäre die Fallunterscheidung nicht nötig.
Wenn man das nicht machen würde, dann wären die in
einer beliebigen Reihenfolge, da müsste man die Fallunterscheidung machen.
Es ist also nur so ziemlich genau so steht es eben auch im Kormen drin.
Aber der andere Pseudocode steht gar nicht drin und deswegen ist das eigentlich,
es ist jetzt nichts falsch, sondern das ist einfach nur,
es wäre eigentlich nicht nötig, weil wir schon sichergestellt haben,
dass immer nur der eine Fall eintritt.
Okay, hier ist noch ein schönes Beispiel. Wie gesagt,
jetzt mit der alten Nomenklatur, das bitte ich zu entschuldigen.
Hier unten habe ich nochmal eine kurze Übersetzung hingeschrieben,
aber das kriegen sie bestimmt hin
Das ein bisschen habe ich es eben schon angemalt. Wir haben also die Situation,
wir haben Vorgänger von X, wir haben X,
wir haben Nachfolger und wir haben dann den Nachfolger des Nachfolgers.
Das ist also hier bei uns der Sibling. Das ist
dann der Knoten D hier. Die haben wir Knoten gerade
oder die haben Baum gerade und die sind aufsteigend sortiert.
Und im Fall 1, hier werden die alle unterschiedlich,
dann rutschen wir nur eins weiter nach rechts.
Im Fall 2 haben wir die Situation, dass wir dreimal den gleichen Grad haben,
dann gehen wir auch nur eins weiter nach rechts.
In Fall 3 haben wir jetzt zwei Knoten mit gleichem Grad
und der Schlüsselwert von Position X, ist kleiner gleich dem Schlüsselwert des Nachfolgers.
Dann sehen Sie, dass der Knoten C aus der Liste
rausgenommen wird und der wandert, wird zum Kind von X,
der rutscht sozusagen hier nach vorne und wird das erste Kind.
Das liegt daran, die Bäume sind der Größe nach Aufsteigen sortiert,
aber innerhalb eines Baumes sind die der Größe nach Fallen sortiert,
die Kinder.
Deswegen darf man nicht durcheinander kommen. Also die Bäume selber
aufsteigend, aber hier die Bäume BK gehen also von Großen
bis zu
kleinen Mann
So, und andersherum, das ist der Fall,
der wie gesagt eigentlich nicht mehr auftreten kann,
wenn man das Merch richtig macht.
Also der Schlüsselwert von X größer ist als der Schlüssel des Nachfolgers,
dann wird B eben aus der Liste rausgenommen oder das X
und wird dann zum Kind von dem Knoten, der danach kommt.
Okay, das ist eigentlich schon alles und damit können wir einen Junior machen.
Hier ist auch noch mal ein Beispiel, wie das aussieht.
Wir haben also hier mal zwei Bäume, vinuinale,
nein, zwei vinuinale Heeps, einer mit diesen drei Bäumen,
einer nochmal mit drei.
Hier gibt es dann so einen ganz großen dabei.
Und sie sehen, jeder Baum für sich erfüllt die Minhieb-Eigenschaft
Und sie sehen, dass es hier dann einer der Bäume das Minimum liefert.
Also hier ist es der zweite und hier ist es auch der zweite.
Es könnte aber auch ein beliebiger anderer sein. So,
wenn Sie jetzt ein Merch machen, ist der erste Schritt aus
diesen beiden Listen wieder eine Größensortierte Liste der Bäume zu machen.
Was wäre der erste Schritt? Und dann sehen Sie,
jetzt kommen, wollen wir doppelt vor. Dieser kommt doppelt vor,
der auch, die hier nicht. Jetzt kommt die Union-Operation,
die jetzt Diese Wildschleife hier realisiert und über diese Liste drüber geht.
So, dann haben wir den Fall 3. Als erstes zwei
Bäume gleicher Größe hier. Das sind beide der Größe B0.
Da würde jetzt also ein neuer Baum B1 entstehen,
12, 18. Jetzt haben wir den Fall 2.
Wir haben nämlich dreimal einen Baum der Größe 2. Das heißt,
wir gehen einfach nur einen Schritt weiter.
Und im nächsten Schritt hätten wir jetzt dann den Fall 4
und würden dann, hier ist jetzt das X,
würden diese beiden Bäume jetzt vereinigen.
Hätten dann also den hier, das wäre der Neue,
der neue binäre Venomial Tree, dann würden wir den mit dem Nachfolger vereinigen.
Das wäre dieser hier und dann wären wir fertig.
Dann hätten wir also
die neue Struktur
Okay, Korrektheit und Laufzeit. Also die Korrektheit folgt aus der Innenvariante,
dass links von der Position X in der Wurzelliste jeder Baum maximal einmal vorkommt.
Das ist die Innenvariante. Rechts davon können die noch mehrfach vorkommen,
aber links davon kommt jeder Baum nur einmal vor und immer dann,
wenn sie jetzt einen korrekten, vollständigen Korrektheitsbeweis aufschreiben würden,
müssten sie zeigen,
dass wenn sie in der Iteration den X-Zeiger um eins nach rechts bewegen,
dass dann links davon jeder Baum nur einmal vorkommt,
dass also insbesondere der Baum,
den sie in die Menge aufgenommen haben,
der links von der Position X steht eben
nur einmal vorkommt
Und die Komplexität, naja, wir haben zwei,
wir hatten ursprünglich ja zwei Bindungen, ja alle Bäume,
die haben also N-1 und N-2 viele Elemente gehabt. Entschuldigung,
wir haben zwei Binimiale Heeps gehabt,
die haben insgesamt N-1 und N-2 viele Elemente gehabt,
das waren H1 und H2,
der neue Heep hat jetzt also N-gleich N-1-Plus-Enzweifiger-Elemente, ja,
und dann wissen sie schon, wie lang unsere Liste ursprünglich ist,
weil wir haben ja gesehen, dass der Binumiale Heep,
Lugorithmus war zwei N von N viele plus eins,
viele Wurzeln haben kann oder Bäume haben kann.
Ja, das können wir addieren und wir können
das N1 und N2 durch N abschätzen.
Dann hätten wir also zweimal Logorithmus zur Basis 2 von N plus 2,
viele Wurzeln in dieser langen Liste, die wir hier erzeugt haben.
Und dann gucken wir uns unsere die Merch-Operation an,
die macht das dann natürlich in Login-Zeit.
Also die Merch-Operation selber war natürlich lineare Zeit,
aber wir haben eben nur Login viele Knoten,
die wir in die neue Liste übernehmen müssen.
Okay, so, dann kommt die Wild-Schleife und jeder Durchlauf
der Wildschleife geht in konstanter Zeit
Können wir nochmal reingucken. Also hier sind nur IF-Bedingungen drin,
keine weiteren Schleifen, keine rekrusiven Aufrufe.
Das ist eine Funktion, die konstante Zeit benötigt.
Und die Anzahl der Schleifendurchläufe können wir auch ganz leicht abschätzen,
weil entweder es verschwindet ein Baum,
weil wir zwei Bäume zusammenbauen oder wir gehen mit der Ex-Position eins nach rechts.
Und dann ist es natürlich klar, dass diese Wildschleife
genauso häufig durchlaufen wird, wie viele Bäume haben.
Mehr geht nicht. Okay, so, das heißt,
in jedem Durchlauf wird entweder eine Wurzel entfernt oder der X-Zeiger,
also der die Position markiert,
geht um eins nach rechts und deswegen haben wir Log-End-Zeit für
das Union
Okay, damit haben wir in Union,
aber wir haben ja das ganze andere noch nicht.
Wir haben noch keinen Insert und kein Delete und keinen Extract-Min insbesondere,
kein Decrease, das müssen wir alles noch bauen.
Aber das ist alles jetzt relativ einfach. Wir machen das jetzt
genau so, wie wir das auch beim Hieb gemacht haben.
Wir benutzen einfach diese Union-Operation und wenden die einfach immer an.
Wenn wir zum Beispiel ein Insert machen wollen,
dann erzeugen wir einen ein elementigen Penomial Heep,
das ist ja nur ein Knoten,
da schreiben wir uns an einen Wert rein und machen einen Union.
Hängt den also vorne in die Liste rein hier und machen dann den Durchlauf,
dann sind wir fertig. Wenn wir einen Extract-Min machen,
dann entnehmen wir die Wurzeln mit minimalem Element aus der Wurzelliste.
Und dann erzeugen wir einen neuen Hieb mit den Kindern.
Und dann führen wir wieder ein Union aus. Also man hat angenommen,
mal angenommen, das wäre das Minimum hier rechts,
dann kann man sich das vielleicht,
weil das ein bisschen größerer Baum ist, wenn wir den wegnehmen,
dann hätten wir hier auf der nächsten Ebene wieder ein binomialen Hieb.
Der ist dann von der Reihenfolge her vielleicht andersrum,
aber den muss man dann nur umdrehen.
Wir haben also einen binomialen Baum, der größte Null,
einen der Größe Eis, der größte 2,
der größte 3. Also diese Ebene hier unter dem Knoten,
ist selber wieder ein vinumialer Hieb.
Das heißt, wenn wir den Quoten entfernen,
haben wir zwei vinumiale Hiebs. Den einen,
das ist der Rest von dem Alten und der zweite vinumiale Hieb,
das sind die Kinder von dem Knoten, den wir entfernt haben.
Und dann haben wir wieder zwei Hiebs,
die können wir wieder mit einer, mit einer, wann?
Mit einer Union-Operation zusammenfügen. Das heißt, wir entnehmen also
die Wurzel mit dem minimalen Element X aus der Wurzelliste,
erzeugen einen neuen Hieb mit allen Kindern von X
und führen Union aus
Hier ist also der Code von Insert, der ist also trivial.
Wir bauen also ein Mini-Hieb mit einem Element X und das
schreiben wir dann hier entsprechend rein und dann machen wir Union.
Hier sehen Sie das Extract Min,
das Extract Min entnimmt quasi das Minimum aus der Funktion heraus.
Also bestimmt das Minimum mit Benormial Heat Minimum,
die haben wir ja schon, wir sehen die Funktion.
Dann wird der Baum entfernt, wenn das der Wurzelknoten ist,
also wenn das der erste Knoten der Liste ist,
müssen wir
den Headseiger manipulieren
Anderenfalls müssen wir den Knoten hier nochmal suchen.
Das machen wir in dieser Wild-Schleife. Und wenn wir den
gefunden haben, werfen die den aus der Liste raus.
Damit extrahieren wir einen Baum aus der Liste aller Bäume,
nämlich der, der den minimalen Wert in der Wurze stehen hat.
Wir bauen einen neuen Minimum-Benomial-Hieb. Wir gehen durch die Liste der Kinder
. Und solange diese Liste der Kinder nicht leer ist,
fügen wir die vorne in die neue Liste ein.
Und dadurch machen wir die Umsortierung von groß nach klein,
haben dann einen neuen Hieb konstruiert mit allen Kindern und machen wieder
unsere Union-Operation und haben dann einen neuen Hieb, wo das Minimum extrahiert ist.
Laufzeit von Xtract Min ist dann lugorithmisch, weil wir wissen,
dass so ein Knoten maximal Log-in viele Kinder hat. Das heißt,
wir müssen jetzt, wir haben die ursprüngliche Wurzelliste mit Log-in,
vielen Wurzeln oder Bäumen und dann haben wir eine neue Liste,
die hat auch ein Log-in, viele und die müssen wir mergen,
macht Log-in Laufzeit für das Extract Min.
Hier ist ein Beispiel für ein X-tract Min,
jetzt ist der hier mal das Minimum,
das ist ein bisschen anschaulicher, der Baum wird aus der Liste entfernt,
wir haben also nur noch diese beiden Bäume hier,
der hier erzeugt jetzt einen neuen einen neuen Heep,
das ist dieser jetzt.
Das sind nur die Kinder eigentlich des Ursprünglichen.
Und jetzt kommt die Merch-Operation auf diesen beiden Listen,
erzeugt dann also hier den neuen Normial Heep.
So, die Crease, Key und Delete haben wir noch.
Die kann ich ihm nächstes Mal auch noch zeigen,
aber dass die Quease Key ist jetzt ganz einfach,
wir müssen das Objekt ja nur innerhalb des Baums,
in dem es ist, nach oben bewegen.
Und beim Delete machen wir nichts weiter,
als dass die Quease Key zu kombinieren,
indem wir das Objekt erstmal zum Minimum machen und dann
Damit haben wir alle Operationen, sage ich Ihnen beim nächsten Mal nochmal,
für den Pynomiel-Hieb realisiert und haben die Log-in-Laufzeit erreicht.
Für das Union. Okay, so, das war Schritt 1.
Und Schritt 1 haben wir nur gemacht eigentlich,
um dann als nächstes den zweiten Schritt zu machen,
um dann von dem Pynomian-Hieb dann zum Firbonatschechieb zu gehen.
Aber das machen wir dann nächste Woche.
Das war es dann für heute