Schleife, mache das, was ich gerade beschrieben habe,
solange also wir nicht an der Wurzel angekommen sind und
die Hieb-Eigenschaft verletzt ist zwischen I und seinem Vorgänger,
vertauschen wir einfach die Werte von I und den Wert von seinen Vorgänger.
Und dann gehen wir zum Vorgänger und machen das nochmal.
Es ist hier nicht rekursiv aufgeschrieben, sondern durch eine Wild-Schleife ganz einfach.
Okay, gut, damit können wir die Werte vergrößern
und haben im Prinzip schon alles, was wir brauchen.
Hier sehen Sie noch eine Inside-Operation, einfach realisiert über Increase Key.
Das heißt, wir machen die Heebsize um eins größer,
wir fügen am Ende des Heebs ein Element ein mit
dem Wert
minus unendlich
Also mit den kleinstmöglichen Wert. Und damit ist sichergestellt,
der Hieb ist korrekt, weil wenn es der kleinstmögliche Wert ist,
kann der Knoten die Hieb-Eigenschaft nirgendwo verletzen.
So, und dann rufen wir einfach unsere Increase-Key-Operation aus und
die würde das dann reparieren, die wir gerade schon gesehen haben.
Okay, also mit dem Hieb können wir eine Prioritätswarteschlange realisieren,
mit Login-Laufzeit, also bei N-Elemente, die gespeichert werden in der Priorität,
zweite Schlange, für die Operation einfügen, löschen,
erhöhen, erniedrigen, Maximum extrahieren.
Das sind die fünf Funktionen, die man bei
einer Prioritätswarteschlange hat
Eine Sache geht nicht. Frage. Na kurz,
wie das nennt sich, wird man so was umsetzen.
Ach, das lernen Sie in der Software-Technik. Ja,
wie würden Sie das machen? Meistens kennt man von der
Problem- und Fragestellung, die man hat, kennt man den Wertebereich.
Und wenn man in einer guten Situation ist,
weiß man, dass es noch einen kleineren Wert gibt.
Das ist hier auch eigentlich gar nicht so wichtig.
Das ist hier, wenn Sie sich vorstellen,
Sie können hier einfach auch den Wert X reinschreiben und Increase Key aufrufen.
Das ist einfach nur zur Symbolisierung. Also wir schauen Algorithmen
hier an und haben in der Regel immer die Korrektheit im Vordergrund.
Und wenn Sie es so aufschreiben, dann können Sie nach
jeder Zeile immer sagen, es ist alles richtig.
Das ist der einzige Hintergrund
Okay. Gut, die eine Sache,
die nicht geht und die eigentlich auch vorkommt, also in den Anwendungen ist,
dass sie nicht die Position des Elements kennen, sie kennen nur den Wert.
Die sagen so, oh wann, irgendwo in meinem Hieb,
da ist ein Element mit dem Schlüssel 18 oder 7,
ja, und sie sagen, das,
was jetzt vorher sieben hat,
soll jetzt mal fünf kriegen oder so.
Das geht nicht, ja. Diese Operationen können sie im
Hieb nicht effizient realisieren. Warum nicht? Weil sie können
das Element
nicht finden
Der Hieb ist kein Suchbaum. Man hat immer das Gefühl,
es wäre auch so ein Suchbaum und sie könnten irgendwie schnell die Objekte finden.
Können sie aber nicht. Ja, wenn sie also ein
Element suchen, müssen sie mehr oder weniger das durchsuchen.
Es gibt natürlich ein paar Regeln, wenn sie mit der Suche
aufhören können. Aber im schlimmsten Fall brauchen sie OVNN-Zeit,
um das Element zu finden in dem Hieb.
Also wenn sie quasi den Wert suchen in der Datenstruktur,
dann kommen sie mit dem Hieb nicht klar.
Und dann brauchen sie was anderes. Dann müssen sie erst eine Datenstruktur haben,
die in den Index sagt, wo der Wert steht.
Das macht einen Suchbaum. Ja, den haben wir hier
noch nicht gesehen. Das ist nur die Prioritätswahl der Schlange
. Das ist der eine Punkt, wo dann die Prioritätswarteschlange
als Datenstruktur sich nicht mehr eignet in der Anwendung.
Wenn man diese Operation braucht
Okay, gut. Das ist alles, was es
zu sagen gibt zum Heepsort. Und wir können auch mit
großen Schritten voranschreiten. Und wir kommen zu dem Sortieralgorithmus,
der heute quasi der am häufigsten eingesetzte ist, nach meiner Schätzung.
Ich kenne keine Statistiken, aber ich gehe mal davon aus,
es ist der, der am häufigsten eingesetzt ist.
Ist der Quick-Sort. Und den Quick-Sort haben die allermeisten von
ihnen schon gesehen. Der kommt,
den zeigt mein Kollege in der Regel auch in SE1,
also ein Beispiel, wenn es um Programmierung geht.
Und da sehen sie schon,
wie der abläuft
Läuft. Der Quickshot ist eigentlich vom Aufbau her ein ganz einfacher Algorithmus,
stammt schon aus den 60er Jahren und ist ein Devide-and-Konker-Verfahren.
Wir haben schon ein Devide-and-Konker-Verfahren zum Sortieren gesehen.
Das war der Merch-Sot. Der Merch-Sot ist so vorgegangen,
der hat geteilt, rechts und links,
rechts und links sortiert und dann hatte der diese komplizierte Mischfunktion,
die dann die beiden sortierten Listen genommen hat und die zusammengebaut hat zu einer Liste.
Der Quickshot macht eine Sache anders. Er teilt erst und
beim Teilen sorgt er dafür, dass auf der einen Seite
alle kleinen Elemente sind, auf der anderen alle großen.
Mit dem Unterschied, dass wenn wir jetzt rekursiv aufrufen,
müssen wir nachher nicht mehr mischen, weil wir wissen ja schon,
dass auf der linken Seite alle kleineren Elemente sind,
auf der rechten Seite die größeren.
Okay, so funktioniert der. Also die Idee des Quicksorts
ist, wir teilen die Folge in zwei Teilfolgen.
Die erste geht von Eis bis irgendeine Position Q. Minus eins,
die nächste von Q plus eins bis N und die Eigenschaft ist,
dass alle Elemente AI, die kleiner gleich,
die mit der Position i kleiner als Q,
da muss der Wert AI auch kleiner gleich aku sein.
Und alle Werte in dem Aray oberhalb der Position Q,
also mit I größer,
glaube ich,
größer als
Da muss das AI auch größer gleich Akku sein.
Diese Eigenschaft müssen wir herstellen und wir müssen das passende Kuh finden dafür.
Also wir müssen dann sagen, wo das, weil für
welches Kuh das entsprechend gilt. Das macht eine spezielle Unterfunktion,
die steht hier schon, die ziemlich berühmt,
das ist eigentlich der Kern des Quicksorts, das ist die Partition-Funktion.
Die Partition-Funktion bestimmt dieses Kuh und rearrangiert das Array,
sodass beide Teile entsprechend diese Eigenschaft hier oben erfüllen.
Also links von Kuh, die kleinen, rechts von Kuh die Großen.
Das heißt nicht, dass die da drin sortiert sind,
das heißt nur, dass alles, was kleiner ist,
also dieses eine Element links ist,
alles andere auf der rechten Seite
So, und dann ist der Quickshot ganz einfach.
Wir haben mal so drei Eingaben hier, weil wir den rekursiv aufrufen.
Wir haben das RAR, wir haben die linke und die rechte Grenze.
Und der Quickshot sortiert also den Bereich zwischen L und R. Ja,
und er macht das, indem er erst mal guckt,
dass dieser Bereich, den er sortieren muss,
auch mehr als ein Element enthält.
Ja, L muss echt kleiner sein als Kuh.
Und dann ruft er Partition auf und teilt entsprechend das Array auf.
Da kommt als Ergebnis die Position Kuh raus, für die diese Eigenschaft hier gilt.
Und dann müssen wir nur rekursiv den Quickshot zweimal aufrufen.
Einmal für den linken Teil, das machen wir hier und
einmal für den rechten Teil, das machen wir hier.
Ja, und am Anfang rufen wir den Quickslot auf mit
dem Array und den Eric-Grenzen 1 und A.Lenk,
also bis zum Ende des Arrays.
Okay, ganz einfacher Algorithmus. Bis auf dieses Partition,
das ist in der Tat da,
muss man noch mal überlegen, wie das genau funktioniert.
Aber abgesehen davon ist es ein relativ einfaches Verfahren.
So, hier ist das Partition so,
wie das Huara, der Autor, entsprechend entwickelt hat.
So steht das in seiner Originalbeschreibung des Algorithmus
Das geht jetzt wie Folge. Also wir haben das RAR,
wir haben L und R, linke und rechte Grenze.
Wir können bei den Partischen nicht das Kuh vorgeben.
Das ist das große Problem am Quicksort. Wir können nicht sagen,
wo das sein soll, weil wir das nicht wissen.
Sondern was wir machen, ist, wir sagen,
dass die linke und rechte Grenze und wir versuchen,
dass Array so aufzuteilen,
dass diese Eigenschaft gilt und als Ergebnis kommt die Position raus,
für die das gilt.
Wie macht man das? Also es wird hier gemacht mit
so einer Steife, wo zwei Repeat-Schleifen eingebaut sind.
Wir haben zwei Indie-CSI und J. I ist am Anfang links
vom linken Rand. Also L und dann nehmen wir noch
eins weniger, also links davon.
Und J ist auf der Position des rechten Randes
So, das erste, was wir machen, ist,
wir überlegen uns, welches Element soll das Element sein,
was nachher die beiden Mengen teilt.
Da müssen wir uns entscheiden. Wir nehmen einfach das,
was am rechten Rand steht. Das nehmen einfach das hier,
sagen einfach, dass am rechten Rand ist. Das,
was wir nehmen, um diese Menge zu teilen in zwei Hälften.
So, wie gehen wir dann vor? Wir haben
diese Wildschleife. Wir gehen mit dem Zeiger I hier,
mit dem Index I, laufen wir von der Position L
minus 1. Immer weiter nach links in das Array hinein,
indem wir hier das immer inkrementieren.
Und mit J laufen wir von der oberen Position
eher nach unten
Ja, das macht diese Repeach-Leife hier. Und zwar machen wir
diese beiden Schleifen so lange, bis unsere Eigenschaft verletzt ist.
Also so lange, bis das AI größer gleich AR ist.
AR ist das Pivot-Element. Und die zweite Schleife so lange,
bis das AJ kleiner gleich AR ist.
Ich zeige ihn gleich im Beispiel, für die,
die den Quick-Shot noch nicht gesehen haben,
dann kann man das relativ leicht nachvollziehen, was passiert.
Wenn wir die gefunden haben, dieses I und J,
dann gucken wir, ob I kleiner ist als J,
die könnten ja auch so aneinander vorbeigelaufen sein.
Das tun die auch tatsächlich irgendwann. Und wenn I noch
kleiner ist als J, dann vertauschen wir diese beiden Elemente
AI und AJ
Und ganz am Ende, also hier ist die Wildschleife dann zu Ende,
an dieser Stelle, vertauschen wir das Element AI mit dem Element AR.
Weil in AR steht das Pivo-Element und das schieben wir
jetzt in die Mitte zwischen den Teil der Folge,
der die kleinen Elemente enthält und den Teil der Folge,
der die großen Elemente enthält.
Okay, und dann gehen wir die Position I zurück.
Und das ist die Position, wo jetzt unser Pivo-Element steht.
Und da haben wir genau unsere Zieleigenschaft. So,
im Westen schaut man sich das an einem Beispiel an,
das an, das müssen Sie sich jetzt von oben nach unten vorstellen,
wie das jetzt läuft.
Also wir haben hier ein Array, wir haben acht Elemente,
hier sehen Sie unsere Zahlenfolge.
Ja, die ist am Anfang unsortiert. Wir rufen jetzt
die Partition auf für den ganzen Bereich, also von El bis Er.
Das I ist am Anfang quasi an der Position Null,
also vor diesem Array, das steht bewusst hier vor dieser Position.
Und das J steht auf der Position eher.
Im Prinzip steht das auch hinter dem Bereich,
den wir betrachten, weil an der Position eher steht ja
das Pivo-Element
Also den Bereich von drei bis eins wollen wir jetzt aufteilen.
In den linken und rechten Bereich mit der Eigenschaft,
dass auf der linken Seite alles kleiner sein soll als AR,
also als vier. Und auf der rechten Seite alles größer
sein soll als AR, also diese vier. So,
wir gucken jetzt von der linken Seite und gucken immer,
ob dieses Element I diese Eigenschaft hat,
dass sie kleiner gleich dem AR ist.
Wenn ja, hier drei, dann gehen wir einfach eins weiter.
Und dann kommen wir hier auf die Position,
wo die acht steht. Und dann sehen wir,
das ist größer als AR.
Hier steht ja
eine vier
Also für dieses Element hier Arzt weiß,
die Eigenschaft verletzt und das machen wir mit J auch.
Da gehen wir hier von dieser Position nach rechts und da sehen wir,
hier ist die Eis und da sind wir die Eins,
ist kleiner als das ARC4, ist also auch an der falschen Position.
Wir haben also jetzt zwei Elemente gefunden, die nicht an der richtigen Stelle sind.
Das hier ist rechts von irgendwie dem Bereich,
wo alles kleiner sein soll und das von dem, von der Trennung,
von dem Pivo-Element und das ist links von dem Pivo-Element.
Da vertauschen wir die einfach beide, die beiden Elemente hier,
das ist die Swap-Operation und dann laufen wir weiter nach hinten innen,
dann finden wir die 14 Das ist die Position I,
das Element, was wieder größer ist als das Pivo-Element.
Wir laufen von hier nach einer zu kleineren Indizes so runter auf die Position 2.
Die haben alle, die waren alle entsprechend größer als die 4.
Da mussten wir nichts machen hier. Die beiden vertauschen wir
wieder. Und irgendwann sind wir dann an, sind die
beiden Positionen aneinander vorbeigelaufen und wir terminieren die Schleife beendet.
Es ist beendet. So, dann vertauschen wir an
diese Position I, das Element eher. Wir tauschen also
die 4 mit dieser 14, die steht dann hier.
Und jetzt sehen wir, dass unsere partischen Funktionen,
dass Eric korrekt aufgeteilt haben.
Wir haben hier das Element mit der 4 stehen.
Links ist alles kleiner gleich 4 und rechts ist alles
größer gleich
Ja, okay. Ja, so funktioniert ein paar Tischen. Und die,
die sich erinnern können, können wir mal sagen,
ist das die Variante von ein paar Tischen,
die sie in SE1 gesehen haben?
Nein. Okay, dann kennen sie die andere.
Die kommt gleich noch. Genau genommen kommt die jetzt.
Die, das ist die ursprüngliche Funktion, also das,
was der ursprüngliche Autor sich ausgedacht hat.
Und ist auch eigentlich okay. Ist auch nicht langsamer
als irgendwas, aber sie hat halt diese drei verschachtelten Schleifen
Das muss man nicht haben. Man kann das auch einfacher machen.
Und das ist quasi die Partition-Funktion, die heute typischerweise eingesetzt wird.
In einem Quickshot, die hat nur eine Vorschleife,
die läuft nur einmal von links nach rechts über das Array.
Kennen Sie schon diese Algorithmen, die das machen, die ein Problem lösen,
indem Sie einmal von links nach rechts da drüber laufen.
Scanline-Verfahren, die machen das. Und das ist ein paar
Tischen-Algorithmus, der quasi auf dem Scann einfach voran passiert.
Okay, wie funktioniert der? Der funktioniert so,
der teilt dieses Array streng genommen in vier Bereiche ein
Ganz hinten, mit dem es, kann man leicht anfangen,
an dieser Position steht das Pivo-Element, genau wie eben auch.
Das ist also nur zum Parken des Pivo-Elements. Dann haben wir wieder zwei Positionen,
I und J. Die haben nur jetzt eine andere Bedeutung,
also hier I und J. Und zwar bedeuten die jetzt Folgendes,
bis zur Position I haben alle Elemente die Eigenschaft,
dass sie vom Wert kleiner gleich dem X sind,
also dem Wert des Pivo-Elements hier hinten.
Und in dem Bereich von I plus 1 bis J oder
J minus 1, das hängt gerade davon ab,
wo wir genau sind in dem Algorithmus.
Also bis hier haben die Elemente die Eigenschaft,
dass die alle größer sind als
Ja, und dann haben wir den weißen Bereich und das
sind die Elemente, die für uns noch unbekannt sind.
Da wissen wir nicht, ob die kleiner oder größer sind.
Und das Scanline-Verfahren verkleinert diesen weißen Bereich. Am Anfang ist alles weiß,
wir wissen nichts. Und für jedes Element entscheiden wir dann,
wo es hingehört und schieben dann die Position J1 weiter.
Okay, also hier nochmal die vier Bereiche.
Habe ich Ihnen erzählt, die Schleife,
also die Position I ist am Anfang wieder el--1,
verstehen also hier vor dem letzten Element.
Jetzt die Schleife J, also die Vorschleife hier,
die geht von El bis R minus 1,
also von hier bis zu dem letzten Element.
Wenn das AJ kleiner gleich dem AR ist,
dann erhöhen wir den Wert von I und vertauschen AI
mit AJ
Ja. Und dann geht die nächste Vorschleife,
hier Iteration der Vorschleife und das J wird entsprechend erhöht.
Ja, hier ist ein Beispiel, wo man sich das nochmal gut vorstellen kann.
Der Pseudokutsch steht hier einfach nochmal. Also wenn wir an
irgendeinem Zeitpunkt hier in der Bearbeitung dieser Schleife sind,
ist die Situation eben so. Wir haben die Elemente von der Position,
bei uns heißt das immer L, hier im Commons,
auf dem Bild war das P. Ja,
also L, jetzt müsste hier stehen,
also von L bis I einschließlich, alles kleiner gleich X,
der Bereich von I plus 1 bis J minus 1.
Hier ist alles größer als X. J gucken wir uns gerade an.
Das ist entscheidend, was wir mit dem machen.
Und hier hinten an der Position R steht das Pivo-Element
Wenn dieser Wert, der hier steht, größer ist als
unser X, dann müssen wir eigentlich nicht viel tun.
Da müssen wir eigentlich genau gar nichts machen.
Da müssen wir einfach nur die Position J1 nach rechts schieben,
weil wir wissen ja, dieses Element erfüllt die Eigenschaft,
dass es größer ist als X. Sie sehen auch hier,
wenn, das ist die IF-Bedingung, wenn das größer ist als X,
ist die IF-Bedingung falsch und wir machen eigentlich gar nichts.
Wir gehen mit der Position J01 weiter, in der Vorsteife.
Wenn der Wert klarer gleich X ist,
dann müssen wir was tun, was wir machen,
ist, wir vertauschen zwei Werte und zwar den Wert an
der Position iPlus 1 und den an der,
nee,
den an der Position i,
mit denen an der Position AJ
Und das I wird vorher um eins erhöht.
Also wir gehen eins nach rechts. Dieses Element ist größer,
denn das ist ja in dem dunkelgrauen Bereich.
Und wir tauschen das sozusagen mit dem an der Stelle J.
Und dann können wir die Grenze I um eins nach rechts schieben.
Die ganze J natürlich auch. Und haben wieder alles korrekt.
Also alles, was kleiner gleich I ist in der Position,
ist kleiner gleich X. Alles, was zwischen I und J ist,
ist größer als X.
So, und dann laufen wir hier durch.
Und wenn wir am Ende des Arrays angekommen sind,
haben wir auch die Partitionierung erreicht. Können das mittlere Element,
also das Pivo-Element in die Mitte kopieren,
an die Position I. Na,
I plus 1. Und
dann sind wir fertig
Ist das das Partition-Verfahren, was sie in SE1 gesehen haben?
Doch, das erste. Okay, gut. Also sie gibt
diese beiden. Und das ist einfach nur ein bisschen kompakter.
Aber es ist von der Laufzeit her kein großer Unterschied.
Mhm. Amt, ey, jetzt nur noch das erste Telement.
Das würde ja im ersten Aufruf sozusagen auf das Zeld
davor geteilt werden. Äh... Nee,
also das ist einfach nur die Initialisierung, sobald sie,
also bevor sie auf das I
überhaupt mal zugreifen,
wird es
hier erhöht
Definiert, dass es die gleichen Dinge wie das eine sind.
Das ist ja schon einmal ein Feld. Du zeigst davor,
wenn das Elt als Elt. Genau, hier ist das
da. Also das zeigt ja dann auch. Nee,
das läuft, oh, das ist blöd, das ist ein L.
Das ist ein L. Oh, das ist, hier ist eine Eins.
Das ist okay. Das ist eindeutig ein Manko dieses Fonds
. Das ist nicht so gut. Können Sie das optisch
unterscheiden hier? Okay, gut. Okay. Gut
Noch eine Frage, oder? Noch eine Frage. Das
ist wirklich der weiße Fehlerwille, wenn es um was geht.
Ja, also momentan haben wir, die Frage ist,
ob wir noch irgendwas machen müssen, geschickt hat, das Pivo-Element wählen müssen.
Das müssen wir noch. Damit werden wir uns gleich noch
auseinandersetzen. Im Moment haben wir einfach das Letzte genommen.
Aber das muss ja keine gute Wahl sein.
Und da werden wir uns gleich mit beschäftigen mit dem Pivo-Element.
Jetzt geht es erstmal nur um den Algorithmus im Allgemeinen.
Und was sie sofort sehen sollten,
sowohl in dem als auch in dem anderen Algorithmus,
dass es Partition ist Linearzeit, also Linearzeitalgorithmus.
Also OVNN, wenn wir N-Elemente durchsuchen, eine Vorschleife,
die das einmal durch das Array durchläuft
Okay, so, jetzt kommen wir zur Laufzeit von Quicksort.
Und jetzt wird es tatsächlich knifflig. Das Knifflige am Quicksort
ist nicht der Algorithmus. Das Knifflige am Quicksort ist die Laufzeitanalyse.
Denn das Dumme ist, dass wir nicht genau wissen,
wo dieses Kuh ist. Wenn wir davon ausgehen,
dass es dumm gelaufen ist, dann war das Kuh vielleicht
das Maximum aus der ganzen Menge der Zahlen.
Dann ist nach dem Partition-Algorithmus ist dieses Kuh immer noch an der letzten Stelle.
Und der linke Bereich des Aris ist ganz groß,
ein minus eins viele Elemente und der Rechte ist eigentlich
nichts drin
Wenn es gut läuft,
dann ist dieses Büroelement ungefähr in der Mitte vom Wert her,
von den Werten, die wir sortieren wollen.
Wenn es in der Mitte ist, dann hätten wir
den Fall, dass beide Teil-Arrays gleich groß sind.
Und dann haben wir, dann wird das effizient sein,
das Verfahren. Aber wir wissen es nicht. Also,
weil wir können ja nicht sagen, nimm mal das Element,
was den mittleren Wert hat.
Wir wissen ja nicht, wo das steht.
Noch wissen sie nicht, wo das steht.
Demnächst werden sie das Wissen, noch wissen sie das nicht.
Also, das heißt, unsere Rekurrenzgleichung ist relativ kompliziert,
die sieht zwar nicht so schwierig aus,
aber die ist kompliziert.
Die hält nämlich noch
das Kuh
Da steht also die Laufzeit zum Sortieren für N-Elemente,
ist die Laufzeit, die wir brauchen, um cu-1-Elemente zu sortieren.
Das ist der linke Teil. Und dann um plus die Zeit,
die wir brauchen, um N-Cu-Elemente zu sortieren,
das ist der ganze rechte Teil, plus 10 mal N,
eine Konstante mal enden.
Das ist die Zeit für Partition. Zehnmal N ist Partition
. Und das sind die beiden rekusiven Aufrufe. Und das
Dumme ist, wir wissen nicht, wo Ku ist.
So, wir setzen mal was ein. Wir setzen mal
ein Kuh gleich N. Ja, also es bedeutet,
wir nehmen dummerweise immer das Maximum. Das würde zum Beispiel auftreten,
wenn wir den Algorithmus nicht modifizieren, wir lassen den so,
wie der ist, der nimmt immer das letzte Element und die Folge wäre sortiert.
Wir starten den Quickstart auf der sortierten Folge und wenn wir das machen,
dann würde der Partischen-Algorithmus sich entscheiden,
das letzte Element zu nehmen, das wäre das Größte,
der würde gar nichts vertauschen und am Ende wäre die linke Folge N-1-Elemente groß,
alles außer das Pivot-Element und die rechte Folge wäre leer.
Das heißt,
wenn das passiert und wir würden immer das Maximum wählen,
dann hätten wir die Regurrenzgleichung T von N,
gleich C von N minus 1 plus T von 0,
also die rechte Seite wird irgendwie nicht plus zehnmal N für Partition.
Und da können sie das, können sie ganz leicht ausrechnen.
Das können sie einsetzen. Und dann steht hier C,
mal N plus N minus eins bis N
minus zwei und so weiter, bis plus eins.
Das sollten sie kennen. Das ist einfach nur die Summe
der Zahlen von eins bis von eins bis N.
Das ist N-Quadrat. Das ist dumm, ja,
weil wir wissen schon, dass wir in O von N-Log N-Site sortieren können.
Und der Quicksort erreicht das nicht. Ja,
der Quicksort ist hier erstmal ein N-Quadratsortierverfahren. Kein Deut besser
als der Selection-Sort, den wir ganz am Anfang der Vorlesung
gesehen haben im Okalkül
Nun, jetzt könnte es natürlich sein, dass das Frivo-Element nicht da liegt.
Und mal angenommen, es liegt ungefähr in der Mitte der Folge.
Dann würden wir sagen, Kuh ist ungefähr in halbe,
ja, auf- oder abgerundet. Dann wäre die Rekurrenzgleichung Tee von N
gleich Tee von N halbe, minus eins plus T von N-N-Halbe.
Das ist dann also linker rekursiver Aufruf,
ungefähr in halbe viele Elemente, rechter rekursive Aufruf,
ungefähr eine halbe Elemente,
plus Zeit für Partition,
zehnmal N. Wäre also ungefähr zweimal Tee von N halbe plus zehnmal N,
wenn man mal diese Rundungen und so außen vor lässt,
ist Tetter von N Login
Okay, also wenn das Pivo-Element an der richtigen Stelle liegt,
ist der Quicksort ein N-Log-End-Verfahren. Genauso schnell wie der Heep-Sort
. Übrigens auch ein In-Place-Verfahren. Hätte also von der Qualität
her, wäre er dann so wie der Heep-Sort.
Aber wir wissen das nicht. Und wenn Sie jetzt überlegen,
ich könnte genauso gut sagen, wir nehmen den Selection-Sort.
Und wenn das Maxi das nächstgrößere Element an der Stelle Log-End
ist in dem Array, dann habe ich Ovon End in Log-End-Laufzeit.
Ja, könnte ich ja sagen. Das heißt,
wenn ich solche Annahmen mache, wie die Teilfolge aufgebaut ist,
kann ich natürlich eine schnellere Laufzeit erreichen.
Aber hier, der Algorithmus hat im Worst Case ein Quadrat.
Tetter von N-Quadrat
Was man jetzt typischerweise macht, ist, dass man dann sagt,
ja, Sie haben ja schon so in diese Richtung gedacht.
Dann sagt man so, ja, ich nehme nicht das letzte Element.
Ich nehme das letzte, ich nehme das erste Element.
Ja, dann finden Sie auch eine Folge, wo das ungünstig ist.
Sie können auch sagen, ich nehme das mittlere Element,
dann finden Sie auch eine Folge, wo das ungünstig ist.
Was man ganz gerne macht beim Quicksort, ist,
heißt Medien of three, also Median von drei, Sie nehmen das Erste,
das mittlere und das Letzte und gucken dann, welches sortieren
die nach der Größe und nehmen dann das Mittlere davon.
Aber auch dafür kann ich Ihnen Folgen konstruieren und Sie brauchen
wieder Endquadratzeit
Ja, das heißt, das ist ein Endquadratverfahren.
Trotzdem ist was anders. Also trotzdem ist etwas anders beim Quicksort
als beim Selection Sword. Und jetzt müssen wir etwas machen,
was wir nicht mehr so häufig tun werden in der Vorlesung,
weil diese Art der Analysen immer viel schwieriger ist.
Wir müssen uns mal dem mittleren Fall angucken,
weil es nützt uns nicht zu sagen,
dass es irgendeine Folge gibt, wo das der Algorithmus langsam ist.
Ja, wann kommt denn diese eine Folge vor?
Ja, wann haben Sie denn eine sortierte Folge?
Wahrscheinlich nicht. Und wenn, dann würden Sie es vielleicht
vorher prüfen oder so. Dann haben Sie eine fast sortierte
Folge. Also Sie müssen eigentlich mal eine Aussage über den
mittleren Fall machen
Das ist schwierig, das werden wir jetzt gleich tun,
damit sie das mal gesehen haben, wie man sowas macht.
Aber bevor wir das machen,
machen wir mal eine Abschätzung, was wir erwarten können.
Und zwar machen wir es mal folgendes.
Wir gehen mal davon aus, wir haben irgendeinen Faktor A.
Und der ist irgendwo zwischen 0,5 und 1. Und wir sagen,
im Schnitt wird da so das Pivo-Element liegen.
Wenn das so ist, also Kuh wäre A mal N.
Also wäre das jetzt 0,5 wäre, dann hätten wir den Idealfall.
Und wenn das eins wäre, dann hätten wir den schlechtesten Fall.
Also irgendwo da wird das wohl so sein.
Kleiner als 0,5 macht keinen Sinn mehr,
dann können wir
diese beiden Therme einfach folgen
Also wir sagen, die größere Hälfte ist jetzt einfach mal links.
Und das liegt irgendwie zwischen 0,5 und 1. Und was kommt
denn dann raus, wenn wir diesen Parameter Aha verwenden?
Dann können wir wieder die Rekurrenzgleichung aufschreiben. Steht da Tee von N,
gleich Tee von A mal N, irgendwie gerundet,
plus Tee von 1 minus A mal N wieder gerundet,
die andere Hälfte, plus 10 mal N, Zeit für Partition.
Okay, das ist auch nicht so einfach auszurechnen,
aber wir können das machen.
Und zwar machen wir das jetzt auch gar nicht ausführlich,
weil wir wollen das ja nur als so eine Abschätzung verwenden.
Wir schauen uns
den Rekursionsbaum an dazu
Und dann können wir sehen, wie viel Zeit das kosten wird.
Das ist der Rekursionsbaum. Und zwar für A gleich 0,9.
Das ist schon ein sehr unbalancierter Fall.
Also wenn wir A gleich 0,9 annehmen, würde das bedeuten,
in jedem Schritt des Quicksorts sind 90 Prozent der Daten in
der linken Folge und nur 10 Prozent der Daten in der rechten Folge.
Es ist also schon sehr unbalanciert. Und selbst in dem Fall
können wir gucken, dann würden wir sagen, okay,
am Anfang haben wir den Aufwand N für das Partition.
Dann haben wir zwei rekursive Aufrufe. Auf der linken Seite haben wir jetzt,
ja gut, hier ist die linke Seite, die kleinere.
Ein Zehntel N, das ist die kürzere Folge.
Und auf der rechten Seite hätten wir
neun Zehntel
Also 90 Prozent der Daten rechts, als 10 Prozent der Daten links.
So, und in der nächsten Rekursionsebene hätten wir dann hier ein Hundertstel,
also 10 Prozent, und dann wieder 10 Prozent.
Und hier hätten wir 9hundertstel, hier auch 9hundertstel, hier 81hundertstel.
Sie sehen hier auf der rechten Seite, bleibt immer der größere Teil des Arrays.
Ja, der bleibt da immer übrig. So,
und nach Logorithmus zur Basis 10 von Endschritten wäre der Rekursionsbaum hier am linken Ende zu Ende.
Also wir nehmen Zehntel, Zehntel, Zehntel,
das können wir genau, Logorithmus zur Basis 10 von End mal machen.
Und auf der rechten Seite hier, da dauert das natürlich viel länger,
da brauchen wir Logorithmus von Neuntel oder wenn sie so wollen,
eins durch A, von N, ja,
um hier bis zum Ende zu gelangen.
So, wenn sie die Zeiten addieren, pro Ebene des Rekursionsbaums,
so haben wir ja die Analyse gesehen mit dem Rekursionsbaum,
dann sehen sie, okay, das ist hier Konstante mal N.
Hier haben sie ein Zehntel plus 19., sind auch wieder zehnmal N.
Und wenn sie die hier addieren, kriegen sie auch
gerade wieder zehnmal N raus. Es ist auch logisch,
weil sie die Aris ja aufteilen und in der Summe sind natürlich alle Elemente da irgendwie erstmal einmal vertreten.
Da kommt also immer N raus, also Konstante mal N. Und irgendwann ab dieser Position,
wenn der Rekursionsbaum hier links abbricht und nicht mehr weiterläuft,
werden das mal weniger als N, aber nie mehr,
weil das ist dann der Teil des A-Race, der schon vollständig sortiert ist,
der wird dann nicht mehr weiter betrachtet werden müssen.
Es ist immer kleiner, gleich zehnmal N. So,
und wenn sie dann hier gucken,
dann haben sie N-Aufwand und sie haben die Tiefe,
Logorithmus zur Basis 10 Neuntel von N, naja,
und die Basis spielt eigentlich für uns keine Rolle im Okalkühl,
das ist ja nur eine Konstante, macht O von N-Login.
Ja, also unter der Annahme, dass wir mindestens 90
Prozent balanciert sind, ja, haben wir schon N-Login-Laufzeit.
So, und das sollte uns eigentlich motivieren, anzunehmen,
was das Verfahren eigentlich fast immer N-Log-in-Laufzeit hat.
Es gibt nur diese pathologischen Fälle, wo wir N-Quadratlaufzeit haben. Also,
der Rekursionsbaum hatten eine maximale Tiefe von Log 1 durch A
von N-Ebenen und pro Ebene des Rekursionsbaums zehnmal in Zeit,
kommen wir dann auf U von N-Log-in-Laufzeit in diesem Fall.
So, jetzt ist der Zeitpunkt da, ist jetzt genauer hinschauen.
Bevor wir das tun, müssen wir jetzt überlegen,
wie wir das machen. Wir wollen eine Analyse im mittleren
Fall machen. Mittlerer Fall würde für uns bedeuten,
wir gucken uns alle möglichen Folgen an,
wie die Eingabe aussehen kann
Also es gibt die Folge 1, 2, 3,
4, 5, 6, 7, 8,
9, 10 und dann gibt es 2, 1,
3, 4, 5, 6, 7,
8, 9, 10 und so weiter.
Alle möglichen Folgen, ja,
berechnen immer die Laufzeit und nehmen davon den Mittelwert.
Wäre die mittlere Laufzeit, die wir brauchen. Das ist
übel, ja, also sowas auszurechnen wäre recht aufwendig.
Das können wir natürlich gar nicht machen. Deswegen machen wir
einen Trick. Wir werden dafür sorgen, dass unser Algorithmus
gar nicht mehr abhängig ist von der genauen Reihenfolge der Eingabedaten.
Weil wenn der davon nicht abhängig ist,
dann müssen wir auch nicht diese ganzen Fälle unterscheiden.
Wir machen einfach das so, dass es egal ist,
in welcher Reihenfolge die Daten sind und wir dann entsprechend
die Laufzeit analysieren können
Story, können wir das machen? Dazu brauchen wir
einen Zufallszahlengenerator. Wir machen das nämlich so,
wir wählen einfach zufällig ein Partitionselement aus. Und dann,
wenn wir das machen, dann spielt die Eingabereuen-Reihenfolge keine Rolle mehr,
weil der Zufallszahlengenerator, der pickt irgendeine Zahl.
Und mit gleicher Wahrscheinlichkeit jede, der zahlt,
dann spielst du gar keine Rolle, in welcher Reihenfolge der ist.
Okay, das heißt, wir ändern den Algorithmus ein bisschen
und entwickeln auf diese Art und Weise einen sogenannten randomisierten Algorithmus,
ein randomisierter Quicksort. Randomisiert heißt unter Verwendung von Zufallszahlen.
Und jetzt kommt eine wichtige Stelle, weil die ganz,
ganz viele missinterpretieren, ein randomisierter Algorithmus bedeutet.
Randomisierte Algorithmen bedeuten für die allermeisten, da kommt eine Zufallszahl
drin vor, da kommt jedes Mal was anderes raus.
Weil ja eine Zufallszahl benutzt wurde. Das ist es nicht.
Es wird eine Zufallszahl verwendet, aber das Verfahren ist deterministisch.
Das heißt, es kommt immer das gleiche Ergebnis raus.
Das muss ja hier auch so sein. Wir wollen ja
sortieren. Wir wollen ja nicht irgendein Ergebnis haben.
Also randomisierter Algorithmus bedeutet, es wird ein Zufallszahlengenerator verwendet zur Berechnung,
aber das Ergebnis ist nach wie vor deterministisch.
Es kommt immer das Gleiche raus. Es dauert gegebenenfalls
unterschiedlich lang, aber es kommt immer das Gleiche raus
Okay, so, hier ist unser randomisierter Quicksort.
Es ist ganz einfach. Wir ändern nur die Partition-Funktionen in
eine Rand-Partition-Funktion, also Randomize Partition. Das Einzige,
was wir machen, ist, wir nehmen eine Zufallszahlenfunktion hier random,
die zwischen L und R eine Zahlauswählt zufällig.
Das ist die Zahl I, dann vertauschen wir das Element AI mit AR.
Und dann steht an der Position eher ein zufälliges Element von
dem Teil, den wir noch sortieren müssen.
Und dann rufen wir Partition auf. Das Einzige,
was wir ändern. Wir nehmen irgendein beliebiges Element,
schieben das an die Stelle eher und dann machen wir Partition.
Und der Randomize Quicksort ist eigentlich Ein ganz normaler Quicksort mit dem einzigen Unterschied,
dass die Stadt Partition, die randomized Partition aufruft.
Das sind die einzigen Unterschiede. So, das ist jetzt
unser neuer Quicksort-Algorithmus und den analysieren wir und von dem berechnen wir jetzt die Laufzeit.
Jetzt haben wir nur ein Problem,
wir haben ja diese Zufallszahl da drin und wir müssen irgendwie mit diesem Zufallszahlengenerator,
naja, wir müssen den irgendwie, müssen das irgendwie auswerten,
was der tut und müssen das in der Laufzeitberechnung berücksichtigen.
Das ist das, was wir uns einkaufen.
Wir müssen nicht jede mögliche Eingabefolge betrachten und einen Mittelwert bilden,
um die mittlere Laufzeit zu berechnen,
aber wir müssen jetzt die Position oder das,
was der Zufallszahlengenerator ausgibt,
in die Laufzeitbetrachtung einbeziehen
Okay, so, hier nochmal zusammengefasst,
dass ich eigentlich gerade schon gesagt habe, wir wollen zeigen,
dass die erwartete Laufzeit des Quickshort-Algorithmus, so nennt man die,
also in randomisierten Verfahren, redet man von einer erwarteten Worst-Case-Laufzeit.
Die erwartete Laufzeit von Quickshort-O von N-Log-Ennis. Das heißt nicht,
dass wenn sie diesen Quickshort-Algorithmus starten, sie immer die Laufzeit,
wovon N-Log-Enner-Schranke erreichen können, das ist die erwartete Zeit,
wenn ihr Zufallszahlengenerator eine gute Folge ausspuckt,
dann geht es in N-Log-En, wenn der eine schlechte ausspuckt,
haben sie auch wieder die engen Quadrat.
Da müssen sie mal im Kopf ballen. Das kann auch
immer noch passieren. Wir haben jetzt sozusagen einen mittleren Fall,
den wir ausdrücken über eine zufällige Reihenfolge der Eingabedaten oder über diese Randomisierung des Algorithmus.
Okay, also das ist eigentlich das, was wir tun müssten.
Wir müssten mitteln über die Rekurrenzgleichung. Wir müssen sagen,
die erwartete Laufzeit von N ist quasi eins durch End, der Mittelwert.
Über alle möglichen Vivo-Positionen. Kuh gleich eins bis N,
T von Kuh minus eins plus T von N-Cup plus zehnmal N.
Das wäre eigentlich das, was wir machen müssten.
Damit mitteln wir über alle möglichen Pivo-Positionen
Können wir noch ein bisschen vereinfachen,
denn C von Q minus 1 ist ab der Hälfte dieser Summe,
also es kugelt ja hoch und ab Ende halbe ist ja der Teil,
entspricht ja dem hier, sind die Werte gleich.
Das heißt, wir können ja die Summierung abbrechen bei N
halbe und das Ganze mit zwei multiplizieren.
Das ist also das Gleiche wie zwei durch N,
mal die Summe kugel ich eins bis in eine halbe Tee von Q
plus dieses 10 mal N und das ist O von N Nog-N. Ne,
das wissen wir nicht, aber das hoffen wir mal,
dass es so ist.
Also hier ist das große Fragezeichen. So,
das hier weiter zu analysieren, ist Gelinde gesagt, ätzend.
Also das zu versuchen, hier aufzulösen und diese Rekurrenzgleichung zu bestimmen,
mit jetzt in vielen Einsetzungen, so ist das ja.
Das ist ziemlich unangenehm. Und deswegen hat man in der Informatik nach einer anderen Technik gesucht,
um solche Algorithmen zu analysieren und hat auch eine gefunden,
die wesentlich einfacher ist und die man in der Regel heute anwendet,
wenn man solche randomisierten Verfahren benutzt.
Was man macht, ist, man versucht,
man hat uns diese Zufallszahlen da drin und man arbeitet mit Erwartungswerten.
Also man nutzt so diese Techniken aus der Stochastik. Also,
Alternative You 2 ist, Wir benutzen die folgenden Argumente.
Erstens, wir wissen, die Laufzeit von Quicksort wird durch Partition dominiert.
Eigentlich ist die, wenn man das alles angucken würde,
da wird immer wieder paar Tischen irgendwie aufgerufen und alles,
was wir an Laufzeit haben, geht in dieses Partition rein.
Die Laufzeit von Partition ist proportional zur Anzahl der Vergleiche,
die gemacht werden. Können wir nochmal nach ein paar Tischen gehen,
mal gucken. Also hier ist Partition und sie sehen,
die Laufzeit ist proportional zur Anzahl der Vergleiche.
In jeder Vorschleife wird ein Vergleich gemacht
Seiter Argument. Und wenn wir die Anzahl der Vergleiche wüssten,
dann wüssten wir auch die Laufzeit, denn wir haben irgendwie linearen Aufwand,
um das Array zu verwalten, lesen und so weiter.
Und dann haben wir die ganzen Partition-Funktionen und in der Summe
brauchen wir das an Zeit, was wir an Vergleichen brauchen.
Wir zählen einfach, wie viele Vergleiche wir brauchen.
Das heißt, wenn wir x viele Vergleiche brauchen,
um das Array komplett zu sortieren,
hätten wir Tee von N,6, also O von N plus x.
Und wir wissen nur noch nicht, was x ist.
Also wie viele Vergleiche braucht unser Algorithmus
Okay, so, die müssen wir ausrechnen.
Und das machen wir über Elementarereignisse. Wir müssen sagen uns
einfach, okay, was wird denn da verglichen?
Da werden die Elemente verglichen in dem Array.
Also schauen wir uns das Ereignis an,
das ein Element AI mit einem Element AJ verglichen wird.
Also zwei beliebige Elemente. Und das ist jetzt ein Ereignis.
Und wenn wir, wenn dieses Ereignis eintritt, dann haben wir einen Vergleich durchgeführt.
Und wenn wir die alle aufaddieren, dann hätten wir die Anzahl der Vergleiche.
Okay. So, wann ist denn das XIJ gleich 1?
Also wann wird der Vergleich zwischen Element I und J durchgeführt?
Und jetzt kommt der entscheidende Gedankenblitz, den man braucht,
um das zu machen, wenn man den Algorithmus betrachtet,
dann sieht man,
dass ein Vergleich immer nur durchgeführt wird mit einem Pivo-Element.
Ja, schauen Sie den Algorithmus an. AJ wird mit AR verglichen.
AR war das Pivo-Element. Wir vergleichen nur mit Pivo-Elementen.
Ja, auch den anderen Partition-Algorithmus können wir uns auch angucken.
Auch der vergleicht nur mit Pivo-Elemente. Das heißt,
der ganze Algorithmus vergleicht immer nur in Partition mit Pivo-Elementen.
Und über diese Erkenntnis können wir jetzt abschätzen, wie lange
der Algorithmus braucht. Also, wenn XJ gleich 1 ist,
also I wurde tatsächlich mit J verglichen,
dann ist entweder AI oder AJ ein Pivo-Element.
Eins von beiden musst du im Fivo-Element sein, sonst würden wir
die gar nicht vergleichen. Und wenn ein Element einmal Pivo-Element war,
wird es nie mehr betrachtet,
weil das Pivo-Element steht in der Mitte und rekursiv schauen sie sich
nur den linken und den rechten Teil an, aber das Pivo-Element nie mehr.
Das heißt, sobald ein Element hier mal dabei war und wurde irgendwie verglichen,
ist es danach raus, weil es stand ja in der Mitte,
es steht dann ja in der Mitte und wird in den
rekursiven Aufrufen des Quickslots nicht mehr verwendet
So, mit dem Trick können wir jetzt ausrechnen,
wie viele Vergleiche wir brauchen. Erstmal, was uns natürlich interessiert,
ist die erwartete Anzahl der Vergleiche. Und dann sagen wir,
okay, wenn ein Element immer nur verglichen wird,
wenn eines der beiden Pivot-Elemente ist und danach nie mehr,
dann kommt jeder Vergleich nur einmal vor.
Es gibt in Quicksort keinen Augenblick oder keine Stelle,
wo zwei Zahlen nochmal verglichen werden.
Das ist übrigens der Grund, warum der so effizient ist.
Er kommt nie wieder zu der Situation,
wenn er einmal A3 mit A17 verglichen hat,
wird nie mehr in dem Algorithmus einen Augenblick kommen,
wo er A3 nochmal mit A17 verglichen hat
Weil wenn er die beiden verglichen hat, war eines von beiden Pivo-Element
und wenn eins Pivo-Element ist, ist es raus aus dem Spiel.
Denn dann kommen die rekursiven Aufrufe und die sind ohne dem Pivo-Element. Okay,
deswegen die Gesamtzahl aller Vergleiche können wir bestimmen,
indem wir einfach sagen, okay, wir nehmen alle Paare,
also alle Is und alle Js, die vorkommen können und sagen,
wir nehmen einfach die Summe.
Also die Summe, I gleich 1 bis N minus 1,
J gleich i plus 1 bis N,
also alle Paare von I und J. Und dann nehmen wir X-IJ.
Also X-IJ ist die Frage, ob der Wert,
also ob I mit AI mit AJ verglichen wurde
Okay, das ist XIJ. So, dann können wir
das hier ausrechnen. Da gibt es ein paar Rechenregeln für
Erwartungswerte, die sind linear. Das bedeutet,
dass das Gleiche wie der Erwartungswert von dem XIJ,
so unseren Erwartungswert von der Variablen, die entweder eins oder null ist,
die ist gleich der Wahrscheinlichkeit, dass dieses Ereignis eintritt.
Also es ist die Wahrscheinlichkeit, dass I mit J verglichen wird.
Also das heißt, die erwartete Anzahl Vergleiche ist nichts weiter
als die Summe über alle Paare und für jedes Paar,
was wir da haben, überlegen ist die Wahrscheinlichkeit,
mit der I mit J verglichen wird.
So, jetzt müssen wir noch
ein bisschen rechnen
Also wir müssen jetzt die Wahrscheinlichkeit rausfinden. Also wie groß ist die Wahrscheinlichkeit,
dass wir ihn mit J vergleichen? Weißt du,
wir brauchen wieder so ein paar Argumente und um hier hinzukommen,
um hier zu sehen, wie groß diese Wahrscheinlichkeit ist.
Hier steht die Wahrscheinlichkeit. Aber jetzt gehen wir Schritt für Schritt vor. Also das erste ist,
X, also wenn X ein Pivot wert ist,
also wenn X ein Pivot-Element ist, ist ja die Bedingung dafür,
dass wir überhaupt mit dem vergleichen,
dann ist das AI und das AI ist links von X
und das AJ ist rechts von X.
Dann werden wir
die beiden nie vergleichen
Ja, also wenn x einmal Pivo-Element war,
dann hätten wir ja ein AI, was kleiner ist und
ein AJ, was größer ist, voneinander getrennt.
Aber nur durch den Vergleich mit dem X. Das heißt,
die sind schon mal alle null.
Die sind alle null. So, jetzt gucken wir uns
einen Array an von i bis J. Irgend so ein Tile Array.
Das ist der Bereich von i bis J. So, wann,
wenn wir das Tile Array betrachten, oder dann fragen wir uns,
wann wird da jetzt AI mit AJ verglichen?
Naja, wenn eins von beiden Pivo-Element ist, haben wir gesehen.
Entweder das AI-Pivo-Element oder AJS-Pivo-Element. Wie groß ist denn
die Wahrscheinlichkeit, dass AI oder AJ-Pivo-Element ist? Naja,
ich habe in diesem Array von i bis J,
habe ich j-i plus 1
viele Elemente stehen
Das sind alle. Meine Zufallszahlenfunktion wählt zufällig ein Pivo-Element aus.
Das heißt, alle mit gleicher Wahrscheinlichkeit. Das heißt,
die Wahrscheinlichkeit, dass AI-Pivo-Elementes ist eins durch j-i plus 1. Die Wahrscheinlichkeit,
das AJ-Pivo-Element, das ist auch eins durch j-i plus 1.
Also alle Elemente in dem Tyle Array, jedes ist gleich wahrscheinlich.
Macht zusammen zwei durch j-i plus 1. Das ist die Wahrscheinlichkeit dafür,
dass wir I mit J, also das IT mit dem J-Element vergleichen.
So, das war der schwere Teil. Jetzt müssen wir
es nur ausrechnen. Der Rest ist einfach. Also,
wir müssen die erwartete Anzahl von Vergleichen,
die wir haben,
ist die Summe
Über alle Paare, mal die Wahrscheinlichkeit, dass wir I mit J vergleichen müssen.
Die Wahrscheinlichkeit, dass wir I mit J vergleichen müssen,
ist zwei durch J-minus i plus eins.
Das müssen wir, können wir ein bisschen umbauen hier,
damit die Schleife einfacher oder der Assomant einfacher ist.
Wir setzen einfach K gleich J-minus i hier ein.
Und wenn wir das machen, dann müssen wir diese Schleife hier verschieben.
Und dann steht hier die Summe von i gleich eins bis M-minus eins
und die Summe von K gleich 1 bis N-minus 1,
2 durch K plus eins.
Haben wir das K hier eingesetzt. Ja, und das
können wir ein bisschen abschätzen hier. Wir machen diese Summe
einfach ein bisschen größer, wir lassen das plus eins mal weg,
dann wird es auch größer, also zwei durch K.
Die zwei können wir hier vor dieser Summe ziehen und dann haben wir
hier die Summe von K gleich 1 bis N1 durch K.
Das ist die sogenannte harmonische Reihe, die Summe über alle Brüche.
Ein Halb plus ein Drittel, plus ein Viertel plus ein Fünftel und so weiter.
Und dafür gibt es eine Abschätzung, wie groß die ist.
Finden sie auch im Kormen hier und das ist logorithmisch.
Also die wächst logorithmisch. Ja,
und dann haben sie hier zweimal Login und das geht von Igarch 1 bis M minus 1,
da kommt das Igar nicht mehr vor,
N mal müssen sie das aufsummieren,
macht zweimal N minus 1,
mal Login plus 1,
macht O von N-Login
Das ist der Nachweis, dass der Quicksort im mittleren Fall,
beziehungsweise der randomisierte Quicksort im erwarteten Fall N-Log-End-Laufzeit hat.
Das ist schwierig. Ich weiß, dass das schwer ist. Diesen Gedankengang zu fassen,
diese über Wahrscheinlichkeiten zu argumentieren, ist eine sehr, sehr schwere Analyse.
Wir machen das auch in dieser Vorlesung nur hier einmal an dem Quicksort-Algorithmus,
weil ich möchte, dass sie es mal gesehen haben,
wenn sie sich mit Algorithmik heute beschäftigen,
also wenn sie das im höheren Semester machen oder wenn sie in dem Bereich forschen,
arbeiten sie ganz, ganz viel mit diesen randomisierten Verfahren,
Diese erwartete Laufzeit ist natürlich das,
was einem viel mehr interessiert,
als diese schwachen Worst-Case-Abschätzung.
Ja, und hier sieht man mal in einem Beispiel,
wie sowas aussehen kann. Okay, so, also,
Merch Heep Quick-Sot, Incertion Saw, Selection Saw,
jetzt haben wir schon so viele Verfahren gesehen.
Und eigentlich müsste man jetzt der Meinung sein,
es reicht jetzt mal so langsam mit dem Sortieren.
Und hier ist erstmal eine Zwischenzusammenfassung von all dem,
was wir gesehen haben. Wir haben gestartet mit ganz einfachen
Sortierverfahren, die haben Endquadrat Zeit verbraucht.
Dann haben wir
den Merch-Sot gesehen
Der hat mit Devite in Conka das gemacht. Er hatte O von N-Log End-Site,
da war also schneller, hat aber zusätzlichen Speicher gebraucht.
War aber trotzdem gar nicht so schlecht. Also wir haben
gesehen, damit konnte man zum Beispiel gut parallelisieren.
Bei einem Argument, um das vielleicht zu benutzen.
Dann haben wir den Heep-Sort gesehen, der Heep-Sort war auch
in der Lage in O von N-Log End-Sight zu sortieren.
Und der war der Erste, der In-Plays sortiert.
Also im Gegensatz zum Merch-Sort. Kein zusätzlicher Speicher, den
wir brauchen. Der Heep-Sort ist also Ja, nahezu perfekt.
Keinen zusätzlichen Speicherbedarf. O von N-Log and Zeitbedarf. Ist
aber ganz schlecht zu parallelisieren, nun mal so nebenbei.
Konstanten wissen wir gar nicht so, genau.
Dann haben wir den Quickshot gesehen. Der Quickshot,
der Algorithmus, der heute in der Regel eingesetzt wird.
Und haben uns die Analyse von dem Quickshort angeguckt.
Und haben als erstes mal von der Annahme,
haben erstmal die Annahme widerlegt, von denen viele ausgehen,
dass der Quickshort quasi auch im Worst Case ein gutes Verfahren ist.
Das ist ja nicht. Im Worst Case hat er
eine Laufzeit von O von N-Quadrat.
Die, wir haben dann aber gesehen,
dass in den allermeisten Fällen,
also wenn es nur irgendwie ein bisschen balanciert ist,
haben wir eine Laufzeit von O von N-Log-In.
Also dann ist er wieder genauso gut wie der Heepshot,
ja. Zu dem Satz sage ich gleich was.
Der Quicksong zeigt in der Praxis sehr gutes Laufzeitverhalten.
Ja, deswegen wird er eben genommen. Der hat
eben neben diesem Eigenschaften im Okalkü eine sehr niedrige Konstanten.
Das ist immer das, was wir im Okalkül nicht sehen.
Ja, da sehen wir immer nur das Wachstum,
aber der hat niedrige Konstanten. Da gibt es auch noch
Tricks, den weiter zu beschleunigen mit sogenannten Endrekussionen.
Die habe ich Ihnen jetzt nicht gezeigt, die Varianten.
Da löst man eine Rekursion auf von den beiden rekursiven Aufrufen
Ist aber nur noch so ein technischer Trick. Aber das,
was am Quicksort eigentlich wirklich noch spannend ist, ist die Frage,
ob es nicht irgendeinen Trick gibt, noch was hinzubekommen,
dass der auch im Worst Case garantiert Of und N-Login-Zeit hat.
Das müsste doch irgendwie zu schaffen sein. Wir müssen ja
nur das Pivot-Element richtig wählen. Wir könnten natürlich da drüber
gehen und gucken, welches Element in der Mitte liegt.
Also das wäre, das ist in der Folge der Median,
haben Sie bestimmt schon mal gehört in der Mathematik,
das Element mit der mittleren Größe ist der Median.
Den müssten wir finden, wenn sie einen Orakel haben,
was ihnen sagt, an der Stelle A17,
Steht der Median, haben Sie sofort ein N-Log-In-Verfahren,
weil Sie würden dann sagen, okay, A17,
du bist Pivo-Element, wir würden die Elemente,
dass Array in zwei Hälften aufteilen, die wären gleich groß.
Wir hätten zweimal eine halbe, wissen wir schon,
kommt N-Log-In raus. Also wir brauchen einen richtig cooles Verfahren,
um so ein Median zu finden.
Und das ist überhaupt nicht einfach. Also es sieht nach
einem ganz einfachen Problem aus, Array in Zahlen unsortiert,
was ist das mittlere Element
Das war bis vor, ich weiß nicht ganz genau,
ich habe jetzt die Jahreszahl nicht im Kopf,
aber das war bis vor 20 Jahren, das waren fünf,
vielleicht sind es jetzt mittlerweile 30 Jahre, war das ungelöst.
Die Frage, wie man dieses Problem schnell löst.
Wir gucken uns das hier noch an. Wir werden
das schaffen. Also wir werden in der Lage sein,
diesen Medien zu bestimmen, und zwar schnell.
Schnell bedeutet in Linearzeit, also in O von Insight.
Wenn wir den in den Linearzeit bestimmen können,
wird der Quicksort zu einem N-Log-In-Verfahren. Im Moment ist das
noch nicht. Ich kann Ihnen aber auch gleich sagen,
dass dieses Verfahren zur Medianfindung mit O von Endlaufzeit so große
Konstanten hat, dass sie das nicht benutzen werden.
Aber in der Theorie sind sie damit in der Lage,
tatsächlich auch dieses Verfahren zu einem O von N-Log-in-Verfahren zu machen.
Okay, bevor wir uns mit diesen Median-Dingen auseinandersetzen,
wollen wir uns erstmal jetzt zurücklehnen und überlegen,
ob wir das Problem eigentlich optimal gelöst haben oder nicht.
Also immer, wenn man Algorithmen entwirft und wir entwerfen ja Algorithmen für Sortieren,
sollte es so einen Zeitpunkt geben, wo sie sagen,
Ist es das jetzt?
Ja, habe ich die optimale Laufzeitsschranke erreicht oder geht es noch besser?
Deswegen brauchen wir eine untere Schranke. Wir müssen gucken,
wie viel Zeit brauchen wir mindestens, um zu sortieren?
Weil wenn wir jetzt zeigen, wir brauchen mindestens sowieso Omega von N-Log-End-Zeit,
dann wissen wir, dass ein O von N-Log-End-Zeit-Verfahren optimal ist,
ja, bis auf Konstanten.
Okay, also wir brauchen eine untere Schranke und diese untere Schranke für Sortieren
kann man ganz leicht finden. Man muss sich nur überlegen,
wie viele mögliche Eingabefolgen gibt es eigentlich, ja.
Die Argumentation für eine untere Schranke für Sortieren ist wie folgt.
Wir nehmen an, dass unsere Sortierverfahren auf Vergleiche basieren
Das haben wir gesehen. Alle Verfahren, die wir hatten
, basierten auf Vergleichen. Wir haben zwei Elemente verglichen,
haben eine Entscheidung gefällt und haben dann irgendetwas rearrangiert in unserem Array.
Die Vergleiche sind irgendwie, naja, irgendwie kleiner,
gleich, größer, gleich irgendwie sowas.
Wir können das alles reduzieren auf kleiner Gleichoperationen. Wenn sie
gleich haben, machen sie kleiner, gleich und größer gleich.
Also wir brauchen nur kleiner gleich. Also alle Sortierverfahren,
die wir hatten, können wir so formulieren,
dass sie nur eine kleine Gleichoperation brauchen.
Okay, so. Jetzt überlegen wir uns folgendes mit jedem
Paarvergleich, den wir machen, fällen wir eine Entscheidung.
Die Summe aller Entscheidungen führt uns zu der sortierten Folge
So, das heißt, wir bekommen einen großen Baum,
wir bekommen einen großen Baum und fällen eine Entscheidung.
Wir vergleichen zum Beispiel das erste mit dem zweiten Element.
Und dann sagen wir, okay,
das ist das erste ist jetzt kleiner als das zweite,
dann machen wir irgendwas anderes.
Dann vergleichen wir das zweite mit dem dritten Element.
Dann war das vielleicht kleiner, dann sind wir hier.
Die Bäume, die Blätter, Entschuldigung,
die Blätter dieses Entscheidungsbaums sind Folgen, sortierte Folgen.
Also nein, sind nicht sortierte Folgen, sind Eingabefolgen,
die sie haben, um an diesem Punkt zu belangen.
Also um diese, wenn diese Entscheidung wahr sind,
kommen sie zu dieser Eingabefolge. Wenn eins kleiner ist als zwei,
wenn zwei größer ist als drei und wenn eins größer ist als drei,
haben sie
die Folge 3,
1,
So, in so einem Entscheidungsbaum stehen hier unten in den Blättern
alle möglichen Eingaben, die sie haben können zum Sortieren.
Ja, wenn sie Endzahlen sortieren wollen, haben sie hier alle möglichen Eingaben.
Die müssen sie voneinander trennen. Ja, Algorithmus muss unterschiedliche
Dinge tun, je nachdem, wie die Eingabefolge aussieht.
Ja, er muss ja anders die Elemente vertauschen,
wenn die Eingabefolge anders ist.
Das heißt, diese muss er unterscheiden können. So,
und ich stelle jetzt die Frage, okay,
wie viel gibt es denn hierhin?
Ja, wie viele Blätter hat dieser Baum
Naja, das ist ganz einfach abzuschätzende Zahl.
Das ist die Anzahl der Permutationen von Enzahlen. In wie vielen
verschiedenen Reihenfolgen können wir Entzahlen aufschreiben? Enfakultät. Wir können
in Enfakultät viele Arten Enzahlen anordnen, in einer Reihenfolge.
Wer das nicht glaubt, für die erste Zahl haben sie N-Möglichkeiten.
Für die zweite haben sie dann noch N-Minus-1-Möglichkeiten,
für die nächste M-Minus-2, in Minustreien und so weiter, ist N-fakultät.
Das sind die Anzahl der Möglichkeiten. So,
das heißt, wir haben einen Entscheidungsbaum,
wir haben hier unten Enfakultät viele Blätter und um den Algorithmus mit Entscheidungen,
sich den Vergleich zweier Elemente dahin zu führen zu der entsprechenden Folge,
müssen wir durch diesen Baum navigieren,
von oben
nach unten
Okay, also wir haben ein Entscheidungsbaumodell, wir haben einen vollständigen,
vinären Baum, die inneren Knoten sind die Vergleiche zwischen zwei
Elementen AI und AJ und die Blätter sind die Permutation,
das heißt, die Rangfolgen dieser Elemente.
AI und AJ? AI und AI, ja,
AI und AJ natürlich, genau. So,
das Theorien besagt, jedes vergleichende Sortierverfahren benötigt bei End zu
sortierenden Datenelementen im Worst Case Omega von Endlock Endside
Ja. Also hier steht Vergleichsoperation, aber wir wissen natürlich,
die Anzahl der Vergleichsoperation ist eine untere Schranke für die Zeit,
die wir brauchen,
weil wir müssen ja die Vergleichsoperation alle durchführen.
Die Argumente sind, jedes Sortierverfahren führt eine Serie von Vergleichsoperationen aus.
Irgendwo vergleicht, dann Quicksort hat immer mit Pivo-Element verglichen,
Heep-Sort hat beim Heepify immer in dem Baum verglichen,
also wir vergleichen halt immer.
Diese Vergleiche entsprechen in der Sequenz einem Pfad von der Wurzel
zu einem Blatt in dem Entscheidungsbaum
Die Sortierverfahren müssen jede mögliche Permutation erzeugen können,
weil die Eingabe kann ja jede mögliche Permutation sein.
Wir müssen ja so jede beliebige Reihenfolge bringen können.
Der Entscheidungsbaum hat also N-Fakultät viele Blätter. Und jetzt ist
die Frage, wie hoch ist jetzt der Entscheidungsbaum?
Wir wissen ein binärer Baum, hat lugerithmische Höhe in der Anzahl der Blätter.
Deswegen ist die Höhe unseres Baums größer gleich Logorithmus zur Basis
2 von L. L ist die Anzahl der Blätter.
Das ist gleich Logorithmus zur Wahl 2 von N-Fakultät
Jetzt müssen wir den Logerhythmus von Endfakultät berechnen. Da haben
die Mathematiker wieder eine schöne Abschätzung, um das zu machen.
Können Sie wieder nachlesen. Das ist genau End-Log-N. Das heißt,
die Höhe unseres Entscheidungsbaum ist größer gleich Omega von End-Log-In.
Das heißt, wir brauchen mindestens End-Log-In viele Vergleiche,
um eine Folge zu sortieren.
Damit sind wir eigentlich ja fertig. Das ist auch gute
Zeit. Nein, eigentlich sind wir fertig.
Eigentlich sind wir fertig. Wir haben gezeigt,
super,
jedes Sortierverfahren braucht End-Log-In-Zeit
Und wir haben gesehen, wir haben zum Beispiel
den Heeps Hot, der genau diese Laufzeitschranke erreicht.
Optimales Verfahren, nicht weiter beschleunigen, zu beschleunigen.
Wie immer gibt es ein kleines Wenn. Ja,
und dieses kleine Wenn verbirgt sich auch in diesem Theorien.
Hier steht nämlich etwas, was das Sortierverfahren einschränkt.
Hier oben steht, jedes vergleichende Sortierverfahren benötigt diese Laufzeitschranke.
Das heißt, wir haben die Annahme gemacht, dass wir paar Vergleiche machen.
Und dann brauchen wir mindestens so viel Zeit.
Was, wenn unser Sortierverfahren gar keine Vergleiche macht
Das klingt erstmal ein bisschen blöde, ja,
weil wir wollen ja Zahlen sortieren, also müssen wir sie irgendwie vergleichen.
Aber ich zeige Ihnen ein Verfahren, was keine Vergleiche braucht.
Gehen wir mal davon aus, wir haben Endschlüsse,
die sind aus der Zahlenmenge von eins bis Ende und sie sind alle verschieden.
Das sind sehr harte Annahmen, ne? Das wird in
der Praxis nicht vorkommen, aber gehen wir mal davon aus.
Ja, irgendwie unsere Zahlenfolgen, die wir eben hier gesehen
haben, waren so, ne?
Das waren die Zahlen von eins bis Ende in irgendeiner Reihenfolge.
Die wollen wir sortieren. Das geht ganz einfach, ja?
Was wir machen ist, wir rufen unsere Funktion auf,
die hat den tollen Namen Trivial Sword,
die geht mit der Vorschleife von eins bis
Und schreibt einfach an die Position A von Idas Element A von I hin.
Ja, die schreibt einfach an die Position 7,
7 hin, ja, und an der Position 10, 10.
Und das macht ihr einfach und dann ist die fertig, ja.
Die hat erstens keinen einzigen Vergleich gemacht und die hat erstens unsere untere Schranke auch gebrochen,
denn das geht in linearer Zeit, ja.
Also, wenn wir es schaffen zu sortieren,
ohne zu vergleichen, dann gilt für uns diese untere Schranke nicht.
Und deswegen werden wir uns am Mittwoch bei Sortierverfahren angucken,
die einfach nicht mehr vergleichen.
Das war's für heute