So, guten Morgen. Willkommen bei Algorithmen und Datenstrukturen.
Können Sie mich hören? Ist laut genug?
Laut genug. Okay. Prima. Okay,
wir beschäftigen uns heute und in den nächsten, ja,
mindestens noch zwei Veranstaltungen mit Sortieren.
Sortieren hört sich zunächst nach einer Fragestellung an, die,
ja, relativ einfach klingt.
Sie ist offensichtlich sehr elementar und sie ist auch wichtig in der Informatik.
Es gibt so Statements, die man immer wieder hört,
95 Prozent der Rechenzeit von Computer gehen, sortieren. Ja,
ich weiß nicht, ob es 95 oder 90 Prozent sind,
aber es ist offensichtlich eine ganze Menge an Zeit,
die wir damit verbringen, Daten zu sortieren und in Ordnung zu bringen.
Insofern haben Sortierverfahren eine wichtige Bedeutung in der Informatik.
Es gibt aber einen anderen Grund, warum man Sortier oder einen weiteren Grund,
warum man Sortierverfahren in der Algorithmenveranstaltung so intensiv betrachtet.
Sie sind ein hervorragendes Lernbeispiel für Algorithmenentwurf.
Es gibt ganz viele verschiedene Konzepte und sie sind auch ein
sehr gutes Beispiel dafür, wie man Algorithmen analysiert und bezüglich
der Laufzeit
Genauer betrachtet. Und deswegen guckt man die sich hier mal
so intensiv an im Algorithmen und Datenstrukturen. Okay,
so, wir werden heute damit beginnen mit ganz elementaren Sortierverfahren.
So ganz simple Geschichten, also die werden das schon, werden es sehen.
Das sind so Verfahren, die so jeder, naja,
ab Kindesalter wahrscheinlich auch benutzt, um Sachen zu sortieren.
Wir gehen dann in den Heepsort und das möchte ich gerne bis heute schaffen.
Ich vermute, dass wir ungefähr bis hierhin kommen.
Wir werden uns also die entsprechenden Datenstrukturen anschauen unter
dem Heepsort und das entsprechende Sortierverfahren betrachten.
Hier schon mal etwas genauere Laufzeitanalysen machen
Und dann in der nächsten Vorlesung, die widmen wir dem Quicksort.
Und hier schon mal nur als Warnung für diejenigen,
die sagen, Quicksort kenne ich schon.
In dieser Vorlesung werden wir wahrscheinlich fünf oder zehn Minuten brauchen,
um den Algorithmus vorzustellen. Und den größeren Teil,
um den zu analysieren. Also selbst wenn sie sagen,
Ulnargorithmus kenne ich schon, heißt das noch nicht,
dass es auf alle Fälle langweilig wird da.
Ja, da werden sie bestimmt auch noch neue Sachen sehen.
So, und dann meint man ja, okay,
das ist ja, Quicksort ist ja das beste Sortierverfahren,
was es gibt.
Das müssen wir übrigens noch sehen, ob das wirklich so ist.
Und da hört das normalerweise auf.
Das ist bei uns aber nicht so.
Wir werden dann schauen
Gibt es eine untere Schranke? Kann man das noch verbessern?
Oder ist irgendwo der Punkt erreicht, wo wir sagen,
das ist jetzt das optimale Sortierverfahren? Die werden wir finden,
seine untere Schranke, nur um dann im nächsten Kapitel wieder zu zeigen, dass sie doch nicht gilt.
Wir werden dann andere Verfahren sehen, die auch sortieren können,
die aber spezielle Annahmen brauchen und wenn die gelten,
brechen die die untere Schranke und werden nochmal schneller und schlagen
auch Verfahren wie Quicksort noch um die Größe 9. Okay,
und am Ende gucken wir uns dann so verwandte Probleme an,
Auswahlprobleme,
wenn es darum geht,
also aus Mengen,
Objekte entsprechend zu selektieren
Gut, so der Plan. Und ja, los geht's. Okay,
in der Informatik sollte man immer anfangen mit einer möglichst präzisen Problembeschreibung.
Unsere Problembeschreibung klingt für Sortieren wie folgt. Wir haben also
eine Menge von Datensätzen, SI und die haben Schlüsse,
KI oder im Folgenden werden wir die auch immer Ki nennen.
Und wir haben eine Ordnungsrelation, also ein Begriff aus
der Mathematik hier, die beschreiben wir mit kleiner gleich.
Dann müssen sie auch intuitiv sofort, was hier gemeint ist.
Und was wir suchen, ist eine Permutation,
wieder ein Begriff aus der Mathematik,
also eine andere Anordnung
Der Zahlen von 1 bis N, dieser Indizes der Objekte,
sodass für unsere Schlüssel gilt, dass das Element an vorderster Stelle kleiner,
gleich dem Schlüsselelement an der zweiten Stelle kleiner gleich dem Schlüsselelement
an der dritten Stelle bis zur Entenstelle ist.
Also Resultat ist eben diese neue Ordnung gegeben oder beschrieben durch
eine Permutation. Permutation heißt dann also nur,
an welcher Stelle in dem sortierten Array soll das Element stehen,
was vorher an der Position I steht. So, der Begriff,
der hier vielleicht noch am schillerndsten ist, ist diese Ordnungsrelation,
die sollten sie eigentlich in der Mathematik schon gesehen haben,
also in der Mathematik 1 für Informatiker,
hier einfach nur noch mal zusammengefasst.
Wir reden von einer partiellen Ordnung, also eine Ordnung ist eine Relation zwischen zwei Objekten.
A und B und wir reden von einer partiellen Ordnung,
wenn sie drei Eigenschaften erfüllt sind, muss reflexiv sein.
Das bedeutet, es gilt, die Relation gilt für das Element,
wenn man es mit sich selber in Beziehung setzt, also A-R-A.
Sie muss antisemetrisch sein, das heißt, wenn A-R-B und BRA gilt,
also die Relation ist gültig für beide Richtungen,
dann muss das auch das gleiche Element sein, A gleich B gelten.
Sie muss transitiv sein. Transitiv bedeutet, wenn ARB gilt
und BRC gilt, dann folgt daraus auch ARC.
Das ist erst mal eine partielle Ordnung und eine partielle Ordnung
reicht für uns noch nicht zum Sortieren,
weil eine partielle Ordnung sichert noch nicht,
dass wir auch jedes Element mit jedem vergleichen können.
Sie sagt nur, wenn wir die Elemente vergleichen können,
dann muss dies gelten. Um das zu erreichen,
brauchen wir eine totale Ordnung und die besagt eben zu den
Eigenschaften der partiellen Ordnung, dass für alle Elemente A gilt,
dass entweder ARB oder BRA gilt.
Also jedes Paar von Elementen können wir zueinander in Beziehung setzen
und sagen, dass das eine kleiner gleich dem anderen ist
oder umgekehrt
Das sind alle Eigenschaften, die wir brauchen,
das sind die Anforderungen, die wir haben,
an die Ordnungsrelation, damit wir ein Sortierverfahren anwenden können.
Das hört sich jetzt noch so nach theoretischem Unterbau an,
den man in der Praxis gar nicht braucht.
Das ist aber nicht so. Also es ist in der Praxis schon wichtig,
weil wir haben nicht immer die Situation,
dass wir noch so ganz einfachen Dingen wie irgendeinem Intager oder so sortieren,
sondern es gibt häufig schon sehr komplexe Anforderungen,
nach welchen Eigenschaften Sachen in eine Ordnung gebracht werden sollen.
Und dann muss man eben aus diesem vielleicht von irgendeinem Anwender
extuell beschriebenen Relationen eine mathematische Relation bauen,
die mir dann in das Sortierverfahren reingeben kann.
Und da muss man genau diese Eigenschaften überprüfen. Ja,
wenn wir uns alle mal betrachten und würden fragen,
okay, wie können wir uns hier sortieren?
Da gibt es ganz viele Möglichkeiten, was weiß ich, nach Körpergröße oder irgendwas.
Oder zum Beispiel könnte man sagen, wir nehmen die Relation,
ist Nachfahre von. Und dann sehen Sie schon ein Beispiel,
dass das eine Ordnung ist,
also wenn sie zum Beispiel in einem Raum sind,
zu Weihnachten mit ihren Eltern und Großeltern und Geschwistern und Enkelkindern,
keine Ahnung, dann wenn sie diese Relation verwenden, um sie,
um eine Ordnung auf den Personen im Raum zu definieren und einzuwenden.
Aber die ist offensichtlich nicht total, weil es gibt ganz viele Menschen,
die sie ihr nicht in Beziehungen zueinandersetzen können.
Selbst innerhalb der Familie, Geschwister, für Geschwister gilt ja weder,
dass der eine Nachfahrer vom anderen ist oder umgekehrt.
Also es ist eine Relation, aber sie ist partiell.
Sie ist übrigens auch nicht reflexiv, was dann aber hier
von Untergeordneter Bedeutung ist. So, wir könnten sowas machen,
wie hat größere Ausweisnummer und unter der Annahme,
dass jeder ein Personal Ausweis hat, den sie vergleichen wollen,
hat auch jeder eine Ausweisnummer.
Und dann wäre das eine totale Ordnung. Sie könnten von
allen Personen Ausweisnummern angucken und dann könnten sie sagen,
okay, der hat die größere Ausweisnummer als die andere Person.
Hier ist aber trotzdem eine Eigenschaft verletzt und zwar ist die Art und Weise,
wie das hier formuliert ist, als Relation, eine Formulierung,
die ist nicht reflexiv.
Wenn man sagt, hat es eine größere Ausweisnummer,
kann ich das nicht, kann ich nicht ein Objekt oder eine Person mit sich selber vergleichen.
Das scheint als eine Eigenschaft,
die das für das Sortieren jetzt gar nicht so wichtig ist,
in der Tat ist es auch so,
weil wir hier wiederum eine andere Eigenschaft haben.
Die Ausweisnummern sind eindeutig. Ja, es werden keine
zwei Personen auftreten, die die gleiche Nummer haben.
Insofern wäre das mit der Reflexivität hier gar nicht so wichtig.
Ja, also die Reflexivität ist die Eigenschaft,
von der man am ehesten noch sagen kann,
dass man sie gegebenenfalls noch vermeiden kann, wenn sie nicht gilt.
In der Regel kann man die Relation auch umformulieren,
sodass die Reflexivität gilt, wenn sie also hier nur sagen würden,
hat keine kleinere Ausweisnummer.
Dann haben sie den Gleichfall mit einbezogen und dann hätten sie eine totale Ordnung.
Könnten darauf vergleichen. So, das zur Modellierung von Ordnungsrelation
, das werden wir im folgenden Jetzt nicht weiter betrachten.
Wir gehen jetzt davon aus, wir haben Schlüssel und
wir haben eine Vergleichsfunktion und können diese Schlüssel miteinander vergleichen.
Die Vergleichsfunktion ist eine totale Ordnung. Wir können also
alle Elemente vergleicht, sie erfüllt genau diese Eigenschaft.
Okay, was wollen wir sortieren? Wir wollen irgendwelche Objekte sortieren,
Objekte können groß sein, viele Informationen haben und irgendwo in
diesem Objekt muss sich ein Schlüssel verbergen, nachdem wir sortieren wollen.
Das ist dieser Key. So,
wir haben also als Eingabe jetzt einen Array von 1 bis Großen,
Großen ist die Anzahl der Elemente, die wir haben,
wie wir sortieren wollen und die sind alle irgendwie so aus,
irgendwie ein größeres Objekt und irgendwo da drin ein Schlüssel
So, was wir wollen, ist, wir wollen dieses RAR-Umsort hier,
die Elemente, in dem RAR anders platzieren,
sodass sie aufsteigend sortiert sind nach dem Schlüssel.
Das heißt, was gelten soll, ist, dass AI.key,
kleinergleich AI plus 1.key ist, für alle eins kleiner,
gleich i, kleiner als N.
Das ist also die Anforderung, die wir erfüllen wollen.
Gut, so, wir werden im folgenden Algorithmen sehen,
mit denen wir sowas tun können, bevor wir das machen,
müssen wir mal ein bisschen überlegen,
wie wollen wir diese Algorithmen überhaupt analysieren?
Was ist überhaupt wichtig für so
ein Sortierverfahren
Wir wissen schon, Laufzeitanalyse machen wir Worst Case im O-Kalkül
und überlegen uns, wie viele Schritte da sind.
Das ist auch im Prinzip richtig, so werden wir das auch tun.
Aber trotzdem lohnt es sich bei Sortierverfahren nochmal ein bisschen genauer hinzuschauen,
weil es gibt Operationen, die vielleicht doch sehr unterschiedlich lang dauern können.
Und die typischen drei Operationen, die wir haben, in Sortierverfahren,
neben irgendwelchen Vorschleifen und Bedingungen und so, sind Zugriffsoperationen.
Also sie wollen das Element IMV lesen. Sie wollen wissen,
was ist denn jetzt der Schlüssel dieses Elements?
Vergleichsoperation. Sie haben also jetzt die zwei Schlüssel gelesen
und wollen gucken, welches ist der kleinere von den beiden
Und schließlich haben sie so eine Datenaustauschoperation. Sie wollen in dem Array das,
was an der Position I steht, an die Position J kopieren.
Wir beiden Sachen umkopieren. Alle diese drei Operationen haben im
Ulkalkül konstante Zeit. Das sind alles so Sachen,
wo sie davon ausgehen, das geht in konstanter Zeit.
Ist wahrscheinlich auch korrekt. Und trotzdem sind das die Operationen,
die in der Praxis sehr von der Laufzeit her sehr stark variieren können.
So eine Zugriffsoperation kann, wenn sie Millionen von Daten haben,
die auf irgendwelchen Festplatten liegen, enorm zeitaufwendig sein.
Im Gegensatz zu vielleicht beim Sortieren von nur ein paar hundert
Zahlen in einem die im Hauptspeicher vorliegen.
Eine Vergleichsoperation kann ganz schnell gehen, wenn sie zwei Intager-Werte haben,
wollen nur wissen, was ist kleiner, was ist größer,
auch so ein String-Vergleich geht ganz schnell.
Aber vielleicht haben sie eine Anwendung, wo die Vergleichsfunktion ein hochkomplexer Algorithmus ist,
der vielleicht selber exponentielle Laufzeit hat, um zu entscheiden,
dass das eine Objekt kleiner oder größer ist als das andere.
Kann eben sein. Und das dritte sind die Datenaustauschoperationen. Auch hier ist es so,
dass die Objekte vielleicht sehr groß sind und es sehr aufwendig ist,
das eine Objekt und das andere Objekt im Platz zu zu vertauschen.
Also auch hier ist wieder die Frage,
wo liegen diese Daten, liegen die auf Sekundärspeichern,
auf Festplatten, dann kann es sehr ungünstig sein,
weil sie irgendwas von der Platte runterkopieren müssen, irgendwo anders hinkopieren müssen.
Vielleicht haben sie Netzwerke, die Daten sind verteilt in einem Netzwerk,
Datenkommunikation kann teuer sein. Das sind alles so Argumente, die da eine Rolle spielen. Das heißt,
wenn man ein Sortierverfahren analysiert, machen wir es so wie immer,
O-Kalkül, wir gucken, wie lange gedauert das,
aber für die Praxis, wenn sie es wirklich machen müssen,
sollten sie immer ein Auge darauf haben, wie lange dauern diese Dinge,
weil das kann die Relation oder die Qualitätsordnung, die sie haben für Sortierverfahren, massiv durcheinander schieben.
Also wir werden das so ein bisschen im Auge behalten, wenn wir jetzt die Sortierverfahren anschauen.
Was man bezüglich der letzten Sache hier noch sagen muss,
also bei den Datenaustauschoperationen,
ist, dass wir natürlich immer die Möglichkeit haben,
also wenn das sehr teuer ist,
haben wir natürlich immer die Möglichkeit, die Schlüssel zu extrahieren,
zunächst nur die Schlüssel zu sortieren und dann sozusagen im Anschluss,
wenn wir die Permutation kennen, die Daten an die richtige Stelle zu schieben.
Also hier kann man sozusagen immer einen Weg finden,
sehr teure Datenaustauschoperation als sie nicht so stark ins Gewicht fallen.
Aber trotzdem, wie gesagt, das sind die drei Dinge,
auf die man achten sollte.
Neben der reinen Laufzeit im Okalkül. Okay, so,
wir beginnen, wir beginnen mit drei ganz elementaren Sortierverfahren.
Und das sind Sortierverfahren, die im Wesentlichen so sind,
wie man vorgeht, wenn man selber Objekte vielleicht sortieren würde.
Als Mensch, Karten sortieren oder irgendwas. Das erste ist
das Sortieren durch Auswahl, ein sogenannter Selection Sword.
Was wir machen, hier steht der Algorithmus
textuell beschrieben
Wir iterieren von 1 bis N minus 1. Also wie gesagt,
Daten sind von 1 bis Groß N. Und im Iten-Durchlauf dieser Schleife
schreiben wir das kleinste Element aus dem Bereich von I bis N.
An die Position AI. Also was wir machen,
ist, wir sagen, wir gucken auf das Array.
Wir sagen, was ist denn hier das Kleinste?
Das schreiben wir dann an den Anfang.
Dann gucken wir in den Rest von 2 bis Enden,
sagen, was ist denn jetzt das Kleinste?
Und das schreiben wir an die Position 2.
Und dann gucken wir in den Bereich von 3 bis N.
Was ist denn jetzt das Kleinste? Schreiben wir an die Position
3. Und so weiter. Und auf diese Art und Weise
sortieren wir offensichtlich das Array. Hier ist der Algorithmus im Pseudo-Code,
der das tut, ja, ist also Selection-Sort,
heißt das Array
Die Vorschleife hier, die geht von eins bis zur Länge des Arrays minus eins,
das ist so süß, N hier, a.lenks, minus eins.
Der erste Schritt ist, wir müssen jetzt das kleinste Element finden,
aus dem Bereich von iPlus 1 bis zum Ende des Arrays.
Ja, das heißt,
oder aus dem Bereich von i bis zum Ende des Arrays,
das machen wir, indem wir eine Variable Min haben,
die setzen wir auf i, gehen jetzt davon aus,
dass i das kleinste Element ist.
Und dann haben wir diese Vorschleife hier,
die läuft von iPlus 1 bis zum Ende des Arrays,
ja, und immer den Van A.j.
kleiner ist als a.min,
dann setzen wir
das Min auf
Es einfach mit dem Index Minn, wo das kleinste Element steht.
Und am Ende vertauschen wir dann die Elemente an der Stelle
Minn und an der Stelle I. Also da,
wo Aminn steht, schreiben wir das AI hin und da,
wo das AI steht, schreiben wir das Amin hin.
Was tauschen die beiden? Hier ist die Swap-Operation,
also Swap-Operation zwischen zwei Objekten AW,
also Hilfsvariabilothek, kriegt den Wert A,
A kriegt den Wert vom B und B kriegt den Wert
vom Tee.
Okay,
so sieht es aus
An dieser Stelle muss ich einen wichtigen Hinweis geben. Sie werden sich in der Übung,
die den Zettel schon angeguckt haben, mit diesem Algorithmus beschäftigen.
Und wenn Sie jetzt parallel dazu sich die Folien angucken,
die da schon unter der Vorlesung liegen, dann beachten Sie bitte,
dass da ein Taipo in dem Algorithmus ist,
den habe ich heute Morgen gesehen,
und zwar ist dieses SWOP falsch eingerückt.
Ja, ich werde die Folien nochmal austauschen, aber nur,
weil Sie jetzt wahrscheinlich ganz vielleicht schon heute Nachmittag an dem Zettel irgendwie sitzen,
dass SWOP ist hier zu tief eingerückt.
Ja, okay. Okay, aber wenn Sie den Algorithmus
verstanden haben, werden Sie den Fehler auch sofort sehen.
Also, dass man hier nicht in der Vorschleife ständig
diese SWOP-Operation macht, ist,
glaube ich,
Ja. Nein. In diesem, wir verwenden momentan an Array von 1
bis N. Also in C fangen Arrays bei Null an,
ja, aber für unsere Algorithmen ist es momentan einfacher,
denen können wir auch problemlos beschreiben von 0 bis N,
würde er auch gehen oder von 0 bis N minus 1,
aber wir werden spätestens beim Heep-Sort gibt es gute Gründe,
dass man das Nulltelement dann nicht berücksichtigt,
weil einfach die Algorithmen einfacher zu beschreiben sind.
Also insofern momentan immer von 1 bis hin
Okay, so, hier ist auch ein Beispiel.
Wahrscheinlich brauchen sie das nicht mehr, der Algorithmus ist so einfach.
Also wir haben hier acht Zahlen, also hier ist der Index von eins bis acht,
hier sind die acht Zahlen, die wir sortieren wollen.
Am Anfang steht das I hier auf der ersten Position drei
und wir laufen dann hier über die Position von hier rüber,
also von der Position I bis zum Ende des Arrays,
bestimmen mit dem Index min, die Position des minimalen Elements,
also des Elements mit kleinstem Schlüssel und vertauschen dann diese beiden Elemente.
Danach steht die einzige vorne, das I rutscht eine Position weiter,
wir laufen dann von dieser Position bis zum Ende des Arrays,
wir finden den Index mit dem kleinsten Element und vertauschen dann diese beiden Elemente.
Und das machen wir dann so weiter.
Und irgendwann am Ende ist das Array sortiert. Okay,
so, erste Frage ist natürlich, wie lange dauert das?
Also was für eine Laufzeit haben wir für dieses Verfahren?
Und das sehen sie mittlerweile schon,
haben sie schon wahrscheinlich ein bisschen einen geübten Blick dafür.
Wir haben also hier zwei ineinander geschachtelte Vorschleifen.
Diese hier wird N mal durchlaufen. Diese hier wird von
der Position I bis N durchlaufen. Und diese Art von
Vorschleifen haben wir schon schon mal gesehen. Ja,
also im Worst Case sind das N-Durchläufe,
sodass wir auf eine Laufzeit von N-Quadrat kommen,
also von Tetter von, oder ist mal von O, von N-Quadrat.
Wir können aber auch zeigen, es ist Tetter,
also wenn sie die Summe hier tatsächlich mal ausrechnen würden,
kommen sie auch tatsächlich auf ein Quadrat.
Also das heißt, dieses Verfahren hat skaliert mit N-Quadrat.
In jedem Fall, unabhängig davon, wie die Folge eigentlich aussieht.
Es wird immer N-Quadrat kosten. Und ja, sagen sie,
okay, das werden wir wahrscheinlich besser können.
Klar werden wir das besser können und trotzdem hat dieser Algorithmus
eine Eigenschaft, die ihn manchmal zur Methode der Wahl macht
Das ist an einer ganz speziellen Stelle ein super Verfahren.
Und das ist, sehen Sie hier,
dieses Verfahren ist optimal bezüglich der Anzahl der Datenaustauschoperationen.
Wir haben sie nochmal hier zurückgucken hier. Wenn Sie also
einen sortieren müssen und Sie wissen, Ihre Datenaustauschoperation ist extrem teuer,
dann denken Sie über dieses Verfahren nach,
weil die Datenaustauschoperation ist nicht nur in O von N.
Sie ist sogar wirklich optimal. Sie bestimmen quasi für
jedes Element als allererstes die Position,
wo das Element hingehört und dann kopieren Sie das genauer an
diese Stellung,
werden Sie
es nie wieder bewegen
Das ist die minimale Anzahl von Datenaustauschoperationen,
die sie überhaupt haben können in den Sortierverfahren.
So viel müssen sie machen, ja,
um überhaupt die Objekte in die richtige Reihenfolge zu bringen.
Ja, wenn sie sich noch umentscheiden und in ihrem Leben
Steinmetz werden und dann ihren Steinbestand nach Größe sortieren,
machen sie es mit Selection-Saison-Sort.
Machen sie es nicht mit Quick-Sort. Ja,
wir müssen nämlich ständig die Steine von links nach rechts schieben.
Okay, also, das ist Selection-Sort und die nächste Variante,
die sehr ähnlich erstmal klingt,
es sortieren durch Einfügen,
also ein Insertion-Sort
In Sessions hat funktioniert wie folgt. Wir haben wieder
eine Schleife, wir gucken uns alle Elemente an,
die läuft jetzt von zwei bis in.
Und im Jottendurchlauf machen wir folgendes. Wir fügen das JT-Element
in die sortierte Folge A1 bis AJ minus 1.1.
Also wir gehen davon aus, wir sind an irgendeiner Position
angekommen und der Bereich bis zu diesem Element ist schon sortiert,
war beim Selectionshot auch so.
Und jetzt nehmen wir uns das nächste Element und gucken,
wo gehört denn das hier rein in diese Folge,
die schon sortiert ist und fügen das an die entsprechend richtige Position ein.
Dabei verwenden wir
einen Trick in Arrays
Und zwar machen wir einfach Folgendes. Das haben wir
schon bei den Datenstrukturen, bei den Listen gesehen.
Wenn wir also in einem Array an irgendeiner Position sind und wir müssen jetzt,
das ist jetzt diese Position J und wir müssen jetzt dieses Element hier irgendwo einfügen,
dann haben wir ja ein Problem, wir müssen ja Platz schaffen,
müssen ja eine Lücke machen in dem Array,
damit wir das Element auch hier einfügen können.
Und das machen wir so,
wie wir das bei der Listenrealisierung gesehen haben,
dass wir einfach von hier,
nach hier bis zu dieser Position I laufen,
wo wir das einfügen wollen und wir schieben einfach die Elemente
immer um eine Position nach links
So machen wir das. Und wenn wir das machen,
dann haben wir danach hier die Lücke,
um dann das J-T-Element dann hier auch reinzuschieben.
Das ist die Art und Weise, wie der Algorithmus funktioniert.
Okay, Pseudo-Code, Insertion-Sort. Hier läuft diese Schleife,
die geht jetzt bei zwei los und bis zum Ende des Race.
Also die braucht das erste Element nicht betrachten. Das ist,
wenn wir ein bisschen darüber nachdenken, ganz logisch,
dass sie das nicht machen muss,
denn wir gehen immer davon aus, das wäre unsere Schleifenin-Variante,
dass die Folge von eins bis J minus eins bereits sortiert ist.
Wenn sie jetzt mal hier das Einsetzen, dann sehen Sie,
also wenn ich nur eine einelementige Folge betrachte,
dann ist die natürlich sortiert, ist ja nur ein Element drin.
Da muss ich also nichts machen. Also das erste Element,
für das ich mir überlegen muss,
an welche Position gehört das denn, ist das zweite.
Das muss gegebenenfalls vor das erste eingefügt werden. Okay,
also ich merke mir in der Variablen Key,
das JT-Element, jetzt muss ich die Stelle suchen,
wo ich dieses Element einfügen muss.
Das mache ich in dieser Schleife,
die Position I ist erstmal J minus 1,
also die Position vor der Position J,
von dem Element,
was wir umsortieren wollen
Und dann haben wir diese Wild-Schleife, die läuft so lange,
wie das I größer ist als null.
Das sichert zu, dass wir nicht über den Anfang des Areas hinauslaufen.
Und wir laufen so lange wie das AI größer ist,
als unser Schlüsselelement hier, AJ.
Dann wissen wir, okay, die sind alle größer,
die müssen alle rechts von AJ stehen.
Und hier verschieben wir das Element, also AI plus eins
wird zu AI. Das ist diese Operation hier,
wo die Elemente immer um eine Position verschieben.
Und dann dekrementieren wir den Index, gehen also eine Position
weiter nach links, so machen wir immer so weiter.
Und am Ende fügen wir an der Position iPlus 1,
unser Schlüsselelement wieder ein
Okay, das ist Insertion Sword und sie sehen auch hier,
haben sie im Prinzip schon eine Laufzeit im Worst Case von N-Quadrat.
Ja, zählt von allen gleich ein Täter von Endquadrat.
Aber hier sind schon ein paar Sachen anders als beim Selection Sword.
Man könnte schon sagen, okay, das sind zwei Algorithmen,
ein Quadrat, tut sich nicht viel, ist eigentlich egal.
Und trotzdem unterscheiden auch die sich bezüglich der Anwendbarkeit enorm.
Also der in dieser Insertion Sword hat nicht mehr
den Vorteil,
der minimalen Datenaustauschoperation
Sie sehen, hier schieben sie die ganze Zeit die Objekte immer weiter an andere Positionen.
Das heißt, der hat im schlimmsten Fall Endquadrat viele Datenaustauschoperationen.
Wenn Datenaustausch teuer ist, ist es keine gute Wahl.
Im Gegensatz zum Selection Sword. Auf der anderen Seite kann man
sich auch fragen, für welche Folgen gilt das Endquadrat und für welche nicht.
Das Selection Sword hatte die Eigenschaft, dass eigentlich unabhängig von der Folge,
von der eingegebenen Folge sie immer ein Quadratzeit brauchen,
also was was proportionales zu enkern hat.
Beim Insertion Sword ist das nicht so,
wenn beim Insertion Sword die Folge quasi schon sortiert ist,
Ja, dann wird diese Wildschleife immer nach einem Durchlauf sofort anhalten und der Laufzeit hätte nur Endschritte.
Also, wenn sie wissen, sie wollen Daten sortieren,
die fast sortiert sind und nur so ein paar Stellen sind falsch,
ist dieser Algorithmus viel besser als der Selection Sword.
Ja, sie sehen also, dass unabhängig oder abhängig davon,
wie ihr Problem genau aussieht beim Sortieren,
die Verfahren sehr unterschiedlich sein können in der Qualität.
So, jetzt kommen wir zu einem Verfahren,
was hier drinsteht, weil es so bekannt ist,
dem ich aber ehrlich gesagt kaum irgendeine positive Eigenschaft abgewinnen kann,
außer dass es vielleicht ganz nett ist,
wie er aussieht und wenn man das dann irgendwie animiert.
Ansonsten ist da, wie gesagt, nichts Praktisches dran.
Das ist der Bubble Sword
Der steht hier einfach für Vollständigkeit halber. Also ein Bubble-Sort funktioniert so
. Wir haben eine Schleife, wir iterieren so lange,
bis wir feststellen, dass die Folge sortiert ist.
Und diese Feststellung machen wir, indem wir immer paarweise zwei Elemente vergleichen.
Und wenn die in der falschen Ordnung sind,
vertauschen wir die in der Reihenfolge.
Und so führt es nach und nach dazu,
dass die Folge sortiert wird. Und der heißt Bubble-Sort,
weil die kleinen Elemente mehr oder weniger wie Luftbläschen
dann nach oben steigen in der Folge
Ja? Okay. So, hier ist der Bubble-Sort.
Das ist die einfachste Art und Weise, ihn aufzuschreiben.
Wir haben also die Schleife, die hier N-Mahl durchlaufen wird.
Und die innere Stleife hier,
die läuft von N runter auf iPlus 1. Und immer dann,
wenn AJ kleiner ist als AJ, minus 1,
vertauschen wir diese beiden Elemente.
Ja, es wäre schon alles. Und damit kriegen sie eine Sortierung hin.
Trotzdem, also muss man bei dem Algorithmus an ein,
zwei Stellen mal nachdenken, ob der wirklich funktioniert.
Also ganz trivial ist er dann nämlich doch nicht.
Die eine Frage ist schon, Okay, also wieso reicht es
an dieser Stelle, bis i plus 1 zu laufen?
Wieso muss ich nicht bis zur Position 1 zurücklaufen?
Das ist ja scheinbar schon ein Geschwindigkeitsvorteil. Also ich muss
in jedem, in die inneren Schleifen werden immer kürzer.
Und um das zu verstehen, muss man vielleicht mal ein Beispiel angucken.
Da sieht man nämlich, dass wir garantieren können,
dass nach dem Iden-Durchlauf das eat kleinste Element an der richtigen Position gebabbelt ist.
Also nach der ersten Iteration wissen Sie,
dass das Minimum garantiert bis ganz nach vorne gerutscht ist.
Ja, und bei der zweiten Iteration müssen Sie,
dass das zweitgrößte Element garantiert bis zur zweiten Position
nach vorne gerutscht ist
Hier in dem Beispiel, also wir haben das Array,
3814, 2 und so weiter. Sie würden also jetzt
4 mit 1 vergleichen. Jetzt ist die erste Iteration,
also Isgleich 1. Sie würden 4 mit 1 vergleichen,
das ist in der richtigen Reihenfolge 1 mit 23 und dann sehen sie,
dass die falsche Reihenfolge, die beiden würden sie jetzt vertauschen.
Und eins ist ja das Minimum unserer Folge und sie sehen,
das Minimum hat jetzt natürlich für den Rest dieser Schleife die Eigenschaft,
dass es immer kleiner ist als das Element, was davor steht.
Es wird also immer vertauscht und wandert damit automatisch schrittweise hier
bis an die vordere Position. Und weil wir jetzt wissen,
das Minimum steht an der vorderen Position,
müssen wir die nächste Iteration nur noch bis zur Position iPlus 1 machen,
um quasi einen korrekten Ablauf zu haben.
Okay, das zum Bubblesort. Wie gesagt,
Sie sehen an der Art und Weise, wie es aufgeschrieben ist,
schon sofort, okay, das hat auch Endquadrat-Laufzeit.
Sie sehen, das sehen Sie auch schon hier an dem Beispiel,
der Bubblesort ist schlecht bezüglich der Anzahl der Datenaustausch-Operation.
Ja, Sie machen Endquadrat mal,
tauschen Sie die Objekte hin und her
Der Insertion Sword hatte ja noch den Vorteil, dass er
wenigstens gut funktioniert, wenn die Folge mehr oder weniger sortiert ist.
Da könnte man überlegen, ja, gilt das denn für den Bubble Sword auch?
Funktioniert der gut, wenn die Folge quasi schon so gut
wie sortiert ist? Und da ist tatsächlich so,
das macht auch der Bubble Sword eigentlich ja dann nicht schlecht.
Allerdings, damit sie diesen Vorteil ausnutzen, müssen sie ihn an einer kleinen Stelle modifizieren.
Der Bubble Sword sollte merken, wenn die Folge bereits schon sortiert ist,
bevor sie hier en mal diese Schleife ausführen.
Das können sie ganz einfach machen,
indem sie eine zusätzliche Variable einfügen, indem sie sich merken,
dieser inneren Schleife zwei Objekte vertauscht wurden oder nicht.
Das macht in meinem Beispiel diese variable No-Swap,
die ist am Anfang true, also keine Vertausschung, es wahr.
Und dann läuft diese innere Schleife und wenn sie hier eine
Vertausschung machen mussten, dann wissen sie, okay,
zumindest bis vor diesem Durchlauf war die Folge noch nicht sortiert.
Sie haben ja noch eine Stelle gefunden, wo sie vertauschen
mussten und da setzen sie diese No-Swap-Variable jetzt auf Falls.
Sie haben ja eine Vertausschrift durchgeführt. Und nach Ablauf der inneren Schleife,
wenn No-Swap noch gilt, dann haben sie keine Vertausschung durchgeführt.
Das heißt, sie haben kein Elementpaar gefunden,
wo die Paarweise Relation verletzt ist und unsere Vergleichsoperation ist ja
transitiv und deswegen wissen wir in dem Augenblick,
die Folge muss komplett sortiert sein.
Und wir können also hier, wenn also No-Swap wahr ist,
können wir hier die äußere Schleife abbrechen und dann können wir das beenden.
Mit dieser minimalen Veränderung ist der Bubble-Sort auf recht gut vorsortierten Folgen auch nicht schlecht.
Aber es gibt eigentlich keinen Grund, ihn wirklich zu benutzen.
Okay, gut, das ist der Bubble-Sort. Gut,
auch der, klar, Endquadrat-Laufzeit,
also wir haben jetzt drei Verfahren gesehen,
im Worst Case
haben die alle Endquadratzeit
Ja, und dieses Zeitverhalten, das ist okay,
wenn wir ein paar hundert Zahlen sortieren wollen,
auch noch bei ein paar tausend,
aber wenn sie den Buchkatalog von Amazon sortieren wollen,
ist das eine schlechte Idee, da brauchen sie schon was anderes.
Ja, das ist zu langsam, wenn es quadratisch wächst
in der Laufzeit. Wir brauchen also ein schnelleres Verfahren.
Und die schauen wir uns jetzt an.
Also wir werden jetzt versuchen, diese Laufzeitschranke hier zu durchbrechen.
Das erste Verfahren, mit dem wir das schaffen werden,
ist ein sogenannter Merch-Sort.
Wer von Ihnen
kennt einen Merch-Sort
Oh, ein paar. Okay. Also auf alle Fälle
weniger als den Quicksort. Aber ich habe jetzt auch gesehen,
der Quicksort kommt in SE1 vor.
Deswegen kennen sie den alle so gut. Okay,
wir gucken uns erst mal den Merch-Sort an hier.
Der Merch-Sort ist ein klassisches Divide in Konka-Verfahren.
Wenn Informatiker algorithmisch nicht weiterkommen, ist immer das Erste,
dass sie denken, dann mache ich mal die Vide in Konka.
Ja, da kann ich das vielleicht lösen, das Problem.
Und auch das Sortier-Problem können wir mit Divide and Konka lösen.
Das funktioniert wie folgt. Wir nehmen also unsere Folge und
wir teilen diese Folge in eine linke und eine rechte Teilfolge
Ja, die linke Folge geht also von eins bis eine halbe,
nach unten abgerundet, könnte ja ungerade sein.
Und der zweite Teil geht von N halbe plus eins bis Ende.
Haben wir es ungefähr in der Mitte geteilt, so gut es eben geht.
Und ja, dann machen wir eben die Wahrheit in Conquer.
Das heißt, jetzt haben wir es geteilt,
in eine linke, rechte Teilfolge.
Wir rufen rekursiv das Verfahren auf. Es wird also
die beiden Teilfolgen sortieren. Dann haben wir zwei sortierte Teilfolgen
und müssen die irgendwie zu einer Folge zusammenführen
Das machen wir wie folgt. Wir haben eine Merch-Funktion hier.
Die Merch-Funktion nimmt zwei Sortierte Teilfolgen und wird daraus eine sortierte
Folge machen. Die müssen wir uns jetzt genauer anschauen.
Der Rest des Algorithmus ist eigentlich klar.
Also wir teilen, rufen ihn rekursiv auf. Klar,
wenn das nur noch ein Element ist,
deswegen ist diese IF-Anweisung hier,
wenn wir also nur eine Liste der Länge 1 haben,
dann ist die natürlich sortiert.
Eine elementige Liste kann man nicht umsortieren.
Und das sind einfach nur die rekursiven Aufrufe hier.
Und hier kommt die Merch-Operation. Da steckt der ganze Hirnschmalz
drin bei der ganzen Geschichte
Ja, hier ist ein Beispiel,
es ist wieder unsere Zahlenfolge, die kennen Sie mittlerweile,
wir teilen die Liste an dieser Stelle,
wir haben also die vorderen vier Zahlen,
die hinteren vier Zahlen, dann kommt der Conker-Schritt,
wir sortieren rekursiv diese beiden Teilfolgen und ein bisschen Magie,
die Rekursion läuft irgendwie ab, wir kommen an dieser Stelle wieder an,
haben wir hier zwei Teilfolgen, die erste ist also 2,
3, 8, 14, die nächste 1, 4,
9, 2, 23 und jetzt kommt der Merch-Schritt,
das ist der Algorithmus,
den wir uns noch anschauen müssen,
der dann aus diesen beiden sortierten Teilfolgen
eine Gesamtfolge produziert
Aufsteigend sortiert ist. Ja, okay. So, wie sieht
dieser Merch-Schritt aus? Wir werden jetzt einen kleinen Exkurs machen.
Also ich werde Ihnen diesen Merch-Schritt vorstellen mit Listen.
Wir haben ja letzte Vorlesung, elementare Datenstrukturen gesehen.
Und das ist ein hervorragendes Beispiel,
wo man ein Algorithmus mit Listen sehr gut verstehen kann.
Mit Arrays wird es ein bisschen schwieriger. Ich werde ihn
gleich auch nochmal dem Pseudo-Code zeigen, wie das mit Arrays aussieht.
Aber wir gucken uns das erstmal mit Listen an,
weil da kann man das viel besser verstehen
Stellen wir uns vor, wir haben zwei Listen.
Die beiden Listen beschreiben diese beiden Teil-Arays, L und R.
Und wir haben eben so Operationen, wie wir auf die Liste zugreifen können.
Wir können mit Hätte auf das Opfer, das der Elementalliste zugreifen.
Und wir haben noch so eine Empty-Funktion. Und dann werden wir gleich noch zwei,
drei andere Funktionen haben, die typisch sind für Listen.
Okay, also die Situation ist wie folgt. Wir haben also die,
wir haben den rekusiven Aufruf gestartet. Wir haben zwei sortierte Teilfolgen,
L und R. Meine sind jetzt so ein bisschen unterschiedlich lang.
Und was ja normalerweise nicht so ist,
die waren ja beide in halbe groß,
aber im Laufe des Algorithmus können die unterschiedlich lang werden.
Deswegen steht das hier schon so. Und hier sind die
obersten Zahlen. Also hier wäre es A, B,
C und hier XYZ, da stehen also irgendwelche Zahlen drin.
Und da haben wir diese zwei Listen und was machen wir jetzt?
Jetzt wissen wir, dass A offensichtlich das kleinste Element der
Liste L ist und X das kleinste Element der Liste eher,
weil die sind ja sortiert.
Dann nehmen wir einfach das Kleinere von den beiden,
also ich vergleiche A mit X und kopieren das Kleinere dieser
beiden Elemente in die Ausgabeliste A hinein.
Wenn wir das gemacht haben,
dann löschen wir dieses Listen-Element und lassen dann die Liste
auf das nächste Element zeigen
Wir haben also jetzt zwei Fälle. Der erste Fall ist,
dass der Kopf der Liste L, das ist hier das A,
kleiner ist, als der Kopf der Liste R,
das ist das X. Und wenn das so ist,
dann kopieren wir ja das A in die Ausgabeliste und der
Kopf der Liste L zeigt dann auf das nächste Element,
auf das B. Okay, und dann der zweite Fall wäre jetzt,
wir vergleichen wieder den Kopf der Liste L,
mit dem Kopf der Liste er.
Und jetzt kann natürlich auch der Kopf der Liste
eher das kleine Relement sein, wissen wir nicht.
Und wenn das so ist,
dann würden wir einfach das Element X in die Antwortliste
kopieren und dann aus der Liste
eher herauslöschen
So, diesen Prozess können wir einfach iterieren und wir hängen dabei,
sie kommen sofort dran, das Element immer an die Liste hinten an.
Frage? Nö, das ist einfach nur, die gehen da irgendwie weiter,
diese beiden Listen. Also das ist jetzt, ne,
also, aber was ich Ihnen schon signalisieren wollte, ist,
dass diese Listen im Laufe des Algorithmus unterschiedlich lang werden können.
Am Anfang sind die beide in halbe, ungefähr lang,
aber es kann natürlich sein,
dass aus irgendeinem Grund in der Liste L
viele kleine Elemente drin sind und dann würde man immer aus
der Liste L entnehmen und dann im Laufe des Algorithmus
würde diese Liste wesentlich kleiner werden als die Liste eher
Okay, so sehe das aus und das können wir jetzt
auch formulieren als Pseudo-Code. Wir gehen jetzt davon aus,
dass wir also eine Datenstrukturliste haben. Wir haben so ein paar Operationen,
die stehen hier, also Inits wäre die Operation,
um eine Liste, eine leere Liste zu initialisieren.
Empty ist die Funktion, die uns sagt,
ob die Liste leer ist oder nicht.
Dann haben wir Tale, Tale gibt uns die Liste,
die entsteht nach Entfernen des vordersten Elements und Appent
ist die Operation,
die am Ende einer Liste
ein Element anfügt
Denn das ist natürlich klar, dieser Algorithmus funktioniert nur,
wenn wir in die Liste A immer am Ende einfügen.
Sonst zerstören wir ja die Sortierung. Okay, so,
dann gibt es noch das Headhead, ist keine Operation streng genommen.
Es hat sich hier so reingeschmuggelt, es ist auch extra
klein geschrieben. Head ist natürlich ein Attribut.
Also damit greifen wir quasi auf das vorderste Element hierzu.
Können Sie auch als Operation interpretieren, wie Sie wollen?
Es ist nicht so wichtig, Sie müssen nur wissen,
was es tut. Okay, so, der Pseudo-Code sieht
also wie folgt aus. Wir haben also Merch Lists,
der Algorithmus, wir haben zwei Listen, L&R,
wir erzeugen eine neue Liste A,
die initialisieren wir hier
Und jetzt machen wir folgendes so lange,
sowohl die Liste L als auch die Liste eher nicht leer sind,
machen wir folgendes,
wenn die Liste eher leer ist oder die Head-Position von L,
kleiner gleich der Head-Position von Air,
dann fügen wir die Head-Position der Liste L an die Liste A an,
hinten dran.
Und L wird danach zu dem Rest der Liste L,
also alles ohne das erste Element. Anderenfalls,
wenn die Liste L leer ist oder wenn die Head-Position von L
größer gleich der Head-Position von Air ist,
fügen wir die Head-Position von Air an die Liste
Hinten an und nehmen dieses Element aus der Liste raus,
indem wir sagen, dass er jetzt der Rest der Liste ist.
Und dann machen wir einen Return, ja, von der Liste A.
Und dann haben wir das Resultat. Und damit können wir
den Merchstord laufen lassen, ist alles, was wir brauchen.
Wir müssen natürlich diese Listendatenstruktur realisieren. Wir sehen ja auch
sofort die Laufzeit, die wir haben. Wir haben hier
so eine Wildschleife und in dieser Wildschleife wird in jeder Iteration
ein Element in die Liste A hineingeschoben. Damit ist klar,
dass spätestens nach Betrag von L plus Betrag von eher vielen Schritten,
also Anzahl der Listenelemente in L und Anzahl der Listenelemente entwickelt eher Schritten,
die Listen L und er beide leer sind.
Und dann steht alles in der Liste A drin und diese Wildschleife terminiert.
Noch eine Frage. Ja, da haben sie,
ja, da, ja, da haben sie recht.
Das könnte man gegebenenfalls sich sparen, sagen wir mal so,
ich finde es so ein bisschen übersichtlicher,
was da passieren soll, aber sie haben recht.
Also wenn man, wenn, wir haben ja über diese
einzelnen Operationen gesprochen, wenn diese Vergleichsoperation natürlich teuer ist,
klar braucht man die da nicht zweimal ausführen.
Das müsste nicht unbedingt sein. Okay, so, hier,
das habe ich nicht abgeschrieben und auch nicht versucht, irgendwie da hineinzubringen.
Hier sehen Sie, falls Sie es überhaupt lesen können,
sonst haben Sie es vielleicht auch ausgedruckt auf der Folie.
Hier sehen Sie die Merch-Operation, nur die Merch-Operation,
realisiert mit Arrays
Es ist aus dem Lehrbuch, ist es nicht aus dem Kormen,
sehen Sie auch unten, ist aus einem anderen Lehrbuch.
Und ich habe es nochmal reingeschrieben, um ihnen zu zeigen,
dass man das natürlich auf Arrays machen kann.
Wir haben die anderen Sortierverfahren auch nur auf Arrays gemacht und
keine zusätzliche Datenstruktur gebraucht. Das ist aber schon einiges komplizierter ist,
den Ablauf hier zu verstehen, was hier überhaupt passiert.
Ich werde das nicht heile für Zeile durchgehen, können Sie wahrscheinlich hinten eh nicht lesen.
Ich habe Ihnen hier so ein paar Pointer gegeben,
so Hinweise gegeben, wie der Algorithmus,
der mit Listen funktioniert, also auf der Seite davor,
zu übertragen ist auf diese Variante mit Arrays.
Also hier steht zum Beispiel,
Wie ist der Index in dem Array von der Position,
wo der Kopf der Liste L ist.
Und J ist der Index des Kopfs der Liste R. K
ist der Index für das Ende der Liste A, der Ausgabeliste.
Hier sehen Sie a pent, also an diesen Stellen wird was angefügt.
Hier sehen Sie Tale, also an dieser Stelle wird die
Listenposition weiter geschoben. Und was sie auch noch sehen,
wenn sie mal gucken, die Schleifenstruktur ist hier etwas anders.
Also es gibt einen ersten Teil, diese Wildschleife,
die wird durchlaufen,
bis eine der beiden Listen leer ist und in diesen beiden
unteren Teilen hier wird jeweils der Rest der anderen Liste
noch angefügt
Okay, also es gilt auch mit Arrays, keine Frage,
ist aber doch einiges komplizierter nachzuvollziehen und zu verstehen.
Macht aber genau das Gleiche wie das,
was wir mit Listen gemacht haben.
Gut, ja,
wie funktioniert eine Frage
Ja, das ist ein Algorithmus,
den werden wir in dieser Form noch sehen in der Tat,
den gibt es, nicht im Kontext von dem Merch-Sort,
aber den werden wir noch sehen als eine spezielle Variante im Quick-Sort.
Da kommt dieses Verfahren so ungefähr, wie sie es gerade beschrieben haben,
kommt davor. Das Problem, was sie hier haben,
ist, also es ist immer nicht so ganz einfach,
dass ad hoc hier zu analysieren, aber das Problem,
was sie dabei haben, ist,
dass wenn ziemlich viele Elemente der zweiten Liste kleiner sind als die erste.
Dann müssen sie irgendwie schieben und Platz schaffen. Also wahrscheinlich.
Müsste man noch mal genau angucken. Aber im Prinzip,
klar, man kann noch versuchen, da was zu optimieren.
Aber das deutet in eine ganz richtige Richtung,
denn ein großes Problem des Merchants,
ich könnte gleich über die Laufzeit reden,
nur weil wir gerade dabei sind, ist der Speicherbedarf.
Also wie viel Speicher braucht der Algorithmus? Wir haben schon gesehen,
also der Selection Sword,
Insertion Sword und sogar der Bubble Sword war ein Sortierverfahren,
die sozusagen innerhalb des Arrays
die Elemente vertauscht haben
Das sind sogenannte In-Plays-Verfahren. Ja,
die brauchen keinen zusätzlichen Speicher, um die Daten zu speichern.
Hier brauchen wir, wenn wir es mit Arrays machen,
ein zusätzliches RAR, wo wir die Ergebnisse reinkopieren.
Und wenn wir es mit Listen machen,
brauchen wir mindestens mal den Speicher-Overhead, um die Listenzeiger zu verwalten.
Also der Merch-Sort, so wie er hier steht,
ist nicht in-place, kein In-Place-Verfahren, braucht zusätzlichen Speicher.
Okay, hier steht das, also das RAR hat irgendwie eine Konstante,
mal en-viel-Speicherelemente, also die Anzahl, den Speicher,
den sie brauchen,
ist proportional zu eins der Elemente
Sie brauchen aber irgendeine Art von Zwischenspeicherung in einem zweiten Array.
Das braucht auch nochmal zehnmal N. Im O-Kalkuhl ist natürlich immer noch linear
viel Speicher, aber es ist doppelt so viel Speicher.
Und selbst wenn sie das mit Pointern machen,
haben sie ein Overhead und der ist ärgerlich.
Auch hier haben wir ein Problem. Also,
das heißt, wenn sie Millionen von Daten haben,
dann wollen sie nicht mal eben den Speicherbedarf verdoppeln.
Damit rutschen sie vielleicht aus dem Hauptspeicherbereich heraus,
den sie zur Verfügung haben und dann swappen sie auf die
Platte und dann ist es langsam.
Das ist also
der große Nachteil des Merch-Sort-Algorithmus
Aber vielleicht jetzt erst noch zur Laufzeit, das habe ich ja gerade überschwungen.
Wie lange dauert das? Wir teilen die Folge in zwei Teilfolgen.
Dieses Teilen ist nur die Positionsbestimmung, geht also in konstanter Zeit,
O von 1. Wir sortieren dann die beiden Teilfolgen L und R.
Das ist Laufzeit zweimal und dann, ja,
das ist ein rekusive Algorithmus, Tee von N halbe.
Und schließlich müssen wir unsere Merch-Operation durchführen und die ist proportional
zur Anzahl der Elemente in L und R zusammen.
Das sind natürlich en viele Elemente. Das bedeutet,
wir haben hier O von N Zeit,
um die Listen wieder zusammen, also um die Teilfolgen,
die schon sortiert sind, wieder zu einer sortierten Folge zusammenzuführen.
Laufzeit, das ist also eine Rekurrenzgleichung, die wir haben,
TVNS 2 mal Tee von N halbe plus 10 mal N.
Diese Art von Rekurrenzgleichung haben wir schon gesehen in der Vorlesung schon.
Und wir wissen, dass wir die mit dem Master-Theorium auflösen können.
Und da kommt O von N-Log-in raus.
Also in der Tat ist der Merch-Sort ein Verfahren,
was die anderen bisher gesehenen Verfahren im Worst-Case deutlich schlägt.
N-Log-in ist eine Laufzeit für große N,
wesentlich weniger als O von N-Quadrat
Gut. Nachteil, habe ich gerade schon gesagt.
Nachteil ist, irgendwie brauchen wir zusätzlichen Speicher.
Zumindest für diese klassische Form des Merch Swords.
Es gibt jetzt so eine Idee, dass man sagt,
also jetzt habe ich das also in zwei Hälften aufgeteilt und
die dann sortiert und zusammengefügt und irgendwie wurde mein Verfahren jetzt schneller.
Von Endquadrat nach Login, das war ja der Trick.
Dann könnte ich ja sagen, okay, dann teile ich
das doch jetzt in drei Teile oder in vier Teile auf.
Vielleicht wird das dann ja nochmal schneller. Wenn ich jetzt sage,
ich mache jetzt vier Teile,
dann hätte ich ja nur noch ein Viertel in jedem dieser
rekursiven Aufrufe und muss natürlich
viermal aufrufen
Und dann muss ich nachher natürlich das wieder zusammenfügen.
Also der Gedanke könnte allen kommen, dass man noch etwas gewinnt,
wenn man es in mehr Teile aufteilt.
Das ist aber nicht so. Wenn man sich überlegt,
es gibt einen sogenannten KW-Gemerge-Sort, also wir teilen das nicht
in zwei Häfen, sondern in den N-Hälften auch.
Wenn K, genau, im K-Teil da auf,
dann hätten wir als Rekurrenzgleichung Tee von Engleich K mal C
von Endurch K. Wir haben also K viele Folgen,
die alle N durch K viele Elemente haben.
Die müssen wir alle sortieren. Das wäre also,
hier ist das K gleich zwei. Also es ist diese
Sortierung der ganzen Teilfolgen. Und dann kommt jetzt das dass
das Mergin selber, und jetzt kommt nämlich der Punkt,
warum die Laufzeit wieder gleich wird, die Zeit,
die wir zum Mergin brauchen, ist zehnmal K mal N.
Also irgendeine Konstante mal K mal N. Das ist nicht zehnmal
N. Und warum ist das so? Weil,
das sieht man nämlich in diesem Algorithmus nicht so,
weil wir an dieser Stelle feststellen müssen,
in welcher Liste steht im Kopf das Minimum drin.
Weil zwei Listen sehen sie das nicht. Dann machen sie
einen Vergleich. Da wissen sie, was das Minimum ist.
Wenn sie Karlisten haben, dann müssen sie das Minimum von den Karlistenköpfen bestimmen.
Und das gilt eben nicht in konstanter Zeit,
sondern da brauchen sie dann auch K-Schritte, zu machen.
Und deswegen sieht die Rekurrenzgleichung eben so aus,
die hat ja also das K noch drin und in diesem Fall können sie das auflösen,
können sie sich ja mit den Mastertheorien machen und dann kriegen sie daraus, dass da Wiederoh von N-Log-in rauskommt.
Und der Extremfall ist ja auch nicht ganz uninteressant,
wenn sie den Extremfall nehmen und sagen,
was ist denn, wenn K gleich N ist,
ich nehme die Liste und teile die auf,
jedes Element ist sozusagen für sich alleine,
dann haben sie hier N mal die Zeit,
um ein Element zu sortieren, das wäre ja super,
alle in der Jahrzeit, N mal T von 1,
das ist Linearzeit,
aber dann haben sie hier plus 10 mal N mal N,
dann haben sie hier den quadratischen Aufwand beim Merging
Deswegen ist das wichtig, dass man im Kopf hat,
dass K, das heißt, die Anzahl der Listen eine Rolle spielt.
Okay, das hört sich jetzt so an,
als hätte der Merchshot irgendwie so gar nichts Schönes, ja, was meistens,
außer für den Bubbleshot, haben wir irgendwie noch was Nettes gefunden,
was den Algorithmus irgendwie dann doch noch ein bisschen adelt.
Sieht ihr erstmal so aus. Und trotzdem auch der hat
seine Daseinsberechtigung und er hat eine ganz tolle Eigenschaft, ja, dieser Algorithmus.
Ich weiß nicht, ob man das sofort sieht,
aber der Merchshot ist leicht parallelisierbar, ja,
also dies als solcher Verfahren, die wir bisher gesehen haben,
die waren alles strikt sequenziell.
Wenn ich Ihnen sage, Sie haben jetzt einen, Jul-Quadcore-Prozessor mit acht Kernen.
Dann nützt ihn das beim Insourcen-Sort herzlich wenig.
Beim Bubble-Sort auch nicht viel, aber der Merch-Sort,
der Merch-Sort ist in der Lage, das direkt zu nutzen.
Sie können, wenn Sie sagen, Sie haben einen Prozessor,
Sie haben acht Kerne zur Verfügung,
dann machen Sie die ersten acht Teilungsschritte und teilen einfach den
Aufwand für das Sortieren der Teilfolgen auf acht Threads auf,
machen das komplett parallel.
Beim Merchant ist es dann nicht mehr parallel,
da können Sie noch so ein bisschen parallel arbeiten,
indem Sie immer teilfolgen, parallel zusammenfügen.
Also der Merch-Sort ist ein guter Ausgangspunkt für ein einfaches Verfahren,
was man auch parallelisieren kann
Ja, wir werden hier in der Vorlesung keine parallelen Algorithmen betrachten.
Das ist nochmal ein gehobener Sport, sich damit auseinanderzusetzen.
Also wie funktionieren Algorithmen optimal in parallelen Umgebungen?
Und da gibt es auch noch ganz andere Sortierverfahren mit
ziemlich komplexen Analysen, wie die parallel sortieren können.
Also es ist keine einfache Aufgabe, ein Sortierverfahren zu parallelisieren.
Aber unter diesen einfachen Verfahren, die wir sehen wollen,
ist das ein schönes Beispiel.
Den kann man parallelisieren. Okay, ja, das war
der Merch-Sort.
Und ja,
wir gehen
Schnell voran und wir kommen jetzt so langsam zu den beiden,
würde ich sagen, komplexeren Verfahren, die man so kennt zum Sortieren.
Vielleicht sollte man sagen, wir haben hier beim Merch-Sort natürlich
bezüglich der Laufzeit schon einiges erreicht.
Wir sind schneller geworden, aber wir haben uns das ja erkauft.
Wir haben uns das erkauft mit dem Nachteil,
dass das Verfahren kein In-Place-Verfahren mehr ist.
Also wir haben uns eine schnellere Laufzeit, er kauft durch mehr Speicher.
Das heißt, wenn wir richtig große Datenmengen sortieren sollen,
haben wir noch nicht wirklich was gewonnen mit dem Merch-Sort
Weil wir gegebenenfalls den Overhead haben durch den Speicher.
Das heißt, wir hätten gern ein Verfahren,
was in Endlock Ensight sortiert und möglichst auch in place ist.
Also keinen zusätzlichen Speicher benötigt. Das leistet dieses Verfahren ja
noch nicht. Ja, der Heepsort wird das leisten.
Der Heepsort ist das klassische Verfahren, was in der Tat
N-Lock-N-Laufzeit erreicht und in places. Also keinen zusätzlichen Speicher benötigt.
Um dem Heepsort zu verstehen, muss man eine Datenstruktur verstehen.
Also wir werden jetzt wieder so einen Switch machen, Vorlesung,
Algorithmen und Datenstrukturen und werden jetzt primär über Datenstrukturen sprechen,
weil manche Algorithmen erschließen sich einfach über die Datenstruktur,
die wir brauchen, um den Algorithmus zu realisieren.
Und so ist das beim Heep-Sort. Der Heep-Sort ist für sich
genommen total einfach. Das Einzige, was er braucht,
ist eine Datenstruktur, die kompliziert ist.
Das ist der Heep. Okay, also was machen wir
? Wir machen etwas, was wir schon hatten.
Wir machen Sortieren durch Auswahlen. Also Selection-Sort. Und wir gucken mal,
das ist der Selection-Sort, der war ja eigentlich nicht schlecht.
Ja, der hat immer geguckt, was ist das nächste
sein Element, hat das da hingeschoben.
War keine blöde Strategie. Der hat nur dooferweise so lange
gebraucht, um das Minimum zu finden.
Wenn der Selection-Sort schnell das Minimum finden könnte, Ja,
dann wäre der natürlich auch schneller.
Dann könnte er sozusagen dieses Randschieben an die richtige Position,
war ja nur eine Austausch-Operation, machen wir mal einmal, sind Endschritte.
Was das Teure war, war immer das Minimum zu suchen.
So, wir brauchen also eine Verbesserung unserer Strategie im Selection Short
, um das Minimum zu finden. Okay, also,
wir machen Selection Short, das kleinste Element wird ausgewählt und
dann an die bereit sortierte Liste angehängt.
Das ist Selection Shot. Und jetzt gucken wir uns diesen Auswahlschritt an.
Dazu entwickeln wir eine neue Datenstruktur und die Datenstruktur hat das Ziel,
dass es uns möglichst schnell immer das Minimum einer Menge
sagen soll
Diese Datenstruktur, die gibt es, die ist einer der wichtigsten Datenstrukturen,
die wir in der Informatik haben,
die nennen wir Heep und die Realisierung müssen natürlich nicht immer nur sortieren,
aber die Datenstruktur, die der Heep realisiert,
ist so eine sogenannte Prioritätswarteschlange, eine Priority Cue.
Also immer dann, wenn sie priorisiert etwas speichern müssen,
um dann darauf zuzugreifen, brauchen sie so einen Heep.
Und das passiert verdammt häufig, dass man genau diese Anforderungen hat.
Also so ein Heep ist also folgendes,
wir haben also N-Objekte, die wollen wir speichern und wir wollen
In unserem Fall das größte Element, könnte auch das Kleinste sein,
aber das größte Element aus dieser Menge herausgreifen können.
Dazu muss die Menge, das ist das erste,
was man sich durch den Kopf gehen lassen muss,
dazu muss die Menge nicht notwendigerweise sortiert sein.
Wir wollen ja nur einen schnellen Zugriff auf das größte Element.
Wir wollen keinen schnellen Zugriff auf das fünfgrößte Element zum Beispiel.
Nur das brauchen wir. Und dazu müssen wir nicht schon
vorher sortiert sein. Das wäre ja dann auch komisch,
weil wir wollen ja damit ein Sortierverfahren realisieren.
Ja, wieso heißt denn diese Datenstruktur hieb,
in dem Wort steckt ja schon etwas drin,
was uns verrät,
wie diese Datenstruktur funktioniert
Also Heep auf Deutsch irgendwie Halde. Und so eine Halde
ist einfach nur so ein Berg von unterschiedlichen Objekten.
Müllhalde. Zum Beispiel. Und wir wollen aus dieser Halde
, wie gesagt, das größte Objekt heraussuchen.
Und die Idee ist, dass unsere Halde die Eigenschaft hat,
dass die großen Objekte immer oben auf der Halde liegen.
Das ist jetzt so ein bisschen schwer, so eine Analogie zu finden. Also die einzige Analogie,
die mir so gut einfällt, ist so,
wenn sie so Sand und Kies hätten zum Beispiel und machen
damit so einen Berg und würden dann mal mit der Schaufel reingehen,
dann werden wahrscheinlich die kleinen Sand- und Kieselsteine in die Lücke rutschen,
nach unten fallen und die etwas größeren bleiben vielleicht oben liegen
Und sie brauchen sich jetzt nicht melden. Ja, die
fallen dann ab und zu auch runter. Aber nur,
damit sie so einen Gedanken haben, warum man das Halde nennt.
Also man schaufelt das irgendwie auf den Berg und die einzige Eigenschaft,
die wir realisieren wollen, ist,
das größte Element soll oben liegen.
Ja, habe ich Ihnen hier so bildlich dargestellt.
So, was für Operationen brauchen wir auf seinem Hieb?
Offensichtlich müssen wir den Hieb, den Hieb bauen können.
Ja, wir müssen ihn aus einer Menge von Objekten konstruieren können.
Ja, dass er die Datenstruktur aufgebaut ist.
Das ist die Funktion Bild maxim. Wer heißt hier bei
mir Maxib, weil wir mit dem das Maximum entfernen können
Sonst hieße der Men hiebt, gibt's auch. Okay,
und dann müssen wir natürlich die Operation haben, die uns das Maximum da rausnimmt.
Die heißt Heep Extract Max. Also wir nehmen das maximale
Element von der Halde herunter. Wenn wir das machen,
dann würde also die folgende Situation entstehen,
hier ist unsere Halde, wir haben unsere Extract Max Operation ausgeführt,
wir nehmen das erste Element von der Halde herunter und haben noch diesen Teil.
Jetzt sehen wir, dass unsere Hiebstruktur verletzt ist, die ist irgendwie zerstört.
Wir haben nicht mehr so einen schönen Berg,
wo oben das größte Element liegt, weil das haben wir gerade da weggenommen.
Das ist keine Spitze mehr da, die spitze ist irgendwie weg.
Das heißt, unsere Heep Extract Max X-Funktion,
braucht eine Hilfsfunktion, die irgendwie diese Hieb-Eigenschaft wiederherstellt,
die den Hieb repariert, weil wir haben da was weggenommen.
Ihr habt den schönen Namen Max Hiepify. Also wir werden
die Halde wieder aufbauen, sodass die Eigenschaft,
dass das größte Element eben an prominenter vorderster oberster Stelle ist, wiederhergestellt ist.
So, das sind die drei Funktionen, die brauchen wir,
um mit Hiebs zu sortieren. Wir werden jetzt davon ausgehen,
wir haben diese drei Funktionen, wir werden noch nicht darum uns darum kümmern,
wie wir die machen, sondern wir gehen davon aus,
wir haben die drei und sagen, okay,
mal angenommen, wir haben die drei,
wie können wir denn dann sortieren
Das geht so. Das ist der Pseudo-Code für einen Heep-Sort
. Die erste Variante und gleich die zweite.
Die erste Variante wäre zu sagen, okay,
wir haben jetzt also diese komplexe Heep-Datenstruktur.
Da haben wir wahrscheinlich irgendeine Art von Init-Funktion,
die uns einen leeren Heep erzeugt.
Das passiert am Anfang. Dann haben wir eine Vorschleife,
die übrigens hier auch falsch eingerückt ist.
Die muss hier vorne hin. Also die Vorschleife läuft von
1 bis N. Und wir fügen dann einfach das ETE-Element
unseres Eingabe-Arays in den Heep
Macht eine Heep-Insert-Funktion. Die stand jetzt gerade nicht in der Liste,
aber wir werden gleich auch sehen, die brauchen wir nicht.
Das ist jetzt erstmal nur ein Gedankenexperiment.
Also wir haben eine Datenstruktur, die ist am Anfang leer.
Wir schieben ein Element nach dem anderen in diesen Heep rein.
Okay, dann laufen wir mit einem Index i vom Ende
des Arrays A zurück zur Position 1.
Und wir schreiben an die Position AI.
Das Maximum, was im Heep vorhanden ist.
Wir machen das jetzt von hinten
nach vorne
Für den Fall, dass sie sich fragen, warum?
Im Moment gibt es noch keinen Grund. Sie könnten natürlich
auch mit einem Min-Hieb arbeiten und von vorne nach hinten gehen,
aber in wenigen Minuten wird es einen Grund geben,
wenn wir diesen Algorithmus weiter modifizieren.
Im Moment ist es eigentlich egal. Sie könnten von vorne
nach hinten oder von hinten nach vorne,
wir gehen also von hinten nach vorne,
extrahieren das Maximum und schreiben das an die IT-Position.
Okay, so könnte das aussehen. Wir würden also jetzt
diese Funktion benutzen. Wir sehen hier der obere Teil,
also diese Init-Funktion, die Vorschleife und die Seap Insert,
das zusammen ist eigentlich
unsere Bildfunktion
Ja, diese drei Zeilen bauen den Hieb aus unseren Daten. Das heißt,
die könnten wir auch ersetzen durch Bild Maxib und dann kommt es,
jetzt geht dir das Array rein und dann wird irgendwie das gemacht und da wird ein Hieb konstruiert.
Ja, und der Teil hier, ja,
der ist quasi der Teil, wo wir dann das Maximum immer extrahieren.
Das ist der Selection-Sort, der eigentliche, ja, dieser Teil hier. Okay,
wenn wir bei diesem Modell bleiben würden und jetzt uns überlegen,
wie, ich habe es im Kopf, wie die Datenstruktur aussieht,
dann hätten wir von vornherein eigentlich schon verloren,
weil ich habe ihn ja versprochen, wir wollen ja ein In-Play-Sortierverfahren haben.
Das wird niemals in Plays, weil wir haben ja die
Datenstruktur H und wir kopieren die Daten von A in diese Datenstruktur rein.
Wenn wir in Plays haben wollen, dann dürfen wir keine zusätzliche Datenstruktur haben.
Wir müssen uns quasi nur gedanklich damit anfreunden,
eine Datenstruktur zu haben und müssen den Speicher,
den wir zur Verfügung haben, unser RAR nutzen,
um die Information dort drin zu verwalten.
Das ist der Trick. Deswegen müssen wir unseren Algorithmus hier
noch weiterentwickeln.
Nee, sie nehmen das ja aus dem Hieb raus mit der Extract-Funktion.
Ja, also da ist dann raus und dann ist ein
Element weniger und dann, ja, okay. Gut,
okay, so, jetzt müssen wir also so ein bisschen umbauen,
dass wir mit dem Array arbeiten,
damit es ein In-Place-Verfahren ist und ohne ihn zu sagen,
wie es geht, gehen wir jetzt davon aus,
dass in dem Array an den Positionen von 1 bis i quasi der Hieb steht,
Also irgendwie nutzen wir das Array, um den Heep zu speichern.
Und alles, was dahinter ist, ist schon sortiert.
Ja, und jetzt sehen sie auch,
warum wir mit dem Maximum arbeiten,
weil wir dann hinten einfach immer die großen Zahlen schon abspeichern können.
Und der vordere Teil ist irgendwie dieser Heep. Okay,
wenn der eben so ist, wenn wir das schaffen,
das so zu machen, dann können wir jetzt also den Heep-Soul aufschreiben.
Wir sagen dann, okay, wir bauen irgendwie den Heep,
wie die Funktion aussieht, habe ich die noch nicht verraten.
Die Heep-Size ist am Anfang die Größe des Arrays.
Also alle Elemente des Arrays gehören zum Heep.
Die Vorschleife ist ja so ähnlich wie die,
die geht jetzt also von der hinteren Position
bis nach vorne
Sie wird dann an die, die das Element AI
mit dem Element A1 vertauschen, warum auch immer.
Das ist noch ein bisschen Magie-Moment. Dann rufen wir,
dann verkleinern wir die Heeps, heißt, wir sagen,
ein Element weniger gehört nicht mehr zum Heep,
und zwar das Element AI gehört nicht mehr dazu, ja,
was der Heep geht, nur noch bis zu der Position davor.
Und dann korrigieren wir hier mit unserer Max Hibify-Option, die Hieb-Eigenschaft.
Das heißt, was wir jetzt gemacht haben, ist,
wir haben die Bildfunktion zusammen, also wir haben diese drei Operationen zusammengefasst,
das ist jetzt die Bildfunktion,
und haben aber das Heep-Extract Max zerlegt.
Also diese Operation hier repräsentieren quasi das Extrahirn des Maximums und die Korrektur des Hiebs,
weil wir jetzt eben auf dem Array selber arbeiten.
So, warum machen wir das hier?
Warumfür tauschen wir das? Wir werden im Folgenden sehen,
dass an der Position eins des Arrays immer das maximale Element liegen wird.
Ja, so werden wir den Hieb konstruieren. Das wissen sie jetzt noch nicht,
aber jetzt wissen sie es genau in diesem Augenblick und
gleich in der Datenstruktur werden sie das sehen.
Deswegen vertauschen wir dann das größte Element aus der Menge von
1 bis I und mit dem Element an der Position I
und damit haben wir das an die richtige Stelle gebracht Haben aber dafür den Hieb zerstört.
Und den müssen wir reparieren. Und das machen wir an dieser Stelle mit der Maxibi-Fair-Operation. So,
wir können sogar die Laufzeit schon analysieren unter der Annahme,
dass wir wissen, wie die Laufzeit für Bild,
Maxib und für Extract Max oder Maxibify ist,
wissen wir, die Laufzeit ist O von die Zeit,
die wir brauchen, um den Hieb zu bauen.
Also der Schritt hier, plus,
und dann haben wir eine Schleife mit Endschleifen-Durchläufen und jedes Mal
in diesem Schleifen-Durchlauf müssen wir Extract Max machen,
was im Wesentlichen so aussieht, aus diesen drei Schritten.
Und sie können sich das auch so vorstellen, dass sie,
die Laufzeit ist offensichtlich genau hier in diesem Maxibify
Das ist ihr konstant und das ist auch konstant.
Also N mal, die Laufzeit,
um das Maximum zu extrahieren und den Hieb wieder zu reparieren.
Natürlich können wir das jetzt nicht auflösen und sagen,
da kommt jetzt N-Log-End raus, weil wir wissen ja noch nicht,
wie lange diese Operationen dauern.
Aber für den Algorithmus sind wir soweit fertig. Ja,
jetzt müssen wir uns überlegen, wie konstruieren wir diesen Hieb in dem Array?
Das ist die entscheidende Frage. Okay, was wir machen,
ist, wir werden einen Baum verwenden. Bäume haben ja
die tolle Eigenschaft, dass sie lugorithmische Tiefe haben für Endobjekte.
Haben sie nur Log-End-Tiefe? Und irgendwie müssen wir diese Baumstrukturen
nutzen, um diese Operation des Hiebs optimal zu unterstützen.
Das ist unser Ziel. So einen Baum haben wir gesehen
, meinen elementaren Datenstrukturen. Wir haben also so Knoten,
unseren Knoten hat einen, wir haben biläre Bäume,
hat also einen linken und einen rechten Nachfolger und hat irgendwie so einen Elternknoten hier.
Die nenne ich jetzt Left und Ride und Parent.
Drei, ne? Also Parent ist hier oben und Left
und Ride sind hier diese beiden Kinder.
Und wir werden an jedem dieser Knoten ein Element AI speichern.
Also da steht ein Element aus dem Array immer drin.
Die Hieb-Eigenschaft, die dafür sorgt, dass das,
was wir hier speichern, als Hieb interpretiert werden kann,
steht hier
Die Hiebeigenschaft bedeutet, unser Baum, den wir haben,
ist ein Hieb, genau dann, wenn für alle Knoten I außer der Wurzel gilt,
dass der Wert, der gespeichert wird, in dem Vorgänger,
in Parent von I größer gleich den Wert an der Position I ist.
Also das heißt, wir vergleichen ein Element in dem Baum,
immer mit seinem Vorgänger und die Eigenschaft, die gelten muss,
ist das, was hier steht, muss immer oder das,
was im Elternknoten steht, muss immer größer gleich dem Wert sein, der in dem Knoten selber steht.
Hier ist ein schönes Beispiel für so ein Hieb. Für diejenigen von Ihnen,
die sortierbäumen, also die Suchbäume schon kennen,
wir ja Vorlesen bei uns später,
aber der eine oder andere hat das sicherlich schon gesehen und im Kopf,
das ist kein Suchbaum, ja, ein Suchbaum hat eine zusätzliche Eigenschaft,
der etwas aussagt über die Ordnung von links nach rechts,
ja, das ist kein Suchbaum hier, die einzige Eigenschaft,
die der Hieb hat, ist,
ich vergleiche das Element mit dem Vorgänger,
der Vorgänger muss das größere Element haben.
Das ist aber auch alles, was wir brauchen,
denn wir müssen ja nur sicherstellen,
dass am Anfang oben im Hieb das Maximum drinsteht,
ja, und wenn wir dann wissen,
also der Knoten hier ist vom Wert her größer als der
der Kinder und der ist wiederum größer als der der Kinder,
der auch und so weiter,
dann wissen wir,
oben steht das Maximum
Ja. Ja, es dürfen auch Werte,
also es kann natürlich sein,
dass beim Sortieren auch Werte auftreten, die gleich groß sind,
dann muss man den Fall, muss man natürlich berücksichtigen,
sonst könnte man nur Schlüssel sortieren, die alle paarweise unterschiedlich sind.
Deswegen steht hier auch tatsächlich ein größer gleich. Genau,
genau, die können sie ja nicht unterscheiden,
die stehen dann da irgendwie rein.
So, jetzt könnten sie berechtigterweise sagen,
ja, tolle Datenstruktur, da brauche ich ja drei Zeiger
Ich musste immer den Vorgänger und ich muss die Kinder finden.
Das ist ja genauso schlimm wie mit den Listen.
Habe ich ja wieder nicht ein Sortierverfahren, was inplace ist,
weil ich musste immer diesen ganzen Zeiger alle merken.
Das ist aber nicht so. Wir können diesen Baum nämlich
ganz einfach in ein Array einbetten, in dem wir implizit wissen,
wo die Kinder und wo die Eltern stehen.
Und das geht wie folgt, das ist hier dargestellt.
Hier sehen Sie den Baum, so wie wir den eben hatten.
Und die Zahlen hier, das sind Array-Positionen. Und hier steht
das Array. Also das steht an der Position 1,
dieser Wert an der Stelle 2, 3, 4,
5, 6, 7,
8. Sie gehen einfach so zahlenweise durch und nummerieren die durch
Wenn sie das machen, dann können sie mathematisch sofort sagen,
wo der Vorgänger ist und wo die Kinder sind.
Den Vorgänger kriegen sie durch die Operation iDive 2,
also ganz zeitige Division durch 2. Da sind sie hier,
vier ganzteilige Divisionen durch 2, 2,
5 durch 2, 2, also ganzzeitige Division.
6 ist 3, 7 ist 3,
2 ist 1, 3 ist 1 und so weiter.
Also sie kriegen durch die ganzteilige Division immer den Vorgänger und
entsprechend kriegen sie durch die Multiplikation mit 2 das linke Kind
und durch zweimal iPlus 1 immer das rechte Kind.
Also diese drei Operationen auf den Indizes reichen,
um die komplette Baumstruktur in dem Array abzubilden,
ohne dass wir irgendwelche Zeiger oder so verwenden
So, das heißt, unsere Operation, Parent und Left und Rides ist nicht,
sind nicht die Operationen, wir gucken uns einen Zeiger an,
sondern es ist nichts weiter als die Manipulation des Indexs, müssen diesen drei Formeln.
Okay, hier sehen Sie dann nochmal, dass Array,
also so sieht das in dem Array aus und diese Zeiger,
die unten, die kann man eigentlich gar nicht so sehen,
weil es so viele sind, aber die Zeiger,
die zeigen ihnen nochmal die gleichen Zeiger,
die sie hier haben, immer zu den Kindern.
Und das können Sie sich das nochmal besser vorstellen. Gut,
so, wir müssen jetzt natürlich den Algorithmus gleich analysieren,
deswegen brauchen wir auf alle Fälle so ein paar Maße in diesem Hieb.
Wir werden jetzt versuchen, Also wir wissen schon,
wie wir das Maximum finden. Das ist ganz einfach.
Steht an der Position A1. Da ist das Maximum, ist immer da.
Weil das ist ja die Wurzel sozusagen dieses Baums.
Das ist immer das größte Element. Wir wissen aber noch nicht,
wie, wenn wir das da wegnehmen, wir diese Hieb-Eigenschaft wieder korrigieren.
Und da müssen wir genau hinschauen, wie das geht.
Und diesen Algorithmus werden wir bezüglich der Laufzeit genau analysieren müssen.
Und dafür brauchen wir diesen Begriff der Höhe.
Höhe von Bäumen haben wir schon gesehen. Hier einfach
nur noch mal, damit es ganz klar ist.
Für einen Hieb der A ist die maximale Anzahl von Knoten
auf dem direkten Weg zu einem Blatt die Höhe,
die wir haben von dem Gesamtbaum hier
Okay, so, die Heep-Size ist natürlich die Anzahl
der Elemente, die in dem Hieb gespeichert sind.
Sie sehen an diesem Bild noch was, wir werden noch
etwas sicherstellen müssen, damit das hier alles so funktioniert.
Und zwar, dieser Baum ist nicht notwendigerweise vollständig.
Das können wir ja gar nicht sicherstellen. Dann hätten wir immer
nur spezielle Anzahl von Elementen, die wir speichern können.
Das ist nicht vollständig, aber er hat die Eigenschaft,
dass er linksvollständig ist. Das heißt,
die Elemente füllen immer komplette Ebenen und in der letzten Ebene
fangen sie an der linken Seite noch an und schreiben da noch was hin.
Und irgendwann hört das dann auf. Das bezeichnen wir als
ein Leben
Ringsvollständigen Baum. Und so werden die aussehen, unsere Heeps.
Und nur so funktioniert das natürlich, weil angenommen,
hier wird jetzt noch so ein Element hängen,
dass wir ja dann in dem Array viel weiter da hinten, das geht nicht.
Also das heißt, unser Heep ist immer links vollständig.
Eine Eigenschaft, die wir sicherstellen müssen,
sonst kommen wir mit unserem Speicherplatz nämlich nicht aus. Okay,
so, der Heep hat,
weil er quasi mehr oder weniger vollständig ist und vollständig wie es geht,
lugerithmische Höhe, das ist die Eigenschaft von Bäumen,
lugerithmische Höhe in der Anzahl der Elemente und auf Ebene I
liegen immer zwei Hoch-I-Knoten
Ebene null Wurzel, ein Knoten, nächste Ebene,
zwei Knoten, nächste Ebene vier Knoten, acht und so weiter.
Außer die letzte, bei der letzten ist es eben nicht
so vollständig, da fehlen irgendwie ein paar Wert.
So, die Hieb-Eigenschaft steht hier oben nochmal,
die haben wir schon besprochen, hier nochmal andersherum formuliert.
Jetzt betrachtet aus der Position des Elternknotens auf die Kinder. Nur eine andere Betrachtungsweise,
ja, da können wir sagen, was gelten muss,
also wir gucken von oben nach unten,
ist, wenn es ein Hieb ist,
dann ist an der Stelle I ein größeres Element gespeichert
als Eleft von I und In Ride von
Das war nur andersrum formuliert. Eben haben wir immer zum
Vorgänger geguckt, jetzt gucken wir von oben nach unten.
So, jetzt kommt unsere Hipify-Operation. Wie können wir
Hieb-Eigenschaften reparieren? Wir gehen mal davon aus,
wir haben einen Hieb, der fast ein Hieb ist.
Also er ist fast überall ein Hieb und an einer Stelle ist er kaputt.
Das ist das Szenario, mit dem wir konfrontiert sein werden,
weil wir nehmen ja das Maximum da raus und dann wird er an einer Stelle nämlich oben kaputt sein.
Ja, da ist die Eigenschaft verletzt. Der Rest ist okay
. Also nur eine Stelle, wo der kaputt ist.
Was können wir jetzt machen? Was wir machen ist,
wir können diese Fehlstelle, also diese kaputte Stelle im Hieb,
die können wir transportieren im Hieb, die können wir bewegen, ja.
Und wenn wir die in die richtige Richtung bewegen,
können wir auf diese Art und Weise den Hieb reparieren.
Hier ist mal ein Beispiel, also hier sehen Sie einen Hieb und Sie sehen,
der Hieb ist kaputt, der ist verletzt,
die Hieb-Eigenschaft ist verletzt, und zwar an einer Stelle,
nämlich hier oben, ja.
Also hier oben steht jetzt so eine 2 und offensichtlich ist hier die Hieb-Eigenschaft verletzt,
weil 2 ist ja kleiner als die Werte dieser Kinder, ja.
So, diese Defektstelle, würde ich mal so sagen,
also die Stelle, wo was kaputt ist, die können wir im Heat bewegen.
Und zwar können wir die nach unten bewegen,
indem wir sagen, wir gucken uns die beiden Kinder an,
Es ist sechs und hier steht die acht.
Und wir nehmen einfach den größeren,
also das Größere von den beiden Kindern und vertauschen die Werte.
Wenn wir das machen, dann ist an dieser Position eins die Hieb-Eigenschaft,
die jetzt repariert. Denn der Wert war ja größer als das,
was vorher da drin stand und weil das der Größere der beiden Kinder ist,
ist der Wert auch größer, wie du das andere kennt,
was da drin war.
Das heißt, wir würden also jetzt sagen, okay,
was ist hier das größte Element? Da würde ich sagen,
es ist die acht von den dreien hier,
dann vertauschen wir die acht Mitte zwei, dann steht die oben,
die acht und hier steht die zwei.
Der Hieb ist immer noch kaputt,
wir haben ihn noch nicht repariert, aber die Stelle,
wo er kaputt ist,
ist eine Ebene nach unten gewandert
Ja. Okay, jetzt könnten wir hier gucken und sagen,
wir vergleichen das wieder mit den Kindern.
Wir nehmen das den Größeren der beiden Werte,
also die sieben, tauschen die nach oben,
steht hier die sieben, hier die fünf und die zwei.
Jetzt haben wir diese kaputte Stelle wieder eine Ebene nach unten gedrückt.
Und das Tolle dabei ist, wenn wir bei einem Blatt angekommen sind,
können wir gar nichts mehr kaputt gemacht haben.
Denn an einem Blatt kann die Hieb-Eigenschaft eigentlich nicht mehr verletzt sein,
weil hier kommen ja keine Kinder mehr. Das heißt,
auf diese Art und Weise können wir die Stelle,
wo die Hieb-Eigenschaft verletzt ist,
Schritt für Schritt nach unten schieben und spätestens nach Lock-End-Schritten haben wir
ein Blatt erreicht und an dem Blatt ist die Hieb-Eigenschaft automatisch erfüllt.
Also immer betrachtet Top-Down nach unten
zu den Kindern
Okay, also unsere Reparaturfunktion funktioniert also wie folgt. Wir bestimmen den direkten Nachfolger,
C, der den maximalen Wert hat,
also mit maximalen Wert A von C. Dann vertauschen wir AI mit AC,
vorausgesetzt, das ist nicht das Element selber.
Und dann rufen wir rekrusiv die Hipify-Funktion nochmal auf. Okay,
so, hier steht Hipify, also die Funktion, die die Hip-Eigenschaft korrigiert.
Wir beginnen, wir gucken, ob Left von I,
kleiner gleich der Heep-Size ist, das ist sozusagen der Abbruch,
damit wir nicht aus dem Array hinauslaufen, ja.
Und wenn das der Fall ist und A von Left von I
ist größer als A von I, dann wissen wir C,
Sync speichern wir das Minimum, soll Left von I sein.
Anderenfalls ist C gleich I. Also in C wollen wir das Minimum
der drei zahlen. Das I und von seinen beiden Kindern
bestimmen. Anderenfalls gucken wir, ob Ride von I,
kleiner gleich der Heep-Size ist und A von Ride von I
größer ist als A von C.
Wenn das der Fall ist,
dann steht das größte Element der drei an der Position Ride
von I und das merken wir uns hier in der Variablen
So, damit haben wir in C einen der Werte,
da steht entweder I drin,
Left von I oder Ride von I. Und da steht das drin,
wo der maximale Zahlenwert gespeichert ist.
Das steht in dem C drin. Also die Index,
der Index der Position mit dem maximalen Zahlenwert.
So, wenn das Ungleich I ist, dann müssen wir
die Werte vertauschen. Dann vertauschen wir den Wert,
der an der Stelle I steht mit dem Wert an der Stelle C.
Das ist sozusagen das Durchtauschen zwischen den beiden Ebenen.
Und dann rufen wir Hipify einfach auf der Position C auf.
Wir gehen also dann eine Ebene weiter und machen dann in
der unteren Ebene wieder die Hipify-Operation
Okay, tja, ich schließe mal die Tür ab um Viertel vor.
Nein, die ist auf, feste Drücken.
Ja. Okay. So, so funktioniert das und jetzt
ist die spannende Frage, wie lange dauert das denn?
Ja, wie lange dauert diese Operation? Grob geschätzt würden wir sagen,
Login habe ich gerade auch schon mal gesagt,
wo ich gesagt habe, ja, also diese Bäume haben ja Login-Tiefe.
Jetzt müssen wir einmal hier von oben nach unten in den
Baum runterlaufen, Login-Schritte. Ganz so trivial ist das nicht,
dass das Login wirklich ist und zwar liegt das an dieser Tatsache,
dass die Bäume nicht vollständig sind, sondern immer nur linksvollständig sind.
Das ist die Rekurrenzgleichung, die gilt. Also Tee-Heepify von N.
Wir haben jetzt klein N, viele Elemente, die wir speichern wollen.
Ist Teehipify von 2 N Drittel plus Tetter von 1, konstante Zeit.
Also, dass diese Funktion hier konstante Zeit hat,
das sieht man, glaube ich, sofort.
Ja, hast also keine Schleife oder irgendwas drin.
Man sieht, man hat einen rekursiven Aufruf hier,
Maxipify, deswegen steht hier kein Faktor davor.
Ja, ist auch offensichtlich. Aber wie groß der Teilbereich
ist, auf dem wir jetzt noch wieder weiterarbeiten müssen.
Das ist die Frage. Das ist nicht so ganz einfach
zu sehen. Und da kommt zwei Drittel N raus.
So, und jetzt werde ich versuchen, Ihnen klarzumachen,
warum das zwei Drittel N ist.
Dazu müssen wir uns mal einen Baum im Allgemeinen anschauen,
so einen linksvollständigen Baum und uns ein bisschen darüber im Klaren werden,
was die einzelnen Zahlen sind.
So ein Verständnis dieser Folie ist es ganz wichtig,
dass Sie jetzt im Kopf haben,
dass kleine N bezeichnet die Anzahl der Elemente,
die wir im Hieb speichern wollen.
Also wie viel das sind
17 oder so. Das große N,
das sehen Sie ja auch, das ist kein Tippfehler,
das große N bezeichnet die Anzahl der Knoten,
die der vollständige Baum der gleichen Höhe hätte.
Wenn da also alle Werte eingetragen sind.
Okay, also hier steht, was wir zeigen müssen.
Also Thiepify on N ist die Hipify von 2 N Drittel plus Tetter von 1. Okay,
so hier ist unser Baum mal so dargestellt und hier ist ein Beispiel für denjenigen,
für den das jetzt zu abstrakt ist.
Also wir gehen mal davon aus, wir haben einen vollständigen, binären Baum.
Der hat en viele Knoten, dann haben wir einen Wurzelknoten,
Und die restlichen Knoten sind auf die linke und die rechte Hälfte aufgeteilt.
Deswegen haben wir, wenn wir hier so einen Schnitt machen,
haben wir N minus eins,
halbe Knoten im linken Teilbaum und N minus eins halbe Knoten im rechten Teilbaum.
Wenn wir die addieren, sind das N minus eins,
plus die Wurzel, die gehört weder zum linken,
noch zum rechten, macht genau die N-Knoten, die der Binärbaum hat.
Das ist das eine, was wir sehen.
Wir können aber noch einen anderen Schnitt in unserem Baum reinziehen,
und zwar hier, und uns überlegen,
wie viele Blätter hat denn so ein Baum?
Das können wir auch angeben für einen Binärbaum,
so ein Binärbaum hat N plus eins,
halbe viele Blätter und N minus eins,
halbe viele Knoten,
die da drüber sind
Zusammen wieder N, ein minus eins,
halbe plus N, plus eins, halbes, wieder N.
Und was man gar nicht so denkt, der hat mehr Blätter
als innere Knoten. Das muss ich immer im Kopf haben.
Und zwar ungefähr die Hälfte. Ein bisschen mehr als die
Hälfte der Knoten sind Blätter in so einem Baum.
Jetzt können wir uns natürlich auch noch fragen,
wie viele Blätter sind im linken und im rechten Teilbau?
Das wäre dann einfach die Hälfte davon. Also N plus 1,
Vierte sind die Anzahl der Blätter im rechten oder im linken Teilbaum.
Wenn der vollständig ist. So, ein ganz einfaches Beispiel,
dieser Baum hier, der hätte jetzt vier, sechs,
sieben Knoten, wenn er vollständig wäre.
Also Großensel ist sieben,
sieben Knoten
Und mal angenommen, wir hätten fünf Datenelemente dort gespeichert,
links vollständig. Das sind die orangenen Kugeln hier.
Hier stehen dann Daten drin und es ist links vollständig.
Also jede Ebene wird gefüllt bis zu dieser Position.
Hier steht dann jetzt nichts. Also die beiden Positionen sind
nicht verwendet. So, hier ist der Baum geteilt,
hier ist der Wurzelknoten. N-1-1-halbe sind links,
drei Knoten, N minus 1-1-halbe sind rechts,
auch drei Knoten. Aber der hier ist ja nicht belegt.
Also die Schwarzen sind ja nicht mit Werten belegt.
Das heißt, der reale Baum,
die reale Anzahl von Werten in diesem Teilbaum,
ist kleiner als N minus 1-halbe
Und im schlimmsten Fall haben wir das Szenario wie hier,
dass nämlich der linke Teilbaum, so viele Elemente wie möglich hat,
da steht dann, das geht genau bis zu der Grenze.
Und der rechte Teilbaum hat die alle nicht.
Das ist die größte, größtmögliche Unbalance,
die wir erreichen können zwischen dem linken und dem rechten Teil.
Wenn wir noch weniger haben, dann gleichen die sich wieder aneinander an,
wenn wir mehr haben, gleichen sie sich auch wieder aneinander an.
Das heißt, in diesem Szenario haben wir die größte Unbalance
zwischen den Knoten links und rechts.
Und wenn wir jetzt überlegen, dass der Hibify-Algorithmus,
der geht ja an der Wurzel los und verzweigt hier irgendwie
und geht dann runter,
dann im Worst Case verzweigt er nach links
Einen größeren Baum. Und die Frage ist dann,
wie viele Elemente sind links gespeichert,
relativ zur Gesamtzahl der Knoten in dem Baum.
Das müssen wir ausrechnen. So, mit den Regeln,
die wir hier sehen, kann man das ganz leicht eigentlich machen,
muss jetzt nur ein bisschen rumrechnen.
Okay, so, jetzt müssen wir es ausrechnen.
Also, das Bild ist das Gleiche geblieben.
Auf der linken Seite haben wir den vollständigen Baum,
also inklusive der kompletten letzten Ebene mit Blättern.
Das sind N-minus eins, halbe, viele Knoten, die
da gespeichert sind. Also N ist der komplette Binär,
aber um N minus eins,
halbe Knoten ist der komplette linke Talbau und das
ist NL
Wenn er der rechte Teilbaum hier oder hier,
können Sie auch gucken, der ist im Prinzip eine Ebene kleiner,
der hat die Elemente hier unten nicht.
Deswegen hat er weniger Knoten, der hat N-minus 1,5
minus die Blätter, die hier unten noch sein könnten,
die Orangen, die fehlen da alle.
Also minus N plus 1 Viertel, N plus 1 halbe
für alle Blätter, die Hälfte davon N plus 1 Viertel.
Die müssen wir abziehen. Macht N-minus 3 Viertel.
Also N-minus 3 Viertel Elemente stehen in diesem Dreieck hier.
Und in dem Großen hier stehen N-minus 1 halbe.
Und wir müssen jetzt zeigen,
dass das Verhältnis von NL zu NR entsprechend diesem
1 Drittel 2
Drittel entspricht
Wir rechnen einfach die Gesamtteil aus. Wie
viele Knoten haben wir? Wir haben also den Wurzelknoten,
eins plus NL, die Knoten im linken Teilbaum plus NR,
die Knoten im rechten Teilbaum, können wir alles addieren.
Also wir wissen, was NL und was NR ist,
macht Eis plus N minus eins halbe.
Das ist NL plus N minus 1 halbe,
minus N plus 1 Viertel, das ist NR.
Also das ist alles NR, steht hier.
Und das ist das NL. Können wir addieren. Das
ist 3N minus 1 Viertel. So, das heißt,
im ungünstigen Fall haben wir 3N minus 1 Viertel Datenwerte,
die in einem wie Nährbaum gespeichert werden, der,
wenn er denn vollständig wäre, großennen viele Knoten hätte.
Okay, das können wir umformen und dann sehen wir,
dass das Großen gleich 4N plus 1 Drittel ist, drehen das einfach um.
Wir sagen einfach, wie viel Platz hat eigentlich der vollständige
binäre Baum, der kleinennen viele Daten speichert.
Also wie viele haben wir hier insgesamt? Da kommen die
dazu, macht 4N plus 1 Drittel. So,
und jetzt können wir dann ausrechnen,
jetzt würden wir einfach in NL,
setzen wir für Großen diese Zahl ein und dann sehen wir NL,
ist also N minus 1 halbe, das stand hier oben.
Für N setzen wir 4N plus 1 Drittel ein
Ja, so, jetzt müssen wir ein bisschen Bruch rechnen,
um das rauszufinden. Und dann steht hier zwei Drittel N
minus ein Drittel, wenn wir ein bisschen rumrechnen.
Und das können wir abschätzen. Dieses Drittel lassen wir mal
weg. Das ist kleiner gleich, zwei Drittel N. So,
und hier steht genau die Beziehung,
die wir zeigen müssen, wir müssen, wir zeigen,
dass die Anzahl der Knoten im linken Teil,
warum in dem größeren der beiden Teilbäume,
kleiner gleich zwei Drittel der Anzahl der Knoten ist,
die wir vorher hatten.
Und das ist diese Rekurrenzgleichung hier.
Also damit zeigen wir das Verhältnis des Linken zum rechten Teil
in der Größe
Und ja, damit können wir zeigen,
dass der Algorithmus N-Log-in, dass die Hiebify-Operation Log-in Zeit hat,
weil diese Rekurrenzgleichung, dürfen sie wieder ins Mastertheorie einsetzen,
mache ich hier nicht, dürfen sie, wenn sie wollen,
dann kriegen sie raus, kommt Log-in raus.
Block-in-Side. Okay, das ist die Analyse, das ist der schwierige Teil,
um zu verstehen, dass dieser Algorithmus wirklich effizient ist. Okay,
so, jetzt zusammengefasst, wie machen wir jetzt unsere Extract-Max-Funktion,
wie benutzen wir es in Hieb, den benutzen wir ganz einfach so,
Xtract Max speichert in M das erste Element hier,
Wir nehmen einfach das letzte Element in dem Hieb,
also bezüglich dieser Array-Folge und kopieren das einfach hier oben rein,
an die Position 1.
Damit haben wir sichergestellt,
schon mal, dass der Hieb links vollständig ist,
weil wir haben einfach das letzte Element da weggenommen,
dann ist er wieder links vollständig.
Okay, das haben wir gemacht. Der Hieb ist jetzt
um der Größe um eins geschrumpft und wir wissen,
der Hieb ist an einer Stelle verletzt, nämlich an der Position 1.
Da ist er kaputt. Da haben wir das Element reingeschoben,
ohne zu gucken, welchen Wert das überhaupt hat.
Und dann rufen wir Max Hipify an der Position 1
auf und mit der Max Hipify-Operation hier,
Also erst schieben wir das Element da hoch.
Das achte Element ist weg. Das haben wir ausgegeben.
Hier steht jetzt eine Eins. Hier ist die Hieb-Eigenschaft verletzt.
Hieb-Ify würde diese Eins jetzt durchschieben. Als erstes würde die
Sieben nach oben wandern, die Eins nach hier runter.
Und dann hätten wir die Eins hier stehen und dann rekursiv wieder aufgerufen,
würde die Fünf hier hochwandern, die Eins hier runter.
Das ist der neue Hieb. Okay. Damit können wir
in Log and Side ein Element aus dem Hieb herausnehmen und den Hieb wieder reparieren.
Damit können wir im Hieb-Sort in Log and Side das Maximum bestimmen.
Ja. Und werden hier in Place O von N-Log
and Laufzeit erreichen
Gut, Zeit ist um. Wir werden den Rest von dem,
wir werden uns noch ein bisschen mit der Prioritätswarteschlange auseinandersetzen und mit dem Wildhieb,
das haben wir ja noch nicht gesehen,
das machen wir dann am Freitag