Bitte jetzt einmal leise sein. Ich will mal eben kurz testen,
ob ihr mich da oben hören könnt, wenn jetzt alle ruhig sind.
Könnt ihr mich hören, oder ist das zu leise?
Das geht. Gut. Also nochmal jetzt, der Versuch,
herzlich willkommen zur 30. Vorlesung. Wir sind fast durch,
ja, diese Woche noch und nächste Woche.
Dann ist das erste Semester schon vorbei und wir haben euch
so einiges erzählt zu objektorientierter und emperativer Programmierung.
Diese Woche ist es nochmal so eine richtig volle Vorlesung.
Nächste Woche wird es dann so eine gemixte Geschichte geben. Das heißt,
ich werde Die Hälfte nochmal was Inhaltliches machen und dann die
zweite Hälfte werden wir hoffentlich die Ergebnisse der Lehre-Evaluation durchsprechen können.
Das hängt natürlich davon ab, ob dann die Ergebnisse auch schon da sind.
Das ist prima. Neuerdings fliege ich immer aus meinem VPN
raus. So, worum geht es heute?
Um grob gesagt zwei Themenbereiche. Einmal weitere dynamische Datentypen oder
abstrakte Datentypen, nämlich zum einen Stack und zum anderen Q.
Und im zweiten Teil heute wird es um Sortieren gehen
Und ich werde erstmalig auch in SC1 nicht nur zeigen,
wie man sortieren implementiert. Also wie ihr etwas produziert,
wie ihr sozusagen sortieren, leisten könnt, implementieren können,
sondern auch mal zeigen, wie man denn die Dinge,
die in Java in den Bibliotheken liegen, auch benutzen könnt.
Das ist ja eigentlich mindestens genauso wichtig, dass ihr das,
was da ist, kompetent benutzen könnt.
Okay, aber heute geht es hier, wie gesagt,
erstmal um Stack on Q,
das sind zwei sehr einfache Datenstrukturen,
so von der Grundidee sind aber
unglaublich vielfältig einsetzbar
Das hier ist eine der buntesten Folien, die wir haben
, glaube ich. Stammt noch aus der Zeit,
bevor ich überhaupt hier nach Hamburg gekommen bin.
Und ich finde sie so interessant, dass ich sie einfach behalten wollte.
Veranschaulicht sehr schön, was ein Stack oder ein Stapel ist.
Ihr wisst sicherlich, was ein Tablettstapel in der Mensa ist.
Das ist so ein Stapel von Tablets und man nimmt sich meistens das Oberste runter.
Es sei denn, es ist gerade sehr dreckig.
Da nimmt man vielleicht noch ein zweites. Und üblicherweise ist es so,
dass man auf so einen Tablettstapel Tablets draufpacken kann und man kann sie runterpacken.
Das ist intuitiv natürlich sehr einfach. Und das ist auch
genau das, was ihr verstehen müsst von einem Stack
Hier ist auch nochmal ein sehr schön anschauliches Bild dazu.
Das finde ich deshalb anschaulich,
weil wenn ich einen von diesen Äpfeln hier mir nehmen möchte,
dann werde ich üblicherweise oben reingreifen und einen Apfel oben rausziehen und nicht versuchen,
einen der Äpfel da unten irgendwie seitlich rauszukriegen.
Das könnte relativ unappetitlich werden. Das heißt also,
das bietet sich an hier bei dieser Einrichtung,
jeweils immer das oberste Element zu nehmen.
Und wenn man einen Apfel hinzufügt,
wird man den auch oben hinzufügen und nicht irgendwie versuchen,
den unten reinzustopfen. Also ganz anders als eine Liste,
die ihr ja auch kennt,
als eine sequenzielle Folge von Elementen,
bei der man aber an jeder beliebigen Position
was einfügen oder entfernen kann
Das heißt, im Grunde genommen könnte man sagen,
ein Stack ist eine eingestrengte Liste. Hier ist so die
klassische Definition, die ja auch im Informatik-Duden finden könnt.
Eine Folge von Elementen eines gegebenen Datentyps, also hier zum Beispiel Datentyp-Apfel,
ja, als Elementtyp, mit eingeschränkten Einfüge und Ausfügeoperationen.
Und man bezeichnet halt eine Datenstruktur, wird auch gerne
als Datenstruktur bezeichnet, weil halt irgendwie Daten drin sind.
Das ist eher ein altertümlicher Begriff, Datenstruktur.
Heute würde man vielleicht sagen, das ist eine Objektstruktur.
Und wenn es mindestens zwei Zugriffsoperationen gibt,
und den eine steht es am Ende der Folge einfügt und
der andere stets das letzte Element entfernt. Das heißt,
es muss einen Push geben, das fügt ein Element ein,
das muss einen Pop geben, dass das oberste Element entfernt,
das zuletzt eingefügte Element, und das ist ganz wichtig,
das ist dieses Prinzip, was dahintersteht,
das ist das sogenannte Livo-Prinzip, last in first out.
Das heißt, das, was zuletzt eingefügt wurde,
wird auch als erstes wieder entnommen. Das ist für euch,
sagen wir jetzt, nicht wirklich was Überraschendes,
ihr habt schon gesehen, dass sich die Aufruf,
den Aufrufsstack von Java so ein bisschen visualisiert habe,
im Debugger von BlueJ,
rekursive Aufrufe, dieselbe Methode wird mehrfach rekursiv aufgerufen.
Dann war gerade auf dem, in dem Debugger so richtig
schön visualisiert, wieder so mehrere Stackframes übereinander lagen.
Das hatten wir auch schon mal in einer der vorigen Vorlesungen.
Das heißt also, das ist wirklich ein ziemlich zentral wichtiger Behälter.
Hier ist das mal angedeutet, wie das als Interface aussehen würde. Wenn wir uns vorstellen,
hier Interface-Stack, und hier ist es tatsächlich auch generisch,
in dem Sinne, dass der Elementtyp hier freigelassen ist,
also hier so ein Platzhalter steht.
Das ist also ein Stack von E-Elementtypen. Wenn ich sowas habe,
so ein Interface, dann gibt es eine Operation Push mit einem Element als Parameter.
Also im Sinne von, hier ist ein neues Element,
bitte auf den Stack drauflegen, auf den Stapel drauflegen.
Und ein Pop, das liefert ein solches Element und die Abfrage,
ob der Stack leer ist. Diese Abfrage braucht man,
werden wir gleich nochmal ein bisschen uns ansehen. Also,
wenn ich jetzt mir mal vorstelle,
ich würde jetzt einen Stack von String mir deklarieren,
entsprechend mit zwei Elementen, hätte dann eine Implementation.
Und da sind also jetzt schon zwei Strings drin,
ziemlich blöde Strings, das sind so ein buchstabige Strings,
also A und T sind da jetzt auf diesem Stapel drauf.
Man kann sich das so vorstellen, zuerst wurde das T draufgepackt,
das A, jetzt machen wir hier einen Push,
Also rufen Push auf, das heißt,
wir legen den String X da mit drauf,
dann kann man sich das so visualisieren, dass dann das X oben auf dem Stapel drauf liegt.
Oder ich rufe nochmal einen Push auf mit Z,
dann kann man sich das so visualisieren. Und wenn ich dann s.pop aufrufe,
das liefert mir ja ein Element und nimmt es auch direkt runter,
dann kann man sich das hinterher so vorstellen,
dass es so dann in diesem T das Z drin ist.
Dieser Kommentar hier ist, sagen wir, ein bisschen irreführend.
Es steht ja hier gleich gleich. Also wenn man das
jetzt Java auffassen wollte, wäre es natürlich nicht richtig ein String mit gleichgleich zu vergleichen.
Aber das ist nur ein Randdetail. Gut,
also wir haben hier dieses Interface-Deck mit drei Operationen.
Und das sind aber, wie ihr wisst bei so einem Interface,
nur Signaturen. Das heißt, man sagt hier nur,
was als Parametertyp ergeben wird, was hier als Ergebnis geliefert wird.
Und da, aber man sagt nichts darüber, was denn diese Operation bedeuten.
Das steht da einfach nicht. Das heißt,
diese gewünschte Umgangsform, die wir für eins der kennen,
last in first out, das,
was zuletzt auf den Stapel draufgelegt wird, auch als erstes wieder runtergenommen,
das findet sich hier erstmal so nicht wieder.
Da gehe ich dann auch nochmal drauf ein.
Es gibt tatsächlich eine klasse Stack im Driver Clections Framework,
auf der vorigen Folie war es ja ein Interface für
einen Stack
Das sieht hier ziemlich genau so aus,
wie man sich das vorstellt. Das heißt also,
man kann mit einem Konstrukt einen leeren Stack erzeugen,
mit einem Konstrukt der Aufruf, genauer gesagt,
dann kann ich fragen, ob der Stack leer ist,
dann kann ich Push aufrufen, dann kann ich Pop aufrufen.
Ich kann auch etwas suchen. Gut, das mag auch manchmal
nützlich sein. Und dann gibt es auch eine Operation Peak,
die nicht das Objekt ganz oben herunternimmt, sondern nur liefert,
was für ein Objekt ist es denn.
Also looks at theotrics the top of Stack without removing it from the Stack.
Okay, und so ein Stack kann man auch ganz gut benutzen.
Hier ist mal ein Beispiel für Postfix-Notationen. Ich weiß nicht,
ob ihr wisst, was Postfix-Notation ist,
wenn ich sowas schreibe hier, in Java einen normalen Ausdruck hinschreibe,
also meinetwegen drei plus vier, mal fünf plus sechs.
Dann wisst ihr, wie das hier ausgewertet wird.
Ihr wisst, dass es eine Klammerung gibt,
also wird hier drei plus vier zuerst gerechnet,
fünf plus sechs Anschließend werden die beiden Werte miteinander multipliziert.
Das heißt, in diesem Fall kommt er raus,
77, genau. Also 3 plus 4,
7 und 5 plus 6 und 11, 7 mal 11.
Soweit so gut, das ist eine Notation, die sogenannte Infix-Notation,
die ihr aus Java kennt und die auch üblich ist häufig,
die erfordert, dass man explizit klammert.
Ihr seht hier, ich musste diese Klammern einfingen,
weil wenn ich diese Klammern nicht gehabt hätte,
würde aufgrund der Bindungsstärke der Multiplikation zuerst hier 4 mal 5 gerechnet.
Dann käme ein anderer Wert raus.
Es gibt aber eine andere Möglichkeit, das hinzuschreiben,
nämlich in sogenannter Postfix-Notation
Und die Postfix-Notation, da ist es so,
dass der Operator nicht zwischen seinen Operanten steht, sondern dahinter.
Also das wäre sowas wie drei, vier plus. Dann habe ich,
bin der Plus-Apparator, ausgewertet wird quasi, dann sagt er,
okay, die beiden Operanten davor, die muss ich jetzt addieren.
Und um solche Ausdrücke auswerten zu können,
kann man sehr schön einen Stack benutzen. Das heißt,
wenn ich jetzt mir das vorstelle, hier drei,
vier plus, dann schreibe ich hier fünf,
sechs plus hin und dann schreibe ich hier Ein Mal hin.
Das ist der äquivalente Postfix-Ausdruck zu dem da oben. Also wenn das hier,
das dürfte das gleiche Ergebnis liefern, weil es wird immer,
wenn ein Operator gefunden wird, werden zwei Operanten vorher gesucht.
Die werden dann miteinander verknüpft mit dem Operator.
Das passiert hier genauso. Und dann passiert das aber nochmal.
Das heißt, es liegen dann ja zwei Werte irgendwie da,
sozusagen 3 plus 4 wird ersetzt durch das Ergebnis.
5 plus 6 wird auch ergesetzt durch das Ergebnis.
Und dann werden diese beiden Werte miteinander multipliziert.
Da braucht man gar keine Klammern. Das ist so ein nettes Detail,
dass man mit solchen Postfix so einer Postfix-Schreibweise keine Klammerung braucht,
um irgendwie die Reihenfolge der Auswertung zu beeinflussen. Das ist ganz schön. Und sowas kann man tatsächlich mit einem Stack sehr schön implementieren,
weil ihr einfach,
wenn ihr jetzt durch diesen Ausdruck durchgehen wollt und ihn vernünftig auswerten wollt,
also wenn ihr jetzt Software schreiben sollt,
die diesen Ausdruck vernünftig auswertet, dann wäre es relativ einfach,
wenn ihr eine Zahl findet, wird sie auf den Stack draufgelegt,
also drei wird auf das Stack draufgelegt,
damit die vier auf den Stack draufgelegt,
dann kommt ein Operator,
dann werden einfach zwei Elemente runtergenommen vom Stack,
ausgerechnet und das Ergebnis wird wieder auf den Stack draufgelegt
Dann kommt das gleiche wieder. 5, 6 plus,
5, 6 plus werden draufgelegt.
Dann wird das bloß ausgerechnet und der Operator bleibt liegen.
Das heißt, wir haben dann zwei Operanten auf dem Stack,
zwei Zahlen. Und dann kann die Multiplikation angewendet werden.
Also ein Stack bietet sich direkt an, um Postfix-Notationen auszuwerten.
Und hier ist tatsächlich mal das so implementiert. Können wir
uns mal angucken. Hier wird also ein String als Ausdruck übergeben.
Werte aus als Postfix. Dann ist es einfach so,
dass als erstes aus diesem Ausdruck, der da übergeben wird,
der muss eine bestimmte Syntax einhalten.
Das muss immer ein Leerzeichen zwischen Operanten und Operatoren
Der macht dir einen Split. Split ist eine Operation im
String-Framework, also in der String-Klasse von Java.
Und die splittet einen String auf an bestimmten Stellen.
Und diese Stellen sind hier beschrieben mit einem regulären Ausdruck.
Das heißt also, es können da beliebig viele Lehrzeichen sein,
es muss aber mindestens ein Lehrzeichen sein.
So, dann habe ich das als Bestandteile in einem Ray.
Ja, dann habe ich mich jetzt hier,
benutze ich tatsächlich diesen Stack aus dem Java-Collection-Stringwork.
Java-You-Till Stack auf Intiger. Kann man hier sehr schön sehen.
Jetzt mache ich eine erweiterte Vorschleife. Also ich gehe durch
alles dringend durch
Die in diesem Ausdruck enthalten waren, also diese Bestandteile hier.
Dieses Array von kürzeren Strings. Und dann wird einfach geguckt,
wenn das ein Plus ist,
dann mache ich auf dem Stack ein Pop und zweimal Pop.
Und die beiden Werte werde ich dann addieren und entsprechend wieder
das Ergebnis auf den Stack legen.
Ja, das Gleiche für die Subtraktion, hier Multiplikation,
Division, letztendlich immer das Gleiche.
Es werden zwei Elemente vom Stapel runtergeholt,
miteinander verrechnet und dann das Ergebnis wieder auf den Stack draufgepusht
Und ganz, wenn es keiner der Operanten,
keiner auch in der Operatoren ist,
dann wird hier der entsprechende Operant ausgewertet.
Also das ist einfach hier vorgeschrieben, es dürfen nur Konstanten sein,
Zahlen oder eben halt diese Operatoren.
Ja, und dann wird das einfach hier tatsächlich auf den Stapel drauf gepusht.
Ja, und das ist eigentlich schon alles.
Und wenn ich das jetzt aufrufe, dann kann ich tatsächlich
diesen wunderschönen Ausdruck hier mal hernehmen.
Ich hoffe, das geht.
Mal gucken. Werte aus, also eine statische Methode
Kann ich also jetzt hier diesen Ausdruck übergeben.
Ich habe da immer ein Leerzeichen dazwischen. Also ihr seht
hier drei, vier, fünf und so weiter.
Das müsste jetzt natürlich auch 77 rauskommen. Und ups.
Cool. Mal gucken, was ich hier jetzt falsch gemacht habe.
Hat die mal eine Idee, was ich falsch gemacht habe?
Bitte? Ah, das kannst du im Wesen sein.
Schauen wir mal. Ja, es kommt 77 raus.
Ihr seht dieses Split war nicht
besonders intelligent
Es hat am Anfang einen Lehrensträngen rausgeschnitten bei dem ersten Lehrzeichen.
Und das hat dann wohl den Abschurz bewirkt hier an der Stelle.
Gut. Ist ja auch nur dazu da gewesen,
dass ihr mal seht, wozu ein Stack so benutzt werden kann.
Ja. Ich weiß es nicht, ich bin ja
ein Beispiel, wenn ich bezüglich Flammen das bringe.
Was für ein eigenes Lehrensträngen zusammen. Er fragt an
dem Beispiel, rechne ich irgendwie X plus Y?
Also es werden, der Stapel ist ein Stapel von Intiger
Und diese Integers kann man direkt mit Infix-Notationen berechnen, weil sie umgewandelt werden.
Also da wird ein Autor Unboxing gemacht. Und der String,
der da irgendwo erzeugt wird, hier wird tatsächlich aus einem,
aus Arstin String S, wenn er denn weder ein Operator,
also wenn er keine der Operatoren ist,
dann muss es eine Zahl sein.
Und dann wird aus dieser Zeichenkette,
die diese Zahl darstellt, wird hier ein Integer gepasst.
Frage beantwortet, gut. Schön. Also das war
wie gesagt nur, um mal zu zeigen,
wie man
einen Stack benutzen kann
Nächste Frage ist, wie implementiert man den einen Stack?
Und da ist eben halt ein wichtiger Vorteil. Wir müssen bei
einem Stack nur immer auf ein Element den Zugriff erlauben.
Es ist also nur wichtig, dass man schnell an ein Element rankommt.
Bei einer Liste wusstet ihr beliebige Zugriff auf Positionen.
Da muss man irgendwie eine schlaue Implementation nehmen. Das heißt,
hier ist jetzt mal so eine Skizze angedeutet,
wie man zum Beispiel mit einer verketteten Liste so einen Stack tatsächlich auch implementieren kann.
Also immer dran denken, bei allem in der Objektorientierung
gibt es so diese zwei Sichten, einmal die Benutzungssicht.
Was bietet mir das Ding für Dienstleistungen an? Also ein
Stapel ist recht nützlich, um bestimmte Dinge zu tun.
Zum Beispiel Postfix, Ausdrücke auswerten zu können
Und zweite Frage ist, wie kann ich das Ding denn implementieren?
Auch das ist hier mal angedeutet, ist aber auch jetzt
keine Hexenkunst. Ja, das ist nicht weiter aufregend.
Gut, das ist auch tatsächlich eine generische Implementation.
Etwas, was ihr noch nicht so richtig kennengelernt habt.
Das heißt also, dass man hier so diese Platzhalter benutzen kann,
diese generischen Typparameter und dann an den entsprechenden Stellen auch diese Dinger benutzen kann als ein Typ.
Das ist ein bisschen fortgeschrittenes Zeug,
das gucken wir uns in SE1 noch nicht an.
Gut, damit sind wir schon bei der nächsten
wunderbaren Datenstruktur,
dem nächsten Datentyp
Das ist die Q. Gibt es noch Fragen zum Stack?
Ich glaube, das ist nicht wirklich so aufregend.
Sieht so aus, als wenn das soweit ganz gut verstanden war.
Eine Schlange. So, eine Schlange. Das wird auch
gerne als Q bezeichnet. Ihr wisst, was eine Schlange ist,
wenn ihr euch im Supermarkt an die Kasse anstellt,
dann macht ihr das üblicherweise so, dass ihr euch am Ende der Schlange stellt.
Seid in ihr seid extrem dreist. Üblicherweise stellt man sich
ans Ende und dann wartet man, bis man dran ist,
weil dann nämlich sozusagen an dem einen Ende erwartet die Kassiererin
die Elemente in der Schlange ab.
Und man selber stellt sich als Element dann am Ende
in die Schlange
Das nennt sich First-in-First-ous-Prinzip. Im Gegensatz zum Stack, da
war es Last-in-First-out. Hier ist es jetzt First-in, First-out.
Auch das ist etwas, was erstmal von der Grundrundidee sehr einfach ist.
Diese ganz normalen FIFO-Schlangen sind eine Ausprägung von Schlangen oder von Cues.
Es gibt auch gewichtete Schlangen, Priority Cues,
bei denen werden dann die Elemente nach Prioritäten sortiert.
Das heißt, es wird tatsächlich nicht das zuletzt hinzugefügte Element
dann als letztes bearbeitet. Es kann auch sein,
dass wenn ein Element mit hoher Priorität kommt,
dass dann Elemente, die schon länger warten,
auch weiter warten müssen
Das passiert zum Beispiel bei Betriebssystemen. Wenn ihr euch mit Betriebssystemen intensiver beschäftigt,
dann gibt es da bestimmte Algorithmen, wie Prozesse priorisiert werden.
Und wie dann auch diese Prioritätsschlangen abgearbeitet werden.
Anderes Beispiel für eine Schlange ist der Tastaturpuffer bei einem Computer.
Wenn ihr was tippt, dann wird er in diesen Puffer
was reingeschrieben und der Computer holt dann aus diesem Puffer diese Zeichen auch wieder raus.
Der Tastaturpuffer ist dazu da. Früher war das so,
dass wenn man zu schnell tippt,
dass dann der Computer nicht hinterherkam mit der Verarbeitung der Zeichen.
Und dass man deswegen in den Puffer was reinschreiben konnte,
also ein bisschen vorweg tippen konnte,
ohne dass der Rechner das verarbeiten musste.
Das ist heutzutage selten noch
ein Problem
Dass die Rechner zu langsam sind. Trotzdem gibt es diese Tastaturpuffer,
auch aus anderen technischen Gründen. Wir gucken uns mal wieder
ein Interface an, also zuerst wieder die Benutzungsseite.
Wie ist das mit so einem Q-Interface? Wir haben also hier eine Interface Q,
es gibt eine Operation NQ, also Einfügen in eine Schlange,
eine Operation DQ liefert ein Element aus der Schlange und wieder die Frage,
ob es leer ist.
Wir haben wieder so eine Q gegeben,
die würde ich mir jetzt immer so veranschaulichen,
dass die Dinger irgendwie so nebeneinander liegen. Das ist natürlich Geschmackssache,
aber im Stapel habe ich immer dieses Modell von,
ich lege etwas drauf, also stapel, die wachsen irgendwie,
wachsen irgendwie nach oben und so eine Schlange,
die ist halt irgendwie so, Ja, horizontal.
Hier habe ich eine Cue von Strings wieder.
Ich sage NQ, also füge das X am Ende ein.
Dann accue ich hier noch das Z, also füge noch das Z ein.
Und wenn ich dann sage, gib mir mal DQ,
das Element per DQ, dann wird mir hier tatsächlich das A geliefert.
Also an der einen Seite, auf der einen Seite
wird etwas weggenommen und hier wird es hinzugefügt.
Ja, auch das nicht weiter aufregend. Was ganz interessant ist,
ist, was man mit so einer Q machen kann.
Und da ist es wieder so,
das ist eine gute Hilfsdatenstruktur, wie wir gerade gesehen haben,
zum Beispiel Postfix-Ausdrücke, kann man Stack benutzen.
Hier haben wir jetzt so was, ich habe einen binären Baum,
ich möchte einen breiten Durchlauf machen. Das heißt,
ich möchte tatsächlich in der Reihenfolge diese Knoten hier besuchen,
wie das in dieser kleinen Zeichnung angedeutet ist.
Also ich möchte beim Wurzelknoten anfangen, möchte dann schichtweise alle Knoten bearbeiten.
Also von der Wurzel immer schichtweise weiter nach unten.
Wenn ich das jetzt euch als Aufgabe stellen würde,
würde ich denken, dass das nicht allen sofort einfällt,
wie man
das machen könnte
Da müsste man nämlich tatsächlich ein bisschen sich überlegen,
ja, ihr habt also irgendwie eine Möglichkeit von einem Knoten,
jeweils den linken und den rechten Teilbaum zu besuchen.
Das wäre so eine elementare Operation auf so einem Knoten.
So, und jetzt sollt ihr mit diesen elementaren Operationen in
der richtigen Reihenfolge diese Knoten besuchen. Das ist nicht
so ganz einfach. Ich habe das irgendwann mal gelesen,
wie das geht, nämlich mit einem, mit einer Q.
Und das ist tatsächlich sehr schön einfach. Hallo? Das
ist echt wunderbar. Ich habe so ein bisschen das Problem
mit meiner Tastatur her, die per Bluetooth angebunden ist.
Und jetzt hier zickt.
Macht nichts. Ich habe ja ein Touchscreen. Es wird
nur schwierig, wenn ich gleich irgendwie was angeben muss.
Hat die mal zufällig zwei AAA-Batterien dabei? Könnt ihr mir gleich
mal leihen dann in der Pause. Also die Lösungskizze ist,
ich füge erst das Wurzelelelement in eine Q ein, habe also eine Q von diesen Baumknoten.
Und solange die Q noch weitere Elemente enthält,
und dann habe ich halt, ich habe da ein Element drin,
habe ich eine Schleife, die sagt,
ich entnehme das erste Element, also den Wurzelknoten,
und füge beide Nachfolger ein, in die Q.
Also zuerst die zwei und dann die drei,
in die Qu ein. Also ich habe das Element für
für zwei und drei
hier ein
So, jetzt geht das weiter in der Schleife.
Ich entnehme das nächste Element, also hier die zwei.
Geh, lass mir davon den Nachfolger geben,
also die vier und die fünf kommen hier rein.
Und dann ist die zwei weg. Dann komme ich auf die drei,
füge die sechs und die sieben ein und die drei ist weg.
Und auf diese Art und Weise fülle ich sozusagen von hier
immer weiter Elemente ein und bearbeite sie von hier ab.
Ja, und wie gesagt,
das ist auch wieder eine ganz einfache, ganz einfache Lösungsskizze.
Man muss erst mal drauf kommen. Oh toll,
das ist wunderbar. Hier sind AAA-Batterien. Mal schauen,
ob mir das jetzt was hilft
Das ist ganz lustig hier. Sich hier und solche Probleme
habe, das ist ja... So.
Ja, ein Traum. Na? Nö. Will er
nicht. Sehr schön. Die Batterien sind möglicherweise nicht mehr
ganz so voll,
Oder es sind Akkus oder was auch immer.
Was für ein Mist. Schön. Also, hier ist
nochmal eine Implementationsskizze. Also auch da wieder, erstens,
wie benutze ich so eine Q? Und zweitens dann,
wie implementiere ich sie denn? Hier ist so angedeutet,
wie man das mit einem beschränkten Puffer machen kann.
Also die Maus funktioniert ja hoffentlich noch, oder?
Verdammt. Man sieht hier, das ist eins, ein Ray realisiert.
Das heißt, das Ray hat eine feste Größe,
deswegen beschränkter Puffer. Und dann wird sozusagen immer ringweise,
hier werden Elemente eingefügt, hier das X,
hier das
Und das Arbeit hier entnommen, das sind die gleichen Operationen,
die auf der anderen Folie auch schon drauf waren.
Und wenn hier tatsächlich dann der Puffer voll ist,
dann muss halt entsprechend da irgendwie gebremst werden.
Das nennt man einen begrenzten Puffer oder Bounded Buffer und ist eine relativ typische Implementierung umso.
Solche Cues mit begrenztem Speicherplatz zu implementieren. Ja,
dann habe ich jetzt das nächste Problem,
und das habe ich eben schon angedeutet,
ich habe hier jetzt zwei Interfaces gezeigt,
einmal für einen Stack und einmal für eine Cue.
Ach, was ich vielleicht noch zeigen könnte mal eben,
Moment, ich habe jetzt wieder, das ist also wirklich
Das geht so nicht. Ich muss hier nochmal eben...
Ich habe diese Batterien nämlich gerade heute Morgen reingelegt,
deswegen bin ich ein bisschen erstaunt,
dass die jetzt hier schlapp machen
Gut, also was ich eigentlich zeigen wollte, ist,
dass es ein Interface cute tatsächlich gibt im Collections Framework.
Also ich habe vorhin gezeigt, es gibt eine Klasse Stack,
da ist es tatsächlich im Collections Framework so gemacht,
dass sie eine Klasse implementiert haben, direkt.
Und da nicht nur das Interface dazu definiert haben,
aber bei einer Q gibt es tatsächlich ein Interface mit
diesen Operationen und noch einigen weiteren.
Und dann gibt es verschiedene Implementationen dieser Q.
Und tatsächlich ist die Linklist eine gültige Implementation einer Q.
Aber es gibt dann auch noch so Priority-Cues und Ähnliches im Collections-Framework.
Würde ich gerne zeigen, wenn ich mit meiner Tastatur hier irgendwie arbeiten könnte.
Das ist echt interessant. Ich könnte mal eben folgendes probieren
. Ah, oh, da bin ich wieder.
Ah, toll. Ja, ja, ja.
Mal schauen, wie lange das jetzt hält. Hier,
Java, YouTill. Naja, ja, war YouTill.
Und dann gibt es hier ein Interface Q.
Das wollte ich eigentlich
nur zeigen
Interface Q und dann gibt es hier All-Known-Implementing-Classes. Unter anderem ist hier die Linked List dabei,
aber es gibt hier auch Array Blocking Q,
Concurrent Linked Q, Delay Q, Priority Q und so weiter.
Ihr seht, das ist relativ häufig verwendet für verschiedene Zwecke,
für nebenläufige Geschichten und so weiter. Gut,
aber was ich eigentlich jetzt nochmal diskutieren wollte, ist,
wenn ich solche Interfaces habe, dann seht ihr hier,
diese beiden Interfaces sind auch dieses Interface hier mit dem wunderbaren Namen Geblubber,
mit den Operationen Blable und Blub,
die unterscheiden sich eigentlich nur in diesen roten Anteilen.
Also hier ist Empty vielleicht noch und blub, aber das
ist alles das Gleiche. Also syntaktisch ist das fast das Gleiche,
sind nur die Namen ein bisschen anders. Die Semantik,
also was da wirklich passiert, die steckt hier nicht drin.
Das heißt, in dem Sinne sind Interfaces nur semiformale Spezifikationen.
Das habe ich auch schon mal angedeutet, als ich über Interfaces gesprochen habe.
Es ist semiformal, weil natürlich formal festgelegt ist,
dass hier ein bestimmter Elementtyp übergeben werden muss.
Und dass hier ein bestimmter Elementtyp rauskommt und dass diese Operation
hier Bolien liefert. Also es sind so bestimmte Dinge festgelegt,
aber das sagt wenig darüber über Last
In, First Out oder First In, First Out
Das heißt, die Semantik dessen,
was diese Interfaces oder die Implementation dieser Interfaces leisten sollen,
die können wir eher momentan mit den Mitteln von SE1
nur mit Kommentaren angeben, also informell.
Das ändert nichts daran, dass man trotzdem sprechenden Namen verwenden sollte.
Es gibt aber in der Informatik schon seit vielen Jahren den Versuch,
das besser zu machen und dass einer dieser erfolgreichen Versuche sind,
die sogenannten abstrakten Datentypen.
Also in der theoretischen Informatik ist das dann
sehr intensiv untersucht worden. Wie kann man denn solche,
die Semantik solcher Datentypen vom Maler beschreiben in Form,
also einer formalen Spezifikation
Das heißt also, die Grundidee ist, dass ein Datentyp
durch sein Verhalten und nicht durch seine Implementation, definiert ist.
Also nicht, da ist Code, der macht irgendwas,
das ist die Semantik, das ist das,
was wir in Java eigentlich üblicherweise vorfinden, sondern ich beschreibe als Spezifikation,
wie das Verhalten sein soll und mache das so exakt,
dass dann die Implementation ziemlich unzweifelhaft vorgenommen werden kann.
Wenn ihr später irgendwie professionelle Software entwickelt und ihr habt mit Spezifikationen zu tun,
wird das nie, ich sage, fast nie der Fall sein,
dass ihr eine formale Spezifikation bekommt,
die ganz eindeutig ist, sondern es gibt immer Spielraum,
wie man eine Spezifikation verstehen kann
Hier ist es aber der Versuch,
sozusagen die Semantik exakt anzugeben, ohne irgendetwas zu implementieren.
Also abstrakt im Sinne von, abstrahieren von einer konkreten Programmiersprache.
Und hier ist das mal so angedeutet, das ist so
ein kleiner Ausblick auf das, was noch kommt in SE2.
Da werden wir das uns mal ein bisschen genauer angucken,
dass man nämlich sagt, es gibt so einen Typ,
das ist ein, der heißt Stack und der hat als Elementtyp hier G,
das ist sozusagen auch hier wieder ein generischer Parameter.
Und dann sind hier die Operationen,
die werden jetzt hier Functions genannt, die Funktionen.
Und das ist auch schon ein Unterschied zwischen diesen abstrakten Datentypen
und dem, was bei so einer Objektorientierung so kennen.
In der Objektorientierung wissen wir, ein Push ändert den Zustand
des Objektes, auf dem gepusht wird.
Da stelle ich mir das wirklich so vor,
das ist ein Behälter und ich pushe da was drauf und
dann ändert sich der Zustand des Behälters.
Hier ist das anders. Hier ist das tatsächlich als Funktion
formuliert. Das heißt, ich habe die Funktion Push,
die bekommt als Argumente einen Stack von einem Elementtyp und ein
Element und liefert dann einen neuen Stack.
Also ein Stack mit drei Elementen,
bekommt ein Element dazu und dann hat man,
bekommt man raus ein Stack mit vier Elementen.
Das ist eher die funktionale Sichtweise. Auch beim Pop hier
Stack G und liefert mir wieder ein Stack.
Das heißt, ich habe eine Überführung von einer Funktion von
einem Stack in einen anderen Stack.
Wenn ich ein Element da runternehme mit Pop
Das ist hier, dieser Pfeil ist durchgestrichen. Das ist absichtlich so.
Das ist nämlich eine partielle Funktion. Und die ist partiell,
weil ich bei einem Pop ja davon ausgehen muss,
dass da auch wirklich ein Element auf dem Stapel drauf ist.
Sie sollte vorher prüfen, dass ich nicht auf einem leeren Stapel Pop,
obwohl, weil dann kann ich nichts runterholen.
Das heißt also, das ist nicht für alle Stacks definiert.
Für einen leeren Stack ist Pop, hat sozusagen keine Definition.
Das ist ein Teil der Semantik, die dann tatsächlich festgehalten
wird in Aktionen. Und man sieht hier,
also das ist erst mal mit der partiellen Definition hier,
Das sieht man daran, wenn ich jetzt hier unten diese sogenannte Pre-Condition sehe.
Hier steht nämlich für Pop, muss gelten, Require, not MTS.
Also ich darf Pop nur aufrufen, wenn das nicht leer ist.
Und auch top, top liefert mir von einem Stack,
das oberste Element, ohne es runterzunehmen.
Und entsprechend ist dann hier auch bei Top die Vorbedingungen,
dass das nicht leer sein darf.
Das sind diese sogenannten Pre-Conditions, das sind also so Anforderungen,
die an einzelne Functions oder Operationen hier gestellt werden.
Und dann gibt es hier die Aktionen und die definieren dann eigentlich tatsächlich die Semantik.
Das heißt also zum Beispiel für jeden X aus der Elementmenge,
für jedes X-Aus-Elementmengen und für jeden Stack von diesen Elementen,
muss alles das hier gelten.
Zum Beispiel, dass wenn ich einen Stack habe und ein Element
draufpuschel und hinterher dann Top-Aufrufe, dann muss da X rauskommen.
Oder wenn ich etwas pushe und dann hinterher Pop aufrufe,
dann muss ich wieder den ursprünglichen Stapel zurückbekommen.
Das definiert tatsächlich dann die Semantik. Das heißt,
hier steckt jetzt ganz viel drin,
was mehr sagt als nur die Signaturen,
die wir mit unseren Interfaces in Java
beschreiben können
Und was aber für uns schon gilt,
was wir ja schon sehr intensiv machen,
ist dieses Trenne die Spezifikation, also die Umgangsform,
die Operation, die angeboten werden, von der konkreten Implementation.
Und wenn ich Implementationen verberge, dann kann ich sie auch austauschbar machen.
Gut, das ist schon mehr oder weniger alles,
was ich zum Stack-on zur Cue sagen wollte.
Also Stack, Sammlungstyp, bei dem Elemente nur nach
dem Lieferprinzip Entfernung hinzugefügt werden können.
Zuletzt eingefügt wird als erstes zurückgeliefert. Q ist ein ähnlicher
Sammlungstyp, nur mit dem FIFO-Prinzip umgesetzt.
Zuerst eingefügt Element wird auch als erstes zurückgeliefert.
Gibt es noch Fragen zur Q? Das zieht mich danach
aus. Ist auch, glaube ich, alles gut nachvollziehbar.
Deswegen werden wir jetzt nochmal uns vor der Pause noch etwas
ein bisschen mit Sortieren beschäftigen. Sortieren. Das ist,
ja, eine klassische Informatikaufgabe, üblicherweise für Sammlung.
Ich will ja irgendwie eine Sammlung von Elementen sortieren.
Und zwar will ich die in eine geordnete Reihenfolge bringen.
Voraussetzung, damit ich sortieren kann, ist, dass es
eine Ordnungsrelation gibt. Es muss also eine strenge,
schwache Ordnung sein.
Für ein Merkmal dieser Objekte
Und dieses Merkmal heißt auch Sortierschlüssel. Und die
darauf bezogene Ordnungssituation heißt Sortierkriterium. Und die Algorithmen,
nach denen Sammlungen sortiert werden oder mit denen Sammlungen sortiert werden,
die heißen Sortierverfahren. Wir brauchen das im Alltag ziemlich viel.
Also ich bin mir ziemlich sicher, dass ihr unglücklich werdet,
wenn ein Telefonverzeichnis, ein Telefonbuch nicht alphabetisch sortiert wäre,
weil dann würdet ihr ziemlich lange suchen, bis ihr einen Namen findet.
Mal wieder so die Überlegung lineare Suche. Klar,
geht natürlich viel schneller, wenn man irgendwie nach der lexikografischen Ordnung quasi hashen kann.
Fahrpläne üblicherweise auch, wenn ihr zum Bahnhof geht, dann erwartet ihr,
dass ihr eine bestimmte Abfahrtszeit an der richtigen Stelle findet,
weil das nämlich sortiert ist nach Abfahrtzeiten
Ganz selbstverständlich, dann ist hier auch definiert, was ist eine aufsteigende Ordnung,
ist was eine absteigende Ordnung, ist, dass es nicht weiter spannend.
Was ich auch mal zeigen kann,
ist sowas wie hier in diesem Ordner von Windows,
da seht ihr jetzt den Ordner,
dem ich hier gerade die Unterlagen für diese Vorlesung habe,
also irgendwelche Blue Jay-Projekte, irgendwie so eine Textdatei,
zwei PowerPoint, Dateien.
Und ihr seht, dass hier jetzt ich darauf klicken kann und sagen kann,
sortier das mal nach Namen und zwar aufsteigen und absteigend.
Das ist ganz normal für uns, dass wir das können.
Genauso kann ich hier sagen, Änderungsdatum,
so Thema nach dem Änderungsdatum und schwupps,
ist das hier alles nach dem Datum sortiert
Oder sortiere es mal nach dem Typ. Dann habe ich
hier die PowerPoint-Dateien, die Ordner und das Textdokument.
Entsprechend sortiert. Das ist ganz selbstverständlich. Das heißt,
dieses Sortieren brauchen wir ständig und überall. Und bevor wir uns jetzt angucken,
das werden wir nach der Pause dann ausführlich machen,
wie man so einen Sortieralgorithmus denn bauen kann,
also wie man wirklich aktiv sortieren anbieten kann,
wenn man ein Anbieter sein möchte, will ich euch erst mal zeigen,
wie man Sortieren konsumiert.
Wir haben jetzt eben schon live hier diese Sortieralgorithmen,
die da irgendwo in Windows implementiert sind,
Und wie immer ist es so,
ihr braucht nicht jetzt hier, sortieren, implementieren lernen,
damit ihr dann zukünftig eure eigenen Sortieralgorithmen schreibt.
Das ist Quatsch, weil ganz viele Sortieralgorithmen liegen schon fertig
irgendwo in Bibliotheken vor. Das ist in jeder vernünftigen Programmiersprache
, ist es so, dass es da irgendwas gibt.
Auch für Java gilt das. Es gibt so außen
implementierte Sortierverfahren im Javac Lections Framework zur Benutzung bereit.
Es gibt also eine Operation oder eine Familie von Operationen,
Java You Till Collections Sword,
also Sortieren einer als Parameter übergebenen Liste,
damit kann ich
Listen sortieren
Oder ich habe hier Java You Till Race, Sword,
sortieren eine als Parameterübergebenen, als Parameterübergebenen Arace.
Ich zeige das mal, in der API, das ist wieder Java You Till.
Und dann gibt es hier diese Klassen, ihr seht ja,
das sind Klassenoperationen, die sind mit Static operiert,
deklariert, also Sword, Incollections und in Arace.
Die heißen tatsächlich wirklich so, also Collections,
das ist der Name dieser Klasse Collections und eine andere Klasse Arace,
wir sind hier,
hier gibt es die Klasse Arace und hier die Klasse Collections
Mal auf Collections draufgehe. Dann gibt es hier ziemlich viele
Methoden, ziemlich viele Methoden, also wirklich viel Zeug.
Und hier ist aber jetzt auch Sword.
Also hier gibt es einmal dieses Sword und einmal dieses Sword.
Zweimal sortieren, einmal mit einer Liste,
nur und einmal mit einer Liste und einem sogenannten Comparator.
Und das werden wir uns gleich mal angucken, was das bedeutet.
Und dann gibt es das Gleiche aber auch für Arace.
Jetzt gehe ich mal auf Arace,
also auch so eine reine Diensteanbieterklasse, lauter statische Methoden,
Binary Search,
on Equals und bla,
also auch wieder
ganz viel Zeug
Hashcode, bla, bla, sword. So, hier haben wir jetzt Sword.
Und ihr seht hier, es gibt ziemlich viele Swords.
Sortieren von einem Charles-Ray-Satieren, von einem Double-Array-Satieren von und so weiter.
Float, Int und so weiter. Also da gibt es ganz viele,
die heißen alle Sword und bieten alle mehr oder weniger
den ähnlichen Dienst an, nur mit sehr unterschiedlichen Parametertypen.
Okay, und ich wollte mir jetzt mal hier mal zeigen,
wie man das benutzen kann für Collections. Dazu habe ich das,
was ich gerade eben hier in dem Explorer-Fenster gezeigt habe,
einfach mal so spontan nachprogrammiert. Das heißt,
ich habe hier so eine Logik von, es gibt einen Verzeichnis-Eintrag,
ein verzeihenes Eintrag im Verzeichnis enthält irgendwie die Namen der Datei.
Das Datum, wann es zuletzt geändert wurde,
was für ein Typ es ist, also so wie hier,
das ja auch zu sehen ist, also irgendwie Name, Datum, Typ.
Und genau sowas habe ich immer nachprogrammiert,
also das sieht hier so aus, ein Verzeichniseintrag,
da gibt es irgendwie Gib-Datum, gib Name,
gib Typ und ein Konstrukt, da gebe ich halt alles Annahme,
Datum und Typ.
Also ich habe sozusagen ein verzeichnis Eintrag in einem Verzeichnis hier modelliert.
Relativ einfach. Dann habe ich gesagt, naja,
so verzeihniseinträge sind natürlich in einem Verzeichnis drin,
das heißt also das, was ich hier so sehe,
könnte ich sagen, dass dahinter muss ja irgendwo ein echtes Verzeichnis sein im Dateisystem.
Das habe ich hier so modelliert, dass ich gesagt habe,
es gibt ein Verzeichnis und die ist auch wieder total einfach implementiert.
Es gibt eine Operation, Füge ein, ein Verzeichniseintrag und
ich kann mir eine Liste geben lassen aller verzeihenes Einträge.
Also liefert eine Liste der Einträge dieses Verzeichnisses. Das ist
tatsächlich so implementiert, dass da eine Kopie gemacht wird.
Also das ist nicht die Liste, die da intern verwendet wird,
wird dann rausgegeben, sondern es wird wirklich eine Kopie rausgegeben.
So, ja, und dann habe ich,
das ist relativ wichtig, wenn man sich mit solchen grafischen Oberflächen beschäftigt,
das, was jemand hier sieht,
ist ja
eine Sicht auf das Verzeichnis
Ich habe das ja auch schon gesehen, als ich hier
mal so dieses andere Ding gezeigt habe, hier CMD.
Damit könnte ich jetzt auch eine Sicht haben auf das gleiche Verzeichnis.
Ich könnte das gleiche Verzeichnis hineingehen und würde dann zwei verschiedene grafische Ansichten haben.
Das heißt also auch das, was man hier sieht,
das ist eine bestimmte Ansicht auf das Verzeichnis.
Und das habe ich hier dann auch exzeptiert und nochmal so implementiert,
dass ich gesagt habe, naja,
ich habe eine Klasse für Verzeichnisansichten.
Und die ist auch wieder ganz einfach, bekommt ein Verzeichnis.
Klar, es soll ja die Ansicht auf ein Verzeichnis liefern
und lässt sich von diesem Verzeichnis die ganzen Einträge geben
Und speichert die in einer Liste von Einträgen.
Hier deswegen, das ist Liste von Verzeihniseinträgen.
Das sind die gezeigten Einträge, dass was wirklich sichtbar ist.
Das, was in einer Verzeihnisansicht sichtbar sein soll.
Und dann habe ich hier Operationen implementiert, Sortieren, Ansicht nach Name,
Sortiere, Ansicht nach Typ, gucken wir uns gleich an.
Und dann hier Schreiber auf Konsole. Und dann lasse ich
also alle Verzeichnis-Einträge einfach mit System Outtrinteln ausgeben. So,
dann habe ich hier einen Klienten, der das Ganze benutzt.
Nur, dass ich jetzt sozusagen einfach mal einmal seht,
das ist wieder eine statische Methode,
die nur hier mal eben so die Benutzung zeigen soll.
Ich erzeuge mir ein Verzeichnis, ich mache mir mehrere Verzeichnis-Einträge Also so Beispiele hier,
so ähnlich wie das bei mir im Verzeihungsgrade ist,
also Choreografie, Folien, Folien, Alt,
Projekte als Ordner, sind also alles Verzeihniseinträge.
Die füge ich alle in das Verzeichnis ein. Dann mache ich
mir eine neue Verzeichnisansicht und stecke da das Verzeichnis rein.
Und dann rufe ich auf, schreibe auf Konsole.
Also diese Verzeichnisansicht soll auf die Konsole ausgegeben werden.
Mehr passiert hier erst mal nicht. Also hier tatsächlich da
ein Schreiber auf Konsole, System Outmanagement für eine Leerzeile.
So, wenn ich das jetzt ausführe, dann kommt da das raus.
Also einfach habe ich da mal so ein bisschen,
dass es einigermaßen, also ich habe hier wirklich in der Reihenfolge,
in der die da reingeschmissen werden, in diese Liste,
tauchen die auch hier auf
Ihr seht, das ist weder nach Name sortiert.
Hier, CF, P-F-F-N. Ist nicht sortiert. Ist auch nicht
nach Typ sortiert, ist auch nicht nach Datum sortiert.
Ich habe hier als Datum einfach ein Int genommen, eine Einfachheit halber.
Man kann an ungefähr verstehen, was gemeint ist.
2013, Januar 06. Also das sollen irgendwie sehr vereinfacht ein Datum sein.
Ja, und was ich jetzt einfach mache, ist,
ich möchte ja zuerst mal das sortieren. Das heißt,
ich möchte tatsächlich jetzt, dass diese Ansicht sortiert wird.
Das heißt, ich sage der Ansicht, dass sie das jetzt sortieren soll.
Also hier Verzeichnisansicht, Sortiere, Ansicht nach Name. Das heißt,
dann wird das, was in dieser Ansicht diese listiger intern
in dieser Ansichtsklasse drin ist, wird nach Name sortiert.
Und das wird auf die Konsole geschrieben. Also auch das
können wir mal eben übersetzen, ausführen. Und dann sieht das
so aus. Also tatsächlich sieht man jetzt hier CFFNP.
Also das ist tatsächlich jetzt lexikografisch nach Namen des Eintrags sortiert.
So, und dann habe ich hier auch noch implementiert,
dass das Ganze nach Typ sortiert wird.
Auch das können wir noch mal eben angucken. Ich habe
einfach hier auf dieser Verzeihnisansicht aufgerufen, sortiere Ansicht nach Typ.
Wir müssen uns jetzt gleich noch mal Verzeichnisansicht ansehen,
wie das da gebaut ist
Nur dass es erstmal in der Benutzung seht, dass es jetzt
keine Hexenkunst. Also auch das können wir mal übersetzen.
Und wenn ich das jetzt ausführe, dann sieht man hier,
das ist zum Beispiel jetzt nach Typ sortiert.
Also Ordner, PowerPoint und dann Text.
Und dann ist das hier wieder irgendwie in einer anderen Reihenfolge.
Ja, das ist erstmal nur, um zu zeigen,
wie relativ einfach das ist. Das habe ich heute Vormittag in,
sagen wir mal, einer halben, dreiviertel Stunde runter implementiert.
Das Spannende ist jetzt hier, das eine,
was ihr das erste Mal jetzt seht, ist,
diese Methode hier, das ist der entscheidende Auf,
wofür ihr nicht eigentlich zeigen will.
Java You Till Collections, Sword. Und ich übergebe,
Eine Liste von Einträgen, eine Liste von Verzeichniseinträgen.
Das ist mir meine gezeigten Einträge. Und da sage ich
einfach, sortiere das und dann macht er das.
Ich muss mich um nichts kümmern. Ich kann einfach sagen,
sortiere das mal. Stimmt nicht ganz. Ich muss mich
darum kümmern, dass diese Einträge auch wirklich sortierbar sind.
Und das ist wirklich tatsächlich technisch eine Anforderung. Ich kann
hier nicht einfach eine beliebige Liste reinstecken von irgendwelchen Elementtypen,
sondern der Elementtyp muss sortierbar sein.
Und ein Elementtyp ist sortierbar, das kann ich jetzt hier zeigen,
der Verzeichniseintrag ist ja das, wonach wir sortieren.
Und der implementiert Das Interface Java-Lang comparable, Verzeichniseintrag,
also ein comparable von Verzeihniseinträgen. Und dieses Java-Lang comparable,
das ist ein ganz einfaches Interface, das hat eine Operation, Compare 2.
Und da kann ich dann das eine Objekt fragen, komm,
vergleich dich mal mit einem anderen Objekt des gleichen Typs.
Und es liefert einen Int und der Int ist negativ,
wenn das gefragte Element kleiner ist, das ist positiv,
wenn das gefragt hätte,
wenn man größer ist und es ist null,
wenn die gleich sind
Dieses Comparable muss nicht, also tatsächlich hier, in dieser Klasse implementieren.
Ich kann das mal in der Implementation zeigen. Hier,
also das sind die Exemplarvariablen, alles keine Überraschung.
Die ganzen Ghetter hier, gib Name und so weiter.
Hier ist das to string, das ist einfach
nur so ein bisschen das mit einem Tab hübsch andeutet.
Aber hier ist das compare to implementiert.
Also ich habe tatsächlich hier oben gesagt, Verzeihniseintrag,
Implements, comparable Verzeihniseintrag, das musste ich machen.
Und dann habe ich hier jetzt die Compare-to-Methode implementiert und zwar ganz einfach,
ich habe gesagt, die natürliche Ordnung,
also Verzeihniseinträge haben eine natürliche Ordnung und die definiert sich über
ihre Namen
Also der Name des Eintrags definiert die natürliche Ordnung von Verzeichniseinträgen. So,
und wenn ich also tatsächlich in der Verzeichnisansicht hier jetzt dieses Thord benutze,
dann darf ich das machen, weil diese Dinger sortierbar sind,
weil die dieses Comparable Interface implementieren.
Die Elemente, die hier in der Liste übergeben werden.
Ich könnte das auch anders machen. Ich kann nämlich auch,
und das ist hier jetzt die zweite Möglichkeit,
das Thord aufzurufen, das ist die andere Möglichkeit.
Hier gebe ich wieder eine Liste rein, aber ich gebe noch
was anderes rein, nämlich hier einen Vergleicher, einen Typvergleicher.
Das heißt, ich gebe sozusagen Etwas an,
was das Sortierkriterium festlegt für die Elemente.
Und das ist jetzt hier so ein bisschen fortgeschrittenes Zeug,
das werden wir eigentlich erst so richtig uns nochmal in SE2 ansehen.
Hier habe ich tatsächlich jetzt innerhalb einer Methode eine lokale Klasse definiert.
Eine klasse Typvergleicher, die implementiert dieses Interface Java, you till comparator.
Und das Comparator Interface ist so ähnlich wie das Comparable.
Bei einem Comparable ist es so, ein Element kann gefragt werden,
dass es sich selbst vergleicht mit einem anderen.
Das Comparator ist sozusagen neutral, bekommt zwei Einträge und vergleicht die miteinander.
Ich kann also hier jetzt implementieren in meinem Comparator,
dass die beiden Typen verglichen werden, von den beiden Verzeihniseinträgen.
Es soll also nach Typ sortiert werden. Und was ich
natürlich jetzt hier so implizit noch nicht verraten habe,
ist dieses Compare 2, kann ich auf einem String aufrufen,
weil Strings auch das Comparable Interface implementieren.
Ich kann also Strings mit größer, kleiner vergleichen.
Deswegen kann ich Strings auch sortieren. Ohne Weiteres. Und
ich kann eben halt auch hier einen Typ wie mein Verzeichniseintrag
entsprechend mit so einem Comparator hier implementieren.
Das heißt, ich habe mir dann hier,
ich habe hier sozusagen ganz lokal nur so eine kleine Klasse definiert,
den Vergleich zwischen zwei Verzeihniseinträgen macht,
indem sie die Tüten vergleicht
Und dann habe ich mir hier einen Typvergleicher von dieser Klasse gleich mal gebaut.
Und diesen Typvergleich übergebe ich hier als Parameter. Ich kann also sozusagen
auch beim Sword mit angeben, wonach sortiert werden kann.
Ich kann das frei programmieren. Also beliebig da irgendein Sortierkriterium
implementieren. Das nur, dass ihr es mal seht,
so benutzt man das Zeug im Collections-Framework.
Das ist das, was schon da ist. Und wenn
da jetzt keine Fragen zu sind, da ist eine Frage dazu.
Was ist eine strenge Schlappe? Eine strenge schwache Ordnung heißt,
Das ist tatsächlich hier, dass es eine Ordnungsredaktion kleiner gibt.
Oh Mann, jetzt bin ich, jetzt komme ich ein bisschen in Schwimmen hier.
Strenge, schwache Ordnung ist ein mathematischer Begriff, der besagt,
dass sie mit, dass sie auch gleich sein können, die Elemente.
Das heißt, ich muss sie in eine Ordnungsrelation,
in eine Ordnung bringen können. Aber jetzt bin ich tatsächlich
ein bisschen überfragt an der Stelle, Zombie.
Bin ich jetzt gerade etwas überfragt? Da bin ich
jetzt schlecht vorbereitet. Das ist jedes Jahr wieder so,
dass ich an dieser Stelle so das eigentlich weiß.
Und diesmal bin ich ein bisschen auf den falschen Fuß erwischt.
Strecke, schwache Ordnung, könnt ihr in jedem Mathematikbuch nachlesen.
Sonst noch fragen, bloß keine blöden Fragen mehr, bitte.
Haha, nein. Noch Fragen, das war keine blöde Frage.
Es war eine sehr gute Frage. Sonst noch Fragen zum
Sortieren, erstmal jetzt für die Benutzung vom Sortieren.
Dann würde ich sagen, machen wir jetzt fünf Minuten Pause.
Die strenge schwache Ordnung heißt tatsächlich,
ich hatte mich wieder etwas ändern müssen,
dass es das eindeutig ist, ob etwas Kleines als etwas anderes,
solange sie nicht gleich sind.
Das heißt, diese strenge schwache Ordnung lässt zu,
dass Elemente auch gleich sein können in Bezug auf diese Ordnungsrelation
Das ist das Entscheidende an dieser Stelle. Es müssen also nicht,
das wäre eine strenge Ordnung, wäre es,
wenn sie wirklich streng sortierbar sein müssten und streng schwach heißt,
sie können auch gleich sein.
Gut, das Konsumieren haben wir gesehen. Jetzt wollen wir
uns mal ansehen, wie man Sortierverfahren implementieren kann.
Jetzt geht es tatsächlich darum, wie man denn sowas bauen kann.
Und da gibt es verschiedene Kriterien erstmal,
wie man die klassifizieren kann. Also das eine ist,
die Frage der Komplexität, das heißt,
wie viele Schritte oder Operationen oder einfache elementare Schritte sind notwendig,
um L-Elemente zu ordnen.
Und das kann man unterscheiden nach im besten Fall,
im durchschnittlichen Fall und auch im schlechtesten Fall.
Das ist auch teilweise abhängig von der Vorsortierung der Elemente
Dann der Speicherbedarf. Es gibt Sortierverfahren, die finden eine
Anzahl von Elementen vor und sortieren die da, wo sie sind.
Ja, das passiert üblicherweise durch so Vertauschen von Elementen.
Das nennt sich dann in Sito oder in Place-Sortierverfahren.
Das heißt, wenn man nicht zusätzlichen Speicher braucht,
um die Dinge zu sortieren.
Dann gibt es den Begriff der Stabilität. Ein Sortierverfahren ist stabil,
wenn es die ursprüngliche Reihenfolge zweiergleicher Elemente, da haben wir es wieder.
Also wenn Elemente gleich sind und die haben eine bestimmte Reihenfolge zu Anfang des Sortiervorganges,
dass dann die relativ zueinander in dieser Reihenfolge bleiben und
nicht das noch durcheinander geworfen wird
Es gibt da tatsächlich einfach die, bei manchen Kontexten gibt
es diese Art von, dass es stabil sein muss.
Und da sind bestimmte Algorithmen nicht geeignet, wenn sie nämlich
tatsächlich instabil sind. Und dann ist es so,
dass man noch die Art des Algorithmus unterscheiden kann.
Also sind die Sortierverfahren vergleichsbasiert? Dann werden Elemente paarweise verglichen.
Es gibt aber auch andere Sortierverfahren, die nicht vergleichsbasiert sind.
Bucket Sword, Radic Sword, die brechen tatsächlich Sortierschlüssel direkt aus,
aus denen der Sortierschlüssel direkt die Reihenfolge der Elemente.
Das ist so eine spezielle Form von Hashing.
Können wir uns dann vielleicht noch kurz angucken,
aber wahrscheinlich jetzt ein bisschen knapp. So, Komplexität von Sortierverfahren,
also eine obere Schranke für die Anzahl von Schlüsselvergleichen,
die im schlechtesten Fall notwendig sind, um N-Objekte zu sortieren.
Also ich möchte sozusagen Garantien bekommen, wie lange das maximal dauert.
Für vergleichsbasierte Sortierverfahren gilt, dass diese Schranke C-Max im besten Fall,
oh von N-Logma, N-My-Log-in ist. Das heißt,
wenn ihr Sortierverfahren euch anguckt, die darauf basieren,
zum Beispiel In-Place-Verfahren, die immer nur Elemente vertauschen,
die können nie schneller sein als N-My-Log-in.
Also ich habe N-Elemente, dann ist N-My-Log-in da sozusagen
das Optimale,
was man rausholen kann
Allgemein ist ein Sortierverfahren von der Ordnung O von G von N.
Wenn für die Zahl der Operation F&N beim Sortieren einer Liste
mit N-Elementen gilt, und das ist hier diese etwas kryptische Formel.
Diese Funktion ist die echte Aufwandsfunktion. Das sind die Operationen,
die man zählt. Und die muss immer kleiner gleich sein, dieser Ordnungsfunktion.
Immer. Und zwar das aber dann ab einem bestimmten Zeitpunkt,
ab einem bestimmten Element N, N größer N0,
also ab einem bestimmten Größe, gilt einfach,
dass diese Funktion hier diese immer dominiert.
Das heißt also, dass diese Funktion immer kleiner gleich ist.
Das kann hier meinetwegen auch noch mit dem konstanten Faktor multipliziert sein,
aber es muss eben halt ein solches C geben,
eine solche Konstante und auch ein solches N0. Ab da ist es einfach immer so,
dass diese eine Funktion die andere dominiert.
Hier ist jetzt nur noch mal was gesagt.
Das ist eine relativ wichtige, qualitative Aussage für euch.
Typische Komplexitätseigenschaften von Sortierverfahren, jetzt von vergleichsbasierten Sortierverfahren, gute Verfahren N-Log-N,
N mal Log-N und einfache Verfahren quadratisch, N- Quadrat.
Und da werden wir uns auch vielleicht zweimal ansehen.
Ich will das noch mal hier zeigen mit diesen Aufwandsfunktionen.
Also das bin ich jetzt zum Aufwandsfunktionen habe,
Hier ein Quadrat, dann ist dann das tatsächlich hier von
der Ordnung ein Quadrat. Jetzt habe ich eine andere Funktion,
die macht irgendwie N mal Log-in plus zwei.
Da ist also irgendwie so ein konstanter Faktor drin.
Dann ist das von der Ordnung N mal Log-in.
Dann ist es tatsächlich so, dass diese beiden Funktionen hier
an unterschiedlichen Punkten starten. Das heißt, diese Funktion ist
zu Anfang besser. Also bei Kleinlisten kann das sein,
dass diese eine Funktion besser ist,
aber ab einem bestimmten Punkt liegen die Werte der Funktionen,
geh immer unter denen der Funktion F nennen.
Das heißt also,
diese dominiert immer
die andere
Das ist letztendlich das, was man bei diesen Aufwandsberechnungen immer mit einbeziehen muss.
Das werdet ihr ausführlich in formale Grundlagen der Informatik euch ansehen.
So, jetzt gucken wir uns mal so ein paar Prinzipien von
vergleichsbasierten Sortierverfahren an. Das eine ist hier so vertauschen,
tatsächlich einträgige Miteinander vertauschen, einfügen,
indem man eine sortierte Teilfolge herstellt und dann in diese Teilfolge was einsortiert oder tatsächlich so Auswahlverfahren,
das ist das, was richtig schnell ist,
wenn man so tatsächlich genau weiß,
welches Element an Platz 1 gehört,
welches an Platz 2
gehört und so weiter
So, und was man aber immer wieder hat als Grundrundoperation,
sind diese Dinge wie Vergleich zweier Elemente und bewegen eines Elementes bzw. Vertauschen von zwei Elementen.
Jetzt gucken wir uns mal das allereinfachste Beispiel an.
Das ist Bubble Sword. Der Algorithmusvergleich der Reihe nach zwei benachbarte Elemente
und vertauscht sie, falls sie in der falschen Reihenfolge verlinkt.
Das ist etwas, was jeder Mensch,
der so ein bisschen sich mit Sortieren beschäftigt hat,
mal irgendwie gesehen hat, auch mal gebaut hat.
Ich habe also jetzt hier ein Beispiel,
hier sind irgendwie fünf Zahlen.
Nicht irgendwie,
hier sind fünf Zahlen
Und ich vergleiche zuerst die beiden und stelle fest,
die sind nicht in der richtigen Reihenfolge.
Also vertausche ich sie. Anschließend sieht das so aus,
die 700, die 55 sind vertauscht.
Jetzt vergleiche ich als nächstes diese beiden Zahlen,
diese beiden benachbarten Zahlen, die sind in der richtigen Reihenfolge.
Dann vergleiche ich hier die 78 mit der 12, stelle fest,
die sind nicht in der richtigen Reihenfolge, also vertausche ich sie.
Also habe ich jetzt die 78 hier rüber geschoben,
dann vergleiche ich die 78 mit der 42, stelle fest,
die sollten vertauscht werden und dann habe ich im ersten Durchlauf hier tatsächlich die 78 ganz nach rechts gebabbelt.
Das ist so ein bisschen so dieses,
da kommt der Begriff Bubble Shout her, Einmal,
weil man das quasi so durchkaut und nach und nach,
also hier dieses zuerst das höh größte Element ganz nach rechts gebabbelt wird.
Man kann es auch so verstehen, dass es wie so eine Blase aufsteigt.
Das leichtere Element, das größte Element steigt wie eine Blase auf.
Wie auch immer, also Bubble Sword ist da sehr anschaulich
von seiner Benennung. So, jetzt sind wir einmal durch.
Und jetzt ist natürlich der nächste Trick,
einen zweiten Durchlauf brauche ich ja nicht mehr durch die gesamte Liste durchzugehen,
weil ich habe ja schon die 78 ganz nach rechts gebabbelt.
Also gehe ich nur noch eine kürzere Teilliste durch,
nämlich diese vier elementige Liste. Aber jetzt geht es wieder,
weil es ein Algorithmus ist und das ist eine maschinelle Verarbeitung,
gehe ich wieder von vorne los
Gucken wir wieder die 7 und die 55 an.
Müll muss ich nicht nochmal vertauschen. Dann habe ich hier als nächstes
die 55 und die 12, ja, die muss ich vertauschen.
Dann habe ich die 55, die 42, muss die auch vertauschen.
Und dann bin ich tatsächlich wieder einmal durch.
Das heißt, der zweite Durchlauf ist hier zu Ende.
Und die 55, die zweitgrößte Zahl steht an der zweiten,
zweitletzten Stelle. Jetzt kommt der dritte Durchlauf,
ich muss wieder eine kürzere Liste mir angucken, stelle fest,
oh, 7 und 12 sind schon in Ordnung,
12 und 42 sind auch in Ordnung, muss ich nichts machen.
Also alles klar, dritter Durchlauf. Bis zu Ende.
Jetzt mache ich den, wenn es automatisiert weiterläuft,
den vierten Durchlauf, mache hier nochmal die Überprüfung, aha,
7 und 12, muss ich auch nicht mehr tauschen.
Und dann habe ich
mein Ergebnis
Das ist das Prinzip von Bubblesword. Sehr, sehr einfach.
Ich glaube, das ist für jeden nachvollziehbar, wie das funktioniert.
Hier ist eine Implementationsskizze. Ich mache also tatsächlich,
ich habe zwei geschachtelte Schleifen und dann sieht man auch schon,
wenn ich zwei so Schachtel, zwei so Schleifen ineinander geschachtelt sehe,
weiß ich schon, das geht in Richtung von quadratischer Komplexität,
weil ich tatsächlich einmal hier über diese Durchläufe,
ich mache hier eine Schleife über den Durchlauf von 1 bis N minus 1. Also wie viele Durchläufe muss ich machen?
Naja, wir haben gerade gesehen,
ich habe vier Durchläufe gemacht bei fünf Elementen.
Also habe ich hier tatsächlich N minus
1 Durchläufe
Und in jedem Durchlauf mache ich wieder eine Schleife,
indem ich wirklich die Positionen durchgehe von 1 bis,
aber dann nur n-durchlauf. Also das heißt,
je weiter ich, je weiter ich bin mit den Durchläufen,
desto weniger weit muss ich durch die Liste durchlaufen,
weil ich dann ja schon die Elemente rechts wegsortiert habe.
Und dann steht hier letztendlich diese entscheidende, der entscheidende Vergleich,
wenn Elde an der Position größer ist,
was an der Position plus 1, also diese beiden Elemente,
wenn das größer ist, dann vertausche ich die.
Ich vertausche sie nicht, wenn sie kleiner gleich sind.
Das heißt also tatsächlich nur echt größer,
da ist eine Frage
Ja. Sehr gute Frage. Er fragt,
warum muss ich den vierten Durchlauf überhaupt noch machen?
Das ist tatsächlich eine Optimierungsmöglichkeit, die man sich relativ schnell überlegen kann.
Wenn ich bei einem Durchlauf nicht ein einziges Mal getauscht habe,
dann weiß ich, dass die Liste dann bereits sortiert ist,
dann kann ich mir den nächsten Durchlauf sparen.
Aber das sind so Optimierungsmöglichkeiten, die muss man dann halt
auch entsprechend implementieren. Aber genau richtiger Hinweis,
ich bin hier einmal bei dem dritten Durchlauf durchgelaufen,
habe keine einzige Vertausschung vorgenommen.
Bei beiden Durchläufen vorher habe ich mindestens eine Vertausschung vorgenommen
2 unter 3. Aber hier keine einzige. Das heißt,
wenn ich mir das merke, dass ich keine Vertausschung vorgenommen habe
bei einem Durchlauf, dann kann ich das tatsächlich optimieren.
Das habe ich hier auch tatsächlich hier schon angedeutet. Gut,
der Algorithmus besitzt eine quadratische Komplexität und ist deswegen langsamer als viele andere Sortialgorithmen.
Das ist ein ganz einfacher Algorithmus, einfach zu implementieren,
einfach zu verstehen, wunderbar, dummerweise ziemlich lahm.
Ich kann das jetzt mal zeigen, weil ihr
in den Übungen ja mit einem wunderbaren Projekt arbeiten werdet.
Dieses Projekt sieht so aus. Es gibt ein Interface,
In-Place, sortierbare In-Liste. Das ist ein fürchterlicher Name,
aber er trifft eben genau das, was es macht.
Es gibt hier Operationen, mit denen man auf einer Liste
etwas tun kann. Man kann nämlich nur fragen,
ob einer bestimmten Position ein Element existiert.
Ob das also eine gültige Position in der Liste ist,
dann kann ich mir ein Element ein an einer bestimmten Position geben lassen.
Ich kann mir die Länge der gesamten Liste geben lassen.
Und ich kann, dass es eben halt darum geht,
in Place sortierbar heißt, man vertauscht immer zwei Elemente.
Es gibt eine Operation, die heißt Vertausche.
Also zwei Indizes können hier
vertauscht werden
Und die Vorbedingungen, dass ich hier vertausche, aufrufen kann,
ist halt, dass das sowohl I als auch K existieren,
als gültige Indizes in dieser Liste.
So, ja, und dann gibt es hier eine Implementation dieser Inplays,
sortiert man Endliste, die ist eine Visual Intlist.
Die ist so schön, weil sie sich visualisiert.
Das heißt, man kann, wir sehen,
wie diese Liste nach und nach sortiert wird.
Hier haben wir jetzt das Beispiel,
mal eine Visual Intlist der Größe 100,
100 Elemente,
wie ist das
hier visualisiert
Ihr könnt euch das so vorstellen,
dass die Liste hier auf der X-Achse abgetragen ist.
Also sozusagen die Zellen der Liste sind hier von 1 bis
100 oder von 0 bis 99 auf der X-Achse aufgetragen.
Und auf der Y-Achse ist eingetragen, welcher Wert da drin ist.
Das heißt zum Beispiel hier in der Zellenposition null,
nehmen wir mal an, ist hier ein Wert,
der irgendwie sowas bei 70 sein wird oder so.
Und hier ist es schon ziemlich hoher Wert.
Hier zum Beispiel ist die 99 oder die 100. Der ist dann
an Position, was weiß ich wie. Das heißt,
wenn das jetzt sortiert wäre, dann würde das eine aufsteigende Diagonale sein,
weil dann sozusagen immer X- und Y-Position übereinstimmen.
Ich kann das mal zeigen. Man kann tatsächlich bei dieser
Visual Indless sagen, initialisiere das doch mal aufsteigend
Und dann sieht man hier tatsächlich, wenn ein Element an
der richtigen Position liegt, dann wird es grün angezeigt.
Ansonsten sind sie weiß. So,
wenn ich jetzt diese Visual Inlist hier nochmal initialisiere,
gemischt, jetzt habe ich also irgendwie so diese gemischte Form.
Und jetzt nehme ich mal hier unsere Musterlösung,
die werdet ihr erst noch bauen müssen.
Einen Bubble-Sortierer, der soll jetzt mal diese Visual Inlist sortieren.
Da habe ich also eine Operation sortiere.
Bubble-Sortierer und Quick-Sortierer implementieren beide, das Interface Sortierer.
Auch das wieder ganz einfach. Ganz kleine Schnittstelle,
eine Operation, Sortiere und bekommt als Parameter juhu
eine In-Place sortierbare Endliste
Das heißt, wir können jetzt, also Visual Inlist,
hier reinschmeißen. Wir können also den Bubble-Sortierer sagen,
sortiere doch mal diese Liste und dann fängt er an,
die zu sortieren. Und weil es eine visuelle Liste ist,
zeigt sie dann sozusagen, während sie sortiert wird, an was passiert.
Das ist ganz schön. Also schmeiße ich jetzt mal diese
Visual Inlist da rein. Und jetzt sieht man hier so richtig,
wie der hier so gemütlich einen nach dem anderen da oben
in die rechte obere Ecke reinbubbelt. Und er sieht zwischendurch,
ist auch mal ein Element an der richtigen Stelle,
da ist hier mal was grün, aber so,
man sieht, das ist recht langsam.
Das ist natürlich künstlich ausgebremst. Wir haben hier eine Verzögerung reingebaut,
dass die Vertauschungsgeschichten immer so ein bisschen verzögert sind,
damit man es gut sehen kann.
Aber es ist fair für alle für alle Abläufe,
die ich gleich zeige. Also das ist jetzt hier eine
Liste mit 100 Elementen und ihr seht, der bubbelt.
Am Ende wird er natürlich ein bisschen schneller und dann ist er fertig.
Also Bubble Sword mal richtig schön visualisiert gesehen.
Das Schöne ist, wenn ihr denn die Bubble Sword implementiert,
ihr seht sofort, ob es geklappt hat oder nicht.
Wenn das nicht eine grüne Diagonale gibt, ist das so,
dass da vermutlich irgendwas noch falsch ist.
Gut, das ist Bubble Sword. Gibt es Fragen zu
Bubble Sword? Ich vermute nicht. Gut. Dann,
ach ne, genau, Vorteil, geringer Speicherbedarf,
es ist ein Implace-Verfahren, potenziell stabil.
Wir vertauschen nur, wenn was so wirklich größer ist.
Das heißt also kleiner gleich
Strenge schwache Ordnung, dann ist es tatsächlich so,
dass sie nicht vertauscht werden. Das heißt,
das Verfahren kann stabil sein, wenn man es richtig macht.
Und es ist für kleine Elementmengen gut geeignet.
So, zweites Beispiel, Quicksart, ein wunderbarer Algorithmus.
Von dem guten Herrn Horr, den kennen den Namen,
haben wir ja schon gehört, schon 1962 entwickelt.
Also das ist eine Zeit, wo eigentlich noch nicht
so richtig über Personal, Computer und ähnliches nachzudenken war.
Das ist ein rekursiver Sortialgorithmus. Also gut,
dass wir unsere Rekursion vorher mal angeguckt haben,
dass ihr jetzt wisst, was Rekursion ist.
Und wir werden jetzt sehen, dass Quicksart auch ein nicht
stabiler Sortialgrund ist. Und wir werden vor allen Dingen sehen,
dass er sehr schnell ist
Das Prinzip ist eigentlich relativ einfach, das ist einer der klassischen Teile- und Herrscher-Algorithmen.
Teile und Herrscher heißt, ich zerteile das Problem in Teilprobleme
und kann dann an den Teilproblemen das Gleiche wieder anwenden,
was ich schon für das Gesamtproblem angewendet habe.
Und zwar ist es folgendermaßen. Ich nehme einen beliebigen Schlüssel
. Also ich habe meine Menge von zu sortierenden Elemente,
nehme einen Schlüssel eines beliebigen Elementes und sage,
du bist jetzt das Pivo-Element.
Dein Sortierschlüssel ist das Pivo-Element. Und dann würde anschließend die gesamte Reihe,
die sortiert werden muss,
in zwei Reihen aufgespittet und zwar in eine Reihe,
in der sind die Elemente mit Schlüsseln größer als extrem
und eine mit Schlüsseln kleiner als
Elemente mit Schluss in Gleichex-Können beliebig auf die beiden verteilt werden.
Das kann man sich beliebig überlegen. Kann man mit Algorithmus
sich dann in der Implementation dann entscheiden. Aber was gilt,
ist, dass beide resultieren in Teilreihen,
um mindestens ein Element kleiner sein müssen als Ziel zu sortierende Reihe.
So, und wenn ich das gemacht habe,
ich habe also sozusagen ein Pivo-Element genommen,
habe dann eine Liste gebaut mit den ganzen Elementen, die größer sind.
Eine, die mit denen, die kleiner sind,
dann kann ich auf diese Listen wieder das Gleiche anwenden.
Das heißt, ich nehme an der kürzere Liste irgendein Pivo-Element,
spalte es auf in zwei Listen und das
mache ich rekursiv immer weiter auf immer kürzeren Listen
Und am Ende muss ich da an diese Listen noch zusammenschmeißen.
Hier ist das mal als funktionale Definition angegeben, also Quick-Sound.
Wenn die Liste, die länger eins hat,
dann bin ich fertig, dann kann ich direkt zurückliefern.
Das ist die klassische rekursive Definition, der Rekursionsabbruch hier.
Eine einelementige Liste, da muss ich nicht mehr viel sortieren.
Ansonsten, die weit, also ich wähle irgendeinen Schlüsselwert aus als Pivo-Element.
Dann berechne ich eine nicht dere Teilfolge S1 und eine typer GS2 und dann in die,
bei den einen gilt halt Schlüsselwert kleiner gleiche X,
bei einem anderen Element Elemente größer gleich X.
Das heißt, ich habe dann hier Eine Element,
eine solche Teile ist der S1, eine Teile ist der S2.
Und nee, die bekomme ich jetzt, indem ich darauf wieder rekursiv Quicksort aufrufe.
Also tatsächlich hier zweimal Quicksort. Ja, und dann am
Ende würden hier dann diese Listen, die sich ergeben,
wieder konkatiniert. Das ist hier funktional genannt von uns,
weil es als eine mathematische Funktion angesehen wird,
in die etwas reingestopft wird.
Und es kommen dann immer wieder neue Listen dabei raus.
Ja, und diese Listen werden dann entsprechend verkettet.
Veranschaulichung können wir uns das mal so hier,
man hat hier so eine Liste,
ich wähle irgendein Element als Schlüsselement,
also zum Beispiel mal das erste hier,
die neuen
Ein Pivot-Element, dann spalte ich das auf. Also 6,
3 ist kleiner, 8, 5, 1,
die kommen alle nach links und die,
die größer sind, kommen nach rechts.
Habe ich so aufgespaltet in zwei Reihen. Das mache ich rekrusiv
so weiter. Also hier habe ich wieder das Pivot-Element 6,
also kommen hier die Elemente, die kleiner sind als sechs.
Hier, die, die größer sind, hier genauso.
Ich nehme das Pivot-Element, das, was größer ist,
kommt nach rechts. Kleiner habe ich nichts. Und dann
habe ich am Ende sowas hier und da muss ich jetzt diesen Baum,
den ich hier sozusagen hier mental im Kopf aufgebaut habe,
nur noch per tiefen Durchlauf durchlaufen und dann diese ganzen Dinger hier verketten.
Das ist Nur die Veranschaulichung, damit ihr das Prinzip versteht,
wie Quickshot funktioniert. Was ihr machen werdet in den Übungen auch,
ist eine imperative Konkretisierung, bei der ihr das Ganze inplace macht.
Also hier ist es nicht so, dass Quickshot eine Funktion ist,
die ihr dann wieder eine neue Liste liefert, eine neue Sequenz,
sondern ihr arbeitet vor Ort inplace auf den Zellen einer gegebenen Sonne.
Ich habe also eine Sammlung, die einen Zustand hat,
es kann eine Raisern oder eine Liste und ihr vertauscht dann diese Elemente der Liste.
Und das ist hier der Algorithmus, den ihr auch versuchen solltet zu implementieren.
Und das ist nicht so einfach. Das ist etwas,
was wir auch mit euch hier üben wollen in SE1.
Ihr bekommt einen Algorithmus und müsst den in Quelltext umsetzen
Und das ist etwas anders als das, was ich gerade beschrieben habe.
Auch hier ist es wieder so, ich wähle ein Element
als Pivo-Element aus. Und jetzt habe ich diese Idee von,
ich sortiere die Liste, habe also zwei Indizes, I und J.
Hier ist das erste Element. J hat das letzte Element
. Und solange jetzt die noch nicht aneinander vorbeigelaufen sind,
also solange I kleiner ist als J,
solange die nicht aneinander vorbeigelaufen sind, erhöhe ich jetzt diesen Index hier,
bis ich etwas finde, was größer ist als das Pivo-Element.
Und von der anderen Seite komme ich und suche ein Element,
das kleiner ist als das Pivo-Element.
Wenn ich die beiden habe, wenn ich sozusagen diese Stellen habe,
dann vertausche ich diese beiden Elemente und setze die beiden Indizes
wieder einen weiter
Das immer weiter, bis die aneinander vorbeigelaufen sind und dann kann ich mit Sicherheit sagen,
hier sind die ganzen, die größer sind.
Hier sind die ganzen, die kleiner sind.
Und dann kann ich das, wie gesagt, rekrusiv machen.
Hier ist das mal wieder angedeutet, jetzt nochmal in dieser,
für diese In-Place-Sortierung hier diese Liste von Zahlen.
Ich wähle wieder das PV-Element 9. Ja, also das,
dann setze ich meine Indizis,
also I und J stehen jetzt hier jeweils außen.
Und jetzt werden diese Indizis nicht verschoben,
weil der Index I zeigt schon auf ein Element,
das größer gleich der 9 ist
Und hier haben wir etwas, was kleiner gleich der Neuen ist.
Dann werden diese Elemente tatsächlich erst mal vertauscht,
weil ich noch nicht aneinander vorbeigelaufen, weil die noch nicht
aneinander vorbeigelaufen sind, vertausche ich jetzt diese Elemente.
Also habe ich jetzt hier die drei und da die Neuen.
Und ich setze auf jeden Fall unbedingt die beiden Initias ein weiter.
Jetzt geht es wieder los. Ich laufe von links so weiter,
bis ich etwas finde, was größer ist als die Neuen,
also bis zur zwölf.
Nur I wird verschoben, weil J steht schon auf etwas,
was kleiner ist als die Neuen.
Also I und J stehen jetzt an diesen beiden Positionen.
Jetzt vertausche ich wieder zwölf und sechs und setze wieder
meine Indizes unbedingt ein weiter
Und jetzt geht das so weiter, bis sie tatsächlich aneinander vorbeigelaufen sind.
Und jetzt gilt tatsächlich, hier sind alle Elemente,
die kleiner sind als das Peer-Volement.
Und hier sind die, die größer gleich sind.
So, und dann habe ich hier tatsächlich jetzt zwei Teillisten.
Und da kann ich jetzt wieder rekursiv auf diesen Teillisten das Quicksalt aufrufen.
Das ist wunderbar. Ich will mal zeigen, dass das deutlich schneller ist.
Wir nehmen nämlich jetzt hier mal die Visual Intlist,
die können wir noch mal nach vorne holen.
Moment, wo ist sie denn? Hier, Visual Intlist
. Ich initialisiere die mal gemischt. Mach mir hier mal
den Quicksortierer, also meine Mosterlösung.
Und sage,
sortiere das mal
Und das ist jetzt die gleiche große Liste. Das heißt,
die Verzögerung ist genau die gleiche. Und ihr seht,
das ist deutlich schneller. Was ihr auch gesehen habt,
ist, dass der sich so rekursiv so richtig schön in
diese Liste reinfaltet. Ich kann das nochmal zeigen,
indem ich hier eine größere Liste mache,
einfach damit es ein bisschen spannender ist, damit man auch sieht,
dass er tatsächlich dann immer noch schneller ist.
Hier seht ihr jetzt 200 Elemente. Wenn ich jetzt den Quick-Sortierer,
dem Twick-Sortierer sage, sortier doch mal diese große Liste,
dann kann man es hier nochmal genau sehen.
Ihr seht also, das ist ist ziemlich eindrucksvoll,
wie der sich so rekursiv da so langsam auf irgendeiner kleineren Liste so reinfaltet.
Was man auch sieht, der ist unter anderem so schnell,
weil der am Anfang über relativ große Entfernungen, Elemente vertauscht.
Der Bubble-Soul ist immer nur so, zwischen zwei kleinen Elementschellen
und der Quickford kann das deutlich besser so verteilen.
So, das ist ganz prima. Das ist der Quicksort
. Da gibt es ein paar Bemerkungen dazu.
Erstens die Wahl des Pivo-Elements. Die hat ziemlichen Einfluss auf
die Effizienz. Was man sich klar machen kann,
ist diese Idee, dass der nicht ein Quadrat braucht,
sondern nur ein Login ist Enmal Login, weil Basiert darauf,
dass ja im Idealfall die Listen immer halbiert.
Also wenn er bei jedem Schritt die Listen halbiert,
dann kommt da dieses logarithmische Element mit rein.
Und dann ist es natürlich am besten, wenn das Pivo-Element
der Median ist, also das mittlere Element in der Sammlung.
Nicht das räumlich mittlere Element, sondern das mittlere Element vom Sortierkriterium her,
dass ich sozusagen genau die Hälfte der Elemente habe,
die kleiner sind und die andere Hälfte, die größer sind.
So, wenn ich bei einer bereits sortierten Reihe immer
das linke oder immer das rechte Element als Pivo-Element wähle,
dann kann ich den Quicksort hier degenerieren lassen
Das heißt also, im durchschnittlichen Fall,
und das ist meistens der Fall, hat der O von Endlock N,
aber im schlechtesten Fall degeneriert er zu O von Endquadrat.
Was ich, ich kann das mal versuchen zu zeigen,
ich habe hier den Quick-Sortierer,
und hier benutze ich tatsächlich das Pivo-Element, Pivo-Element ist hier,
Liste gib mir das im Moment aus der Mitte.
Ich berechne also die räumliche Mitte in dieser Liste
und lass mir das, dass das als Pivo-Element gelten.
Wenn ich das jetzt einfach mal auskommentiere oder einfach anders implementiere,
ich nehme mir tatsächlich hier einfach Statt gib Mitte,
sage ich einfach, das Startelement.
Also ich nehme immer das erste Element als Pivo-Element.
Das lässt sich übersetzen, dann ist dieses,
dann habe ich hier diese Visual End List.
Ich nehme einfach mal die 100er Liste, das ist jetzt auch egal.
So, hier habe ich jetzt meine Invisual End List.
Und jetzt nehme ich diesen Quicksortiere, der jetzt hier jetzt
auch genauso wieder schnell das Ding sortieren wird.
Also wenn ich das jetzt sortieren lasse, geht das schnell.
Weil es halt einfach eine unsortierte Liste ist.
Wenn ich jetzt aber, und das ist das Entscheidende,
hier sage, initialisiere das mal, absteigend.
So, dann ist das Ding quasi vorsortiert. Und jetzt
wird aber als Pivo-Element immer das erste Element genommen. Nicht,
dass irgendwie räumlich mittlere, was hier ideal wäre,
sondern es wird das Element genommen, was sozusagen dann das kleinste Element ist.
Oder in diesem Fall das größte Element. Das heißt,
man hat in jedem Schritt, in jedem Rekrusionsschritt wird die
eine Liste nur um eins verkürzt.
Und dann degeneriert der Quicksart jetzt zu einem Bubble-Sort.
Also nicht zu einem Bubble-Sort, aber zu einem O von N-Quadrat.
Jetzt sieht man hier, wie das hier so richtig schön langsam runtergeht.
Da sieht man, der Quicksart kann auch langsam sein,
wenn er einfach ungünstig das Pivo-Element wählt
Wenn wir, also das jetzt hier nochmal korrigieren und
ich nehme jetzt also hier wieder die räumliche Mitte,
da ist das Pivo-Element wieder die räumliche Mitte.
Ich zeige mir nochmal diese Liste hier.
Wunderbar und mache jetzt auch wieder diesen einen Schritt erstmal,
ich mache das mal absteigend. Und jetzt nehme ich den Quick-Sortiere,
der das räumlich mittlere Element nimmt mit dem Pivo-Element und lasse denen das sortieren,
dann nimmt der jetzt immer das räumliche mittlere Element,
was zufällig, weil das Ding sortiert ist,
auch der Median ist und dann ist es knallerschnell,
dann besser geht es nicht,
dann ist es zack.
Also richtig deutlich ein Unterschied
Man kann das sehr schön sehen hier in der Visualisierung,
durch deswegen ist das ganz nett.
Wir sind jetzt auch in den Übungen dann mal.
Ihr werdet auch sehen, wenn ihr bei der Implementation Fehler macht,
dann bleiben da manchmal so einzelne Elemente stehen.
Das ist gut. Quicksalt. Sehr schnell, sehr schön.
Jetzt nochmal ein Ding zur Veranschaulichung. Lohnt sich die Mühe
überhaupt? Sowas wie Quicksalt zu bauen. Ihr werdet sehen,
wen der Quicksalt implementiert, das ist nicht einfach zu implementieren.
Ein Bubbleshot ist viel einfacher zu implementieren. Da kann man
sich jetzt fragen, warum mache ich denn überhaupt diesen Quatsch,
dass ich mir da jetzt so eine komplizierte Struktur baue?
Klar, es ist schneller, habt ihr gesehen,
naja, aber wie viel schneller ist das wirklich?
Macht das so viel aus
Warum man nicht einfach immer mit Bubble Sword sortieren.
Und ich habe das tatsächlich auch in vielen Kontexten erlebt,
bei Versicherungen, bei Banken.
Ganz häufig ist da einfach nur als Sortieralgorithmus ein Bubble Sword implementiert.
Und der ist langsam. So, wir vergleiten das mal
. Deswegen möchte ich hier mal so eine Veranschaulichung machen.
Wir haben also jetzt tatsächlich hier, wo von einem Quadrat,
also Bubble Sword, verglichen mit O von N-Login.
Nehmen wir mal an, wir haben jetzt so Klimaberechnungen zu machen.
Also richtig große Datenmengen. Geophysiker will irgendwie 10 Millionen Zahlen
Werte sortieren. Und das macht er auf einem Rechner,
der 10 hoch acht elementare Schritte pro Sekunde ausführen kann.
Das ist schon ein ziemlicher Bolide,
10 hoch acht elementare Schritte ist schon
relativ viel
Dann hat er in der Sortieralgorithmus implementiert, sagen wir mal so
ein Bubble Sword und startet den jetzt und geht einen Kaffee trinken.
Denkt sich halt so, ja, mal eben sehen,
das wird wahrscheinlich eine Weile dauern,
bis wir jetzt meine 10 Millionen Werte sortiert hat.
Aber der kommt wieder und der Frechner ist nicht fertig
und wird auch so schnell nicht fertig werden.
Das kann man sich relativ schnell ausrechnen. 10 Millionen zum
Quadrat, das ist 10 Uhr 7 mal 10 hoch 7.
Und auf einem Rechner, der 10 hoch 8 Operationen ausführt,
also kann man die 10 hoch 14 durch 10 Uhr 8 teilen.
Das sind dann 10 hoch sechs Sekunden. Das ist natürlich
nur größenordnungsmäßig gerechnet. Das sind ungefähr 11 Tage.
11 Tage mit dem Bubble Sword
Aber was meint ihr, wie lange dauert das beim Quickshaut?
Gut, die, die es im Skript stehen haben, die wissen es schon.
Das geht deutlich schneller. Das geht in 2,4 Sekunden.
Ja, und ich finde, das sind so Zahlen,
wo man einfach den Unterschied richtig spürbar hat.
Also ob ich jetzt elf Tage vor dem Rechner sitze oder
zweieinhalb Sekunden, ist für mich persönlich ein Unterschied.
Und das ist ganz einfach hier, da kommt dieses Logarithmische mit rein.
Logarithmus-Duales von 10.07 Uhr, das ist irgendwie 24.
Das ist also 10 Uhr 7 mal 24. Das ist also
irgendwie 2,4 mal 10.08 Uhr durch 10.08 Uhr ist dann 2,4.
Das ist einfach
ein Riesenunterschied
Okay, also effiziente Sortierverfahren sollte man kennen und man sollte wissen,
was der Unterschied ist zwischen O von End-Quadrat und O von End-Login.
Lohnt sich gerade bei großen Zahlen sehr. Sortieren mit linearer
Ordnung ist diese Idee, dass man tatsächlich die Schlüssel der
zu sortierenden Elemente in einen Array direkt rein sortieren kann.
Es muss also nicht in SITU sortiert werden.
Ich kann sozusagen die Elemente in einen anderen Behälter reinwerfen.
Das ist so ähnlich wie beim Hashing. Dann muss man
am Ende sozusagen nur noch einmal durch diese Liste durchgehen und dann diese Elemente aufsammeln.
Da kann man tatsächlich dann linear in N sortieren.
Aber das ist eher bei Ausnahmefällen auch
wirklich machbar
Meistens geht das nicht. Hier sind noch mal so ein paar
andere Sortierverfahren mit ihren Komplexitäten. Das ist nur so zur Übersicht.
Wir machen jetzt noch mal ein paar Verständnisfragen mit dem Storesi.
Wir wollen ja nicht, dass Storesi hier zu kurz kommt.
Eine kleine Abstimmung. So. Welche der folgenden Aussagen ist richtig
? Ein Stack ist, nach dem FIFO-Prinzip organisiert,
die Operation top entfernt üblicherweise, das zuletzt eingefügte Element eines Stacks.
Der Operation Pushlick üblicherweise ein weiteres Element auf ein Stack oder die Semantik eines Decks
wird ausschließlich über die Signatur und seine Operation in einem Interface definiert.
A, B, C, D. Könnt ihr euch jetzt entscheiden.
Es sind schon einige Stimmen eingegangen. Ma,
kommen wir wieder auf 100 Stimmen. Schauen wir mal,
schauen wir mal
80, 80, 90, oh, oh, oh.
Also 100 Leute sind ja mindestens hier.
Ach, komm, komm, komm. Na gut,
ich breche das dann jetzt ab. Dann scheiße euch schnell.
Da kommt nichts mehr gut. Dann gucken wir mal,
was erst mal die Abstimmung so geliefert hat.
Die Operation Push legt üblicherweise ein weiteres Semit auf einen Stack.
Da waren 81 Prozent dafür und das ist die richtige Antwort.
Toll. Das ist auch wirklich ganz einfach gewesen.
Gar keine Frage. Die nächste Frage ist vielleicht nicht mehr
ganz so einfach zu beantworten. Also, welche der
vorherigen Aussagen ist richtig? Ich starte das schon mal.
A, Quicksort ist ein stabiles Sortierverfahren. B,
es gibt Sortierverfahren, die schneller sind als O von N-Log-N. C,
ein Sortierverfahren heißt stabil, wenn es auch eine Elementmenge sortiert,
in der einige Elemente den gleichen Sortierschlüssel haben.
D, Quicksort kann auch bei idealer Wahl des Pivolements zu
O von Endquadraten
Ja, ja. Ja, 50 Stimmen, ja,
60 Stimmen gleich.
Komm, gib mir
eure Stimme
Ja. Okay. Ja, ich glaube,
jetzt so langsam bei 90 sollten wir dann mal abbrechen.
Gut. Auch da vermute ich jetzt mal,
dass ihr euch nicht habt hinters Licht führen lassen.
Oh. Oha. Also einige waren der Meinung, quickst du
das ein stabiles Sortierverfahren? Dann sagen nur 22 Prozent,
es gibt Sortierverband, die schneller sind als O-Phone N-Log-N.
Dann die Sache, das ist stabiler. Die meisten waren dafür,
dass ein Sortierverfahren stabil heißt,
wenn es auch eine Elementmenge sortiert,
in der einige Elemente den gleichen Wert haben
Und die richtige Antwort ist das hier. Das hätte ich
jetzt nicht gedacht. Toll, toll, toll, toll
. Also Quicksort ist nicht stabil. Da stand auch auf
der Folie drauf. Es gibt dort Tierverfahren,
die schneller sind als O von N-Log-N. Ja,
das sind nämlich genau die, die so mit Hash-Verfahren arbeiten.
Also so Bucketsort und ähnliche. Ein Sortierverfahren heißt nicht stabil,
wenn es die Dinger aussortiert, sondern das heißt stabil,
wenn es die tatsächlich gleiche Elemente in der Reihenfolge lässt.
Und Quicksort, wenn man das Pivo-Element ideal wird,
dann ist es schon sehr unwahrscheinlich, dass Quicksort degenerieren kann.
Ich glaube, der einzige Fall,
den man sich vorstellen könnte,
ist,
dass alle Elemente gleich sind
So, also in dem Falle könnte man sagen,
naja, das könnte man auch als richtig ansehen,
aber wenn man das Pipo-Element ideal wählt,
dann geht der eher deutlich Richtung O von N-Log an und nicht von N-Quadrat.
So, vielen Dank für diese Vorlesung.
Ihr könnt jetzt noch einmal hier eure Note abgeben für die
heutige Vorlesung und dann wünsche ich euch einen schönen Resttag