Ja, herzlich willkommen zur 13. Vorlesung von SE1. Fast haben wir es geschafft.
Wir haben noch heute und nächste Woche als Vorlesungstermine.
Ihr habt noch ein Aufgabenblatt, das kommt heute raus
oder beziehungsweise ab morgen wird es bearbeitet in den Übungen.
Ihr müsstet es eigentlich schon gestern im Komsi gehabt haben.
Für das Aufgabenblatt ist gerade schon gefragt worden,
ist nicht irgendwie längere Bearbeitungszeit möglich. Das heißt,
ihr müsst die 13 Wetter bis zum nächsten Donnerstag bearbeitet haben.
Also da müsst ihr eventuell nochmal ein bisschen Antritt zeigen,
wenn ihr ein bisschen im Hintertreffen seid
Heute werde ich nochmal das Thema Sammlung ansprechen,
indem ich zwei spezielle Sammlungstypen vorstelle und nächste Woche,
na und heute geht es auch noch um Sortieren,
auch etwas für Sammlungen,
also wenn ich viele Elemente habe und die in eine geordnete Reihenfolge bringen will,
dann gibt es da verschiedene clevere Sortieralgorithmen,
da mache ich einen kleinen Einstieg heute.
Und nächste Woche geht es dann eine halbe Vorlesung um Grafen,
weil da gibt es auch sehr schöne Algorithmen auf solchen Grafen,
da kann man auch so ein bisschen nochmal was üben und sehen,
was dann eher so eine für die Feinschmeckervorlesung und vor allen Dingen
nächste Woche wird es dann Feedback geben zu eurer leere Evolution.
Das heißt, wir werden es ein bisschen zurückblicken,
wie war dieses SE1, was ist gut gelaufen,
was ist schlecht gelaufen und werden das dann entsprechend ein bisschen hier reflektieren.
Üblicherweise habe ich in der letzten Vorlesung schon die Ergebnisse der Evaluation,
die ihr jetzt in den Übungen gemacht habt. Gut,
jetzt heute vor der Pause und vor der Pause auch noch ein bisschen sortieren,
aber vor der Pause auf jeden Fall zwei Kandidaten,
die in der Informatik ziemlich weit verbreitet sind.
Zwei Sammlungstypen,
nämlich Stack und
Und wir steigen einfach mal direkt ein. Das hier ist,
ja, wie soll man sagen, eine der ältesten Folien in SE1,
die ist irgendwie noch von der Vorvorlesungsklasse, die für diese Veranstaltung existierte.
Ich fand die trotzdem irgendwie so schrill, dass ich sie behalten wollte.
Das ist, glaube ich, die bunteste Folie,
die wir haben. Es geht also um den Stack,
um den Stapel. Ein Beispiel hier in der Mensa,
ein Stapel von Tablets, üblicherweise ist es da so,
dass da frische Tablets draufgelegt werden auf so einen Stapel.
Und wenn ihr euch ein Tablett nehmt,
nehmt ihr euch üblicherweise das oberste Element vom Stapel runter.
Das ist also eine sehr schöne Veranschaulichung des Konzeptes, Stapel oder Stack im Englischen.
Hier ist ein anderes Beispiel, rechts zu sehen,
dieses eigenartige Küchengerät, in dem man Äpfel auch bewahren kann,
die man nicht irgendwie quer rausnehmen kann,
sondern nur immer den obersten Apfel.
Das finde ich auch ganz anschaulich, um deutlich zu machen.
Es geht immer um das Element, was gerade anschaulich obendrauf liegt.
Das müsst ihr bei Stacks sowieso euch irgendwie als Veranschaulichung merken.
Die sind immer vertikal. Ich weiß nicht warum,
aber das hat wahrscheinlich diesen Effekt von, da kann keiner
irgendwie unten was reinschieben, sondern man liegt immer was drauf.
Während wenn ich was Horizontales mache wie bei einer Liste,
dann kann ich irgendwie an allen Stellen irgendwie zugreifen.
Also, der Keller oder Stack oder Stapel als Folge von Elementen,
das heißt, die sind linear geordnet.
Ist dann eine Datenstruktur über einem Datentyp T,
ist wieder der Elementtyp in dem Fall jetzt, also hier Äpfel oder Tabletts.
Und dann gibt es zwei klassische Zugriffsoperationen, einmal Push,
die legt ein Element drauf auf den Stapel und die Operation Pop nimmt ein Element runter.
Und das Prinzip ist eben halt beim Stapel ausgewiesen immer so,
Das ist was zuletzt draufgelegt wurde, als erstes wieder runterkommt,
also last in first out.
Das ist das sogenannte Liefo-Prinzip. Also wenn ich das mal
irgendwie über den Weg läuft, keine Hexenkunst,
ganz einfach, Liefo last in first out.
Warum erzählen wir das? Naja, weil Stacks relativ viel
vorkommen in der Informatik. Ihr habt den Stack schon gesehen,
zum Beispiel, als ihr den Debugger benutzt habt,
gerade bei der Rekursion war das dann so,
da hattet ihr dann mehrere Stackframes, die übereinander lagen.
Das war so richtig visualisiert, dass die da so übereinander
und die Berge auch dargestellt wurden
Und so ein Stapel ist tatsächlich auch sehr zentral in der Auswertung von Java-Programmen.
Also wenn eine Virtual Machine Java-Programme ausführt, dann hat die intern auch einen Stapel.
Und das nicht nur für Methodenaufrufe, sondern auch für die Auswertung von Ausdrücken.
Das können wir uns gleich mal ein bisschen genauer angucken.
Erstmal gucken wir uns nochmal so ein Stack-Interface an.
Also zuerst, und das ist was,
was ich jetzt auch grundsätzlich wieder machen werde, was ihr euch,
glaube ich, generell merken müsst in der Objektorientierung.
Es gibt immer zwei Sichten auf einen Typ oder eine Klasse oder
ein Interface. Es gibt die Sicht, ist Klienten.
Was kann man denn damit machen mit dem Datentyp?
Was kann ein Klient damit tun? Und wie ist das
Ding implementiert? Und da haben wir ja inzwischen gelernt,
dass man das durchaus unterscheiden kann, dass man sogar dieselbe Dienstleistung unterschiedlich implementieren kann.
Es kommt das Gleiche dabei raus, aber es ist eventuell unterschiedlich schnell oder ähnliches.
Also nehmen wir mal an, wir haben sowas wie ein Interface-Stack.
Das ist hier generisch im Sinne von der Elementtyp,
der auf diesem Stapel jetzt drauf liegt, ist hier nicht angegeben, ein Platzhalter.
Dann gibt es die Operation Push, die liegt ein Element drauf,
dann gibt es die Operation Pop, die liefert ein Element,
also nimmt ein Element runter und liefert es auch gleich zurück.
Und man kann fragen, ob ein Stack leer ist.
Ist durchaus sinnvoll, weil man möchte vielleicht, bevor man Pop aufruft,
sicherstellen, dass da wirklich was drauf ist. Wenn man jetzt so ein Stack von String haben,
also jetzt mal ein konkreter Variable, konkretes Exemplar hier,
ein Stack von String, über eine Variable S,
zugreifbar und da sind schon zwei Elemente in diesen Stapel eingeführt oder auf diesen Stapel draufgelegt,
dann kann ich jetzt auf diesem Stack einen Push aufrufen und ein weitere Zeichenkette einfügen,
kann man sich so veranschaulichen,
dass dieses X dann da oben draufgelegt wird, sehr einfach,
sehr nachvollziehbar,
wenn ich jetzt nochmal ein Z drauflege,
dann wird das das Z da obendrauf gelegt.
Und wenn ich jetzt sage,
gib mir doch mal das oberste Element, s.pop,
dann wird das Element tatsächlich runtergenommen,
der Stapel sieht hinterher so aus und in dieser Variablen
Tee ist dann eine Referenz auf das zuletzt draufgelegte Element.
Das ist also soweit ganz einfach. Da muss man
ein bisschen aufpassen hier. Das ist dann ein Gleichgleich.
Eigentlich wissen wir ja, Strinks vergleichen wir nicht mit gleichgleich.
Also nur so, das ist jetzt hinten zur Veranschaulichung.
Also es soll heißen,
hinterher verweist Tee auf diese Zeichenkette
Was wir hier sehen, ist eine Interface und das spezifiziert die Signaturen der Stack-Operationen.
Also hier Push, Pop und so weiter. Aber
dass da jetzt diese Logik dahinter liegt, also das,
was wir hier jetzt gerade uns hier so vorgestellt haben,
dass das so ein Stapel ist und dass da Dinge draufgelegt werden und wieder runtergenommen werden,
das ist hier in keinster Weise beschrieben.
Das ist also erstmal nur ein Interface und darauf
komme ich gleich nochmal zurück, was das bedeutet.
Es gibt tatsächlich eine Klasse Stack im Java Collections Framework,
die so ähnlich aussieht wie das, was ich gerade beschrieben habe.
Also auch da kann ich fragen, ist das Ding leer
Dann gibt es statt einem Pop, was wirklich,
also hier ist das Pop entfernt wirklich das Element und liefert es auch.
Also Push und Pop gibt es auch,
aber es gibt auch noch ein Peak, wo ich sagen kann,
was ist denn das oberste Element, ohne dass sie es runternehmen?
Das ist oft mal ganz nützlich,
dass man gucken kann, was ist denn da obendrauf,
ohne sich das mit einem Pop machen muss,
weil dann ist das Element ja sofort runtergenommen.
Gibt es hier noch so ein Search,
man kann da irgendwie dann auf die Elemente durchsuchen.
Das ist ein bisschen eine komische Operation.
Das Ding liegt tatsächlich im Collections Framework, ist aber uralt,
also wenn ihr euch das Ding anguckt,
dann ist das wirklich
ziemlich gruselig
Im Collections Framework. Ich habe hier momentan eine ziemlich lahme Verbindung.
Also hier seht ihr die Klasse Stack. Und dann steht
hier irgendwie, wovon die Upline All implemented Interfaces.
Also es wird irgendwie auch das Listen-Interface implementieren.
Also der Stack implementiert das Listen-Interface. Warum weiß der Geier
? Dann steht hier Extense Vektor, auch was,
was wir noch gar nicht verstehen müsst, aber das ist wirklich altes Zeug.
Der Vektor ist eigentlich ein, würde ich sagen, ein Deprecated-Typ,
also ein Typ, den man nicht mehr benutzt heutzutage.
Man sieht das hier Sins JDK 1.0,
also wirklich in der allerersten Java-Version war das Zeug
schon drin
Genau. When is Deck is created. First created it
contains no items. Hier steht ihr Lifo, ja,
steht da auch tatsächlich in der Doku drin.
Fine, fine. Wofür braucht man so ein Ding jetzt?
Naja, also man kann damit eine ganze Menge machen.
Wie gesagt, so Ausdrucksauswertung ist ein Beispiel.
Das gucken wir uns mal an. Ich habe zufällig ein
Blue-Jay-Projekt dabei. Ich glaube, das ist mehr oder weniger
die Musterlösung von dem, was ihr in den Übungen machen sollt.
Und zwar ist das ein sogenannter Postfix-Rechner. Ich weiß nicht,
ob ihr wisst, was Postfix Notation ist,
habt ihr wahrscheinlich in der Mathematik schon gehört.
Wenn ich jetzt sowas hinschreibe wie 3 plus 4 mal 5 plus 6.
Dann ist das ein Ausdruck und ihr wisst ziemlich schnell,
was da rauskommt. Da kommt irgendwie 4 mal 5 ist
20 plus 6 plus 3 sind irgendwie 29.
Wenn ich jetzt möchte, dass zuerst die Addition ausgeführt wird,
dann muss ich hier in diesem Ausdruck klammern.
Dann kommt da nicht 29 raus, sondern was anderes.
3 plus 4, 7 mal 5 plus 6 ist 11,
also 7 mal 11, also kommt da 77 raus.
Das Schöne an der Postfix-Notation. Das ist eine In-fix-Notation.
Das heißt, der Operator steht zwischen seinen Operanten.
Ich habe immer zwei Operanten beim Plus und beim Mal und so weiter.
Und der Operator steht dazwischen. Und das Coole ist,
jetzt an der In-fix-Notation, an der Postfix-Notation,
ich kann jetzt das Gleiche, diesen gleichen Ausdruck in Postfix-Situationen hinschreiben,
also drei, vier plus, fünf, sechs,
ups, fünf, sechs, plus mal.
Das ist die sogenannte Postfix-Notation. Es werden zuerst die beiden Operanten genannt,
dann der Operator, der auf diese beiden Operanten angewendet wird.
Und bei 5, 6 das Gleiche. Und am Ende
hat man dann ja aber hier das Ergebnis von 3 plus 4 und 5
plus 6 und darauf wird dann nochmal die Multiplikation angewendet.
Das hat also gewisse Vorteile, ich brauche nicht Klammern.
Die Klammerung ist quasi implizit. Das ist einer der Vorteile von dieser Postfix-Nutation
, heißt auch, ich glaube, umgekehrt polnische Notation.
UPN, also wenn ihr so Taschenrechner noch kennt,
das sind diese Geräte, die man früher hatte,
wo man rechnen durfte in der Schule.
Da gab es dann auch so einen von HP,
der hatte dann auch so einen Er hat umgekehrt polnische Notationen
verwendet. Cooles Zeug. Warum? Na, weil das
sich mit so einem Stack sehr gut auswerten lässt.
Ich kann tatsächlich hier diese Operanten auf einen Stapel draufschmeißen und
dann wende ich einfach den Operator auf die beiden obersten Elemente,
auf dem Stapel an.
Das heißt, ich habe hier tatsächlich mal so einen Postfix-Rechner implementiert.
Ich kann also hier mal Werte ausaufrufen, wertet einen einfachen
Ausdruck in Postfix-Nutationen aus. Beispielsweise liefert Werte aus 1,
2 plus 3 mal, die zahlen 9,
ein plus 2 mal 3. Zugelassen sind nur die vier Grundrechenarten
als Operatoren und inliteraler Art Operanten
Ich kann also jetzt genau diesen Ausdruck, den ich hier stehen hatte,
diesen hier, den kann ich jetzt einfach mal hernehmen.
Und einfach mal hier die statische Methode aufrufen. Setz das
hier mal ein, muss das natürlich als Zeichenkette übergeben.
Und da sollte jetzt auch 77 rauskommen. In Postfix-Notation.
Und siehe da, da kommt tatsächlich 77 raus.
Ja, warum sage ich das? Warum zeige ich das?
Das ist erstmal jetzt nur, dass dieses Ding das kann.
Aber das Spannende ist natürlich in der Implementation von diesem Postfix-Rechner
Sieht man, wie das mit einem Stapel sehr einfach implementiert werden kann.
Ich kann also den Quelltext mal zeigen, da wird tatsächlich
intern ein Stapel verwendet. Java You Till Stack von Intager
. Also ich habe ja hier ganze Zahlen als Operanten.
Und da würde ich hier einfach ein Java You Till Stack erzeugt.
Also genau der, den wir gerade in der Doku gesehen haben.
Und der String, der hier übergeben wurde, also diese Zeichenkette,
in der so die Operanten und Operatoren drin steckten, das wird hier aufgesplittet.
String Operation Split an beliebig vielen Zeichenketten. Also da können
beliebig viele Leerzeichen dazwischen stehen zwischen den Elementen,
die da miteinander verknüpft werden sollen.
Da hat man hier so eine zum String-Array von Bestandteilen.
Also zum Beispiel die 5 und das Plus und so weiter.
Und jetzt wird einfach für diesen Ausdruck, das ist alles,
was man braucht, um diesen Ausdruck auswerten zu können,
wird einfach durch alle Bestandteile durchgegangen mit einer erweiterten Vorschleife.
Also für jeden String-S in diesen Bestandteilen. Wenn es das Pluszeichen ist,
dann nehme ich die beiden obersten Elemente vom Stapel
runter und berechne dann die Summe der beiden und packe das
wieder auf den Stapel drauf
Also zweimal Pop, einmal Push. Genauso für die anderen
Opper, Ratoren. Und am Ende sieht man dann,
wenn da etwas kommt, was weder ein Plus noch ein Minus,
noch ein Mal noch ein geteilt ist,
dann muss es wohl ein Operant sein, also eins,
zwei, drei oder 15 oder ähnliches,
dann wird einfach hier versucht, mit Inditure Pass Int,
diese Zeichenkette zu passen.
Und dann wird diese Zahl einfach draufgepusht. Das heißt also,
wenn da mehrere Zahlen kommen, wenn wir die einfach draufgepusht und
dann wird mit dem Operanten hinterher, werden diese wieder runtergenommen.
Und am Ende, wenn sozusagen alle Bestandteile einmal durchgegangen wurden,
dann wird einfach das geliefert, was auf dem Stapel übrig geblieben ist.
Ja, das Ergebnis des letzten Ausdrucks. Der letzten Berechnung. Ja,
das ist eigentlich schon alles, was man wissen muss.
Eine Möglichkeit, wie man Stacks sehr elegant verwenden kann,
eben halt für die Postfix-Notationen von Ausdrücken.
Ja, da ist eine Frage. Die Frage auch,
ob man das auch mit der Switch-Stätte schreiben kann.
Anstatt mit dem Verbreiten. Ah, okay. Da kam die Frage,
ob man das hier, was ich gemacht habe,
auch hätte machen können mit einem Switch-If-S-Equiels plus oder minus und
so weiter
Die Antwort ist inzwischen ja, weil man inzwischen auch über Strings switchen kann.
Früher ging das nicht in den alten Java-Versionen,
war das noch nicht möglich,
da konnte man nur über Int-Cha und so weiter switchen.
Inzwischen würde das tatsächlich auch funktionieren. Man könnte auch
über einen String switchen. Also dann hier ein Switch und
dann Case Plus, Case minus, würde gehen.
Ja, guter Hinweis an der Stelle.
Gut. Sonst noch Fragen jetzt zu dieser kleinen Demonstration,
wie man so einen Stapel verwenden kann.
Implementationsskizze ist jetzt die andere Seite. Das eine ist,
wie benutze ich so ein Ding? Man kann das zum Beispiel
für Ausdrucksauswertung verwenden, wenn einem die Dienstleistung angeboten wird.
Andersrum, wir wechseln jetzt die Seite, wir wollen jetzt so
ein Ding bauen, wir wollen so ein Stack implementieren.
Das ist jetzt nicht so wahnsinnig schwierig. Listen kennt ihr,
Listen kennen wir. Ich benutze einfach intern eine Liste,
eine Liste von diesen Elementen. Also hier auch dieser Stack-Impel,
der irgendwie so ein Stack-Interface implementiert. Beides generisch. Das
ist also hier jetzt wieder mit generischen Typen. Das ist alles,
was wir noch nicht wirklich intensiver uns angeguckt haben, aber auch keine Hexenkunst.
Dann wird hier tatsächlich einfach Link List verwendet,
weil man bei der Link List so schön immer vorne einfügen kann,
mit relativ geringem Aufwand. Egal wie groß der Stack ist,
füge ich immer vorne ein, add an Position
0. Und wenn ich dann mit Pop das erste Element haben will,
dann mache ich einfach nur Remove an Position 0. Und vorher wird hier noch gefragt,
if not is empty.
Das heißt, es ist ein bisschen sicherer.
Man wird also wohl noch geprüft, ob man auch wirklich
ein Element runternehmen kann von dem Stapel.
Soweit so gut. Das ist der Stack mal so implementiert.
Keine weltbewegende Geschichte, aber ich glaube,
ihr solltet ihn mal gesehen haben, weil er,
wie gesagt, in sehr vielen Anwendungen sehr, sehr nützlich ist.
Zweiter Datentyp, den wir uns angucken, auch wieder ein Sammlungstyp,
nämlich die Schlange oder auch Q. Ist auch was,
was man aus dem Alltag sehr, sehr gut kennt.
Wenn ich nach London fahre und mich mit dem Bus fahren will,
dann stelle ich mich üblicherweise in eine Schlange.
Bascue, wo man tatsächlich dann, die Engländer sind da
ja total diszipliniert, die stellen sich da wirklich sauber an.
Und es kommen immer nur die ersten wirklich mit und so.
Also das ist so wirklich first in,
first out oder first come first surft.
First in, first out ist hier also
das FIFO-Prinzip
Immer der, der zuerst kommt, wird auch als erster rausgenommen.
Also das ist eher eher so, ich habe hier sowas und da
kommt immer was dazu und von hier nehme ich weg.
Also die, die sozusagen zuerst gekommen sind,
werden auch als erste bearbeitet. Das ist bei Schlangen nicht
so klar festgelegt wie bei einem Stack. Ich denke,
würde man eigentlich immer sagen, das ist das FIFO-Prinzip,
es wird immer das zuletzt eingefügte Element als erstes wieder entnommen.
Bei einer Schlange ist das, gibt es Abweichungen,
da gibt es Variationen. Es gibt Schlangen,
die sind so organisiert, dass wenn die Elemente eingefügt werden,
die Reihenfolge geändert wird. Wenn die Priorität des Elementes,
was dann neu angefügt wird, sehr hoch ist,
Dann kommt es weiter vorne in die Schlange rein.
Das heißt also nicht unbedingt immer ist das bei einer Schlange First-in-First-out.
Es gibt eben halt auch sowas wie gewichtete Warteschlangen, Priority-Cues.
Die sind auch sehr nützlich in ganz vielen Kontexten, braucht man ständig.
Beispielsweise Tastatureingabe, auf so einer Tastatur wie hier. Früher war das so,
wenn man da irgendwie schnell getippt hat, dann musste man aufpassen,
dass der Rechner nicht irgendwie nicht hinterherkam mit dem Verarbeiten dieser Zeichen.
Also hat man einen Puffer angelegt, in den diese getippten Zeichen reinkamen.
Und dann hat sozusagen der Rechner auf diesen Puffern drauf zugegriffen,
hat immer das nächste Zeichen abgearbeitet und man konnte aber
schon vorweg tippen
Heutzutage sind die Rechner so schnell,
dass das wahrscheinlich in dem Sinne nicht mehr notwendig wäre,
aber es hat auch andere Vorteile, wenn man einen gewissen Tastaturpuffer hat.
Das ist also auch relativ einfach zu verstehen.
Auch da kann man sich jetzt wieder vorstellen, wie das
genau vorgehen kann. Ich habe ein Interface Q,
habe zwei Operationen, NQ und DQ,
also etwas einreihen in die Schlangen und etwas ausklinken aus der Schlange.
Auch wieder die Frage, ob es leer ist,
weil man sonst ja DQ nicht unbedingt aufrufen kann.
Und wieder mache ich das jetzt und ihr seht,
ich mache das jetzt hier visualisiere, das ist horizontal.
Also ich habe hier
irgendwie eine Cue mit zwei Elementen
Und meine variable Kuh verweist da drauf. Jetzt sage ich,
QNQXQX, dann wird ja so hier X sozusagen hinten dran gehängt.
Und dann mache ich noch ein NQZ,
dann wird das Z hinten dran gehängt.
Und jetzt rufe ich auf DQ und dann bekomme ich genau das erste Element.
Also das, was zuerst eingefügt wurde, das A in dem Fall.
Gut, auch das ist jetzt nicht so wahnsinnig spannend.
Wir können uns dazu, da ist es tatsächlich so,
der Steck ist eine Klasse aus der Frühzeit von Java.
Da waren das mit den Interfaces noch nicht so etabliert.
Das sieht aber alles auch im Collections Framework,
also unter Java You Till,
wisst ihr ja,
Collections Framework unter anderem
Und da sieht man hier das Interface Q. Man sieht hier auch die Klasse Stack,
da ist sie, die haben wir gerade gesehen.
Aber hier ist auch das Interface Q. Und ja,
das hat etwas andere Operationen. Man sieht hier,
es gibt eine Operation Offer, eine Offeration Peak,
auch wieder so, was jetzt als nächstes dran wäre,
nur mal angucken, nicht rausnehmen aus der Schlange.
Offer ist so eine Operation, versuch mal es einzufügen.
Das ist insbesondere bei bestimmten Schlangenimplementationen,
die mit einem beschränkten Puffer auskommen müssen, sinnvoll.
Das heißt, irgendwie ist da so ein Puffer.
Und wenn der Puffer voll ist, kann ich ja kein Element mehr einfügen.
Und mit diesem Offer wird ihm halt genau sowas ermöglicht,
dass man sagt, versuch mal, es einzufügen.
Und deswegen wird dann da auch ein Bolien geliefert.
Also ob das Einfügen geklappt hat. Das heißt,
Klienten können tatsächlich überprüfen, ob das Einfügen funktioniert hat.
Relativ leicht. Genau. Remove und Poll und die heißen
dann so ein bisschen anders. Aber da gibt es dann
eben halt auch einiges dazu, was hier so steht.
Hust typically, but do not necessarily,
order elements in a FIFO-Männer
Und man sieht hier auch zum Beispiel Linked List ist
eine Implementation von der Q, von dem Q Interface.
Ja, jetzt auch da wieder, wie kann man sowas benutzen?
Wofür ist so eine Q nützlich? Ja,
ich habe jetzt gerade gesagt, du Tastaturpuffer ist ein Beispiel.
Zweites Beispiel ist ein breiten Durchlauf durch Bäume. Ihr habt
Bäume letzte Woche von mir so ein bisschen vorgestellt bekommen.
Nehmen wir mal an, wir haben einen binären Baum.
Ihr wisst, ein binärer Baum, da hat jeder Knoten maximal zwei Kindknoten.
Und ich möchte jetzt einen breiten Durchlauf durch die Knoten machen.
Also ich habe hier so einen Baum als Beispiel
und ich möchte jetzt praktisch dieser blau gestrichelten Linie folgen
Wenn ich euch jetzt fragen würde,
wie würdet ihr das implementieren, dann würde ich vermuten,
dass euch das erstmal nicht so leicht fällt,
da auf eine Idee zu kommen, wie man das machen könnte.
Ihr habt eigentlich nur eine Operation, um euch von einem Knoten,
den linken und den rechten Kindknoten geben zu lassen.
Und ihr müsst also jetzt sozusagen diese Reihenfolge,
die hier angedeutet ist, 1, 2, 3,
4, 5, 6, 7 und so weiter,
die müsst ihr irgendwie hinkriegen.
Jetzt ist es aber so, dass zum Beispiel
nach Knoten 2 soll ja hier Knoten 3 bearbeitet werden.
Und es gibt jetzt keine direkte Verbindung von Knoten 2
zu Knoten 3. Oder nach 5 soll 6 kommen und
auch die sind ja nicht direkt verknüpft
Noten 5 hat ja nur Referenzen auch. Den Vaterknoten oder den Elternknoten
und hier auf die beiden Kindknoten. Das heißt also,
es drängt sich jetzt hier nicht irgendwie auf,
wie man leicht hier einen breiten Durchlauf durchmachen kann.
Wie kriege ich es hin, dass die genau in dieser
Reihenfolge verarbeitet werden? Das ist nicht so einfach.
Andererseits habt ihr das Skript möglicherweise vor euch liegen,
da habe ich es leider schon enthüllt.
Das heißt also, ich brauche es jetzt auch nicht zu spannend zu machen.
Die Lösungsskizze sieht total einfach aus. Wenn man erstmal
eine Q verstanden hat,
geht das wirklich
wie von alleine
Ich habe eine Q von diesem Knoten, vom Typ,
also der Elementtyp, der Q ist dann Knoten oder Baum,
je nachdem, wie man das Ding nennt.
Füge erst das Wurzelelement in die Q ein. So, Initialisierung.
Und jetzt mache ich eine Schleife, die ganz einfach ist.
Solange da noch was drin ist und am Anfang ist ja
mindestens der Wurzelknoten drin, mache ich folgendes.
Ich entnehme das Element und füge die Nachfolger in die Q ein.
Das heißt also, ich entnehme hier den Wurzelknoten und füge
dann linken und rechten Nachfolger ein.
Und das mache ich in der Schleife. Das heißt,
als nächstes nehme ich jetzt natürlich Den Knoten mit der 2,
weil der ist in der Kühe jetzt einfach drin.
Also wir haben sozusagen nochmal für euch jetzt veranschaulicht hier,
Knoten 1, Knoten 2 Knoten 3. Ich habe Knoten 2
rausgenommen und dann erst Knoten 2 und 3 eingefügt.
Jetzt ist das nächste Knoten 2 dran. Also wird
jetzt von dem, wenn jetzt die Kindknoten eingefügt,
also 4 und 5 kommt dahinter und dann wird der Knoten entfernt.
Dann kommt als nächstes der Knoten 3 dran,
von dem werden die Kindknoten dahinten angehängt.
Also in dem Fall 6 und 7 und schwuppdiwupp, wunderbar.
Ich kann mit so einer Kühle ganz einfach einen breiten Durchlauf
durch einen Baum machen.
Sehr einfach
Und das ist tatsächlich so,
wenn man so mit Algorithmen auf so dynamischen Datenstrukturen arbeitet,
dann wird man ganz häufig entweder den Stack oder die Q gut verwenden können.
Also die solltet ihr euch irgendwie so ein bisschen aneignen.
Gut, hier ist eine Implementationsskizze, jetzt wieder Szenenwechsel.
Vorher, wie haben wir so ein Ding mal benutzt,
um irgendein Anwendungsproblem oder ein Programmierproblem zu lösen?
Jetzt, wie könnte man es denn implementieren? Naja,
man nimmt irgendwie ein Array,
um es mal mit einem beschränkten Puffer zu machen,
diese Entwurf als Ringpuffer ist speichereffizient für begrenzte Schlangen,
also für Bounded Buffer heißt das auch,
also ein Tastaturpuffer zum Beispiel hat eine bestimmte Größe.
Und da ist es einfach so,
irgendwann ist das Ding voll und dann muss man irgendwie darauf achten,
dass man da nicht mehr reinfügt, als reinpasst.
Das kann man sich eben halt so veranschaulichen, dass dieses Ray
quasi so zusammengekettet ist. Und dann hat man sozusagen immer zyklisch,
wird da eingefügt und entfernt und die dürfen sich halt nicht überholen,
diese beiden Klienten, die einen, die was reinfügen,
die anderen, die was rausholen.
Gerade bei nebenläufigen Prozessen, das ist was,
was wir hier in SEA als SE2 gar nicht thematisieren,
ist das ganz häufig so, so ein Bounded Buffer gearbeitet wird,
also irgendein Erzeuger packt was rein und ein Verbraucher holt was raus.
Erzeuge ich mit der Tastatur, Verbraucher,
der Computer, der das dann irgendwie verarbeitet.
Gut. Implementationsskizze also auch für Q. Ich sehe viele gelangweilte Gesichter.
Gibt es da Fragen dazu? Ist also auch relativ klar,
denke ich. Was ich jetzt nochmal eben sagen will,
ist, was wir gerade gesehen haben, waren zwei Interfaces,
einmal für Step, einmal für Q, Push, Pop ist MT.
Und bei Q habe ich die jetzt mal NQDQ genannt,
etwas anders als im Collections Framework, aber es ist sozusagen nachvollziehbar, was gemeint ist.
Ich habe hier noch ein weiteres Interface, das nenne ich mal Geblubber,
hat drei Operationen, bla, blö und blub.
Ihr seht, das ist ziemlich sinnbefreit, aber diese Signaturen
von diesen Interfaces, die sehen irgendwie ziemlich ähnlich aus.
Die unterscheiden sich fast gar nicht, nur in hier Benennungen von den Elementen,
also so wie das Interface heißt und wie die Operation heißt,
aber ansonsten sind die Signaturen formal gleich.
Also es gibt eine Operation, die bekommt ein Element von
dem generischen Typ hier. Hier die Operation,
die liefert irgendwas von diesem Typ und man kann fragen,
ob es leer ist oder irgendwie blub.
Aber die lieferten Bulien. Die sind also eigentlich kaum zu unterscheiden. Und das ist ganz wichtig,
dass euch klar ist, dass so ein Interface,
wie wir es ja hier in SE1 euch jetzt nahegebracht haben,
was man sehr gut verwenden kann, um Schnittstellen zu beschreiben.
Aber die sind eben halt nur semiformal. Das heißt,
es ist klar, der Typ des Parameters ist hier festgelegt,
der Typ des Ergebnisses ist festgelegt.
Es ist klar, dass es mindestens diese drei Operationen gibt.
Also jede Klasse, die das implementiert, das Interface,
muss genau diese Operationen auch implementieren.
Da ist also schon formal ein bisschen was drin für Java festgelegt,
aber da steht überhaupt nichts darüber drin,
dass das eine sich wie ein Stack verhält und das andere
wie eine Q. Wir lesen das so und sehen, ah,
stack arg, und wir verbinden damit ein mentales Modell.
Das habe ich euch gerade versucht nahezubringen, es gibt Stacks,
es gibt Cues. Und die verhalten sich unterschiedlich.
Aber das steht da nicht in diesen Signaturen drin,
es steht auch nicht in den Interfaces.
Was man machen kann, ist, man schreibt da Kommentare
rein und sagt, dieses Interface soll einen Stack beschreiben.
Auch das ist dann eben, deswegen nennt man das semiformal,
ein Kommentar ist kein formaler,
keine formale Spezifikation
Das ist nur informell. Was nicht heißen soll,
dass ihr nicht sprechende Namen verwenden solltet. Also gerade,
wenn man dann den Klientencode lesbar haben will,
ist es schon gut, wenn die Operationen gut benannt sind.
Ich sage das an dieser Stelle nur deshalb,
weil wir auch so einen kleinen Ausblick jetzt hier machen auf das, was in SE2 drankommt.
Da gucken wir uns nämlich dieses Konzept von abstrakten Datentypen nochmal ein bisschen genauer an.
Das ist etwas, was in der theoretischen Informatik stark ausgeforscht wurde.
Also abstrakte Datentypen, die dann eine formale Spezifikation darstellen.
Also da wird tatsächlich versucht, das Verhalten zu beschreiben,
informalerweise, also in eindeutiger Weise, ohne festzulegen, wie es implementiert ist.
Ihr wisst, wenn ich mir in Java eine Implementation angucke,
dann ist die Semantik festgelegt, dann sehe ich aber,
ah, die Klasse macht ja das und das, Verhalten ist klar.
Aber die Idee ist eben halt, man möchte eigentlich
das Verhalten abstrakt beschreiben, ohne eine Implementation vorzugeben.
Und das kann man mit abstrakten Datentypen machen. Und die sehen ungefähr so aus,
man sieht hier, das ist sehr ähnlich,
Functions sind so mehr oder weniger die Operationen,
die wir bisher kennengelernt haben, also das ist quasi das,
was wir bisher als Interface kennengelernt haben hier,
Name des Typs mit einem generischen Parameter und dann irgendwie push,
pop, top, and the new,
was weiß ich.
Das sind so die Operationen. Und dann ist das spannende Neue,
sind dann hier die Aktionen. Das war so Aussagen getroffen werden,
wie denn diese Operationen im Zusammenspiel wirken sollen.
Und das ist etwas, was wir uns hier nicht sehr ausführlich ansehen wollen. Es ist nur,
ihr könnt euch schon mal so ein bisschen merken,
hier kommen dann so Pre-Conditions rein,
also zum Beispiel ich darf bei einer Vorbedingung für einen Pop ist,
bevor ich also etwas runterholen kann vom Stack.
Require not empty, also er darf nicht leer sein.
Und das kann man eben halt tatsächlich formal beschreiben,
also so das Vertragsmodell, das sehen wir dann in SE2,
sehen wir uns das näher an. Und das mit den Aktionen,
das gucken wir uns auch nochmal ein bisschen genauer an am Ende von SE2. Ja,
aber die sind tatsächlich diese Spezifikation auch ein bisschen anders als das,
was wir hier kennengelernt haben,
weil die hier tatsächlich immer als Ergebnistyp wieder ein neues Deck rauskommt.
Also das ist nicht so ein zustandsbasiertes Modell, sondern ein funktionales Modell.
Das gucken wir uns ausführlich an. Was wir jetzt aber
hier so in SE1 für uns hier merken können,
wir wollen einfach weiterhin Spezifikation und Implementation trennen,
also immer diese Idee haben, ich kann eine Dienstleistung beschreiben,
ohne sie implementieren zu müssen.
Und ich verberge die Implementation teilweise auch,
um einfach dann als Klient mehr Freiheiten zu haben,
mich nicht zu sehr darauf zu verlassen, wie das implementiert ist.
Gut, ja, Sturisi zur Auflockerung. Machen wir doch mal.
Gucken wir mal, was wir so an spannenden Fragen haben.
Vorlesung 13. Dann gib doch mal eure Meinung ab.
Also, welche der folgenden Aussagen ist richtig? Ein Stack ist
nach dem FIFO-Prinzip organisiert oder die Operation top entfernt üblicherweise,
das zuletzt eingefügte Element eines Stacks
Oder die Operation Push legt üblicherweise ein weiteres Element auf einen
Stack oder die Semantik eines Stacks wird ausschließlich über die Signaturen
seiner Operationen in einem Interface definiert.
Das sind die vier Möglichkeiten, die ihr jetzt habt und ihr
könnt da jetzt abstimmen, wie ihr das denn seht.
Auch das ist so klassische Klausuraufgabe, kommt gerne in dieser Form vor.
Ja, super, wir haben schon über 100 Stimmen.
Großartig, also sind offensichtlich mehr als 100 Leute hier.
Das ist sehr beruhigend
Von den ursprünglichen, ich glaube,
wir haben nach wie vor in den Übungen irgendwie 480 Teilnehmer.
Die sind nicht alle hier,
aber wir haben ja auch diese wunderbare Aufzeichnung,
das ist klar, dass wir das auch offline gucken.
So, 125 Stimmen, wunderbar. Ich halte das mal
an. Ich gucke mal auf die Verteilung. Und das ist
keine Überraschung. Das war im letzten Jahr so ähnlich.
Die Mehrheit von euch hat die richtige Antwort erkannt hier,
also die mir davon sagt, sagt erstmal, es sei diese Operation pusht.
Aber es gibt auch ziemlich viele, fast ein Fünftel,
die sagen, die Semantik eines Stacks wird ausschließlich über Signaturen seiner Operation.
Ja, ist natürlich nicht richtig. Ja, das ist
hier die richtige Aussage, das ist mit dem Push.
Operation Top entfernt nicht
Sondern Pop entfernt. Top liefert nur das erste Element.
Stack ist nach Liefern, nicht nach FIFA organisiert. Und die Semantik ist,
wie gesagt, nicht nur über die Signaturen festklickt,
sondern da muss man mehr machen, um die Semantik exakt zu beschreiben.
Gut. Schön. Noch Fragen jetzt zu Stack und Q.
Nein, ich mache weiter. Zusammenfassung, Stack,
Sammlungstyp, Lieferprinzip, Q-Sammlungstyp, FIFO-Prinzip weiter.
Sortieren. Jetzt gucken wir uns mal an,
wie man Sammlung von Objekten sortieren kann. Das braucht man
ziemlich viel. Also im Alltag finden wir sortierte Sammlungen,
Telefonverzeichnis, ihr werdet ziemlich unglücklich, wenn ihr ein Telefonbuch
in die Hand nehmt und das wäre nicht alphabetisch nach Nachnamen sortiert.
Da würdet ihr ziemlich lange suchen, bis ihr einen Eintrag findet.
Kennt ihr ja, soll linear suchen, mag man nicht so gerne.
Ein Telefonbuch, nur so als Erinnerung, ist eigentlich Hashing.
Man weiß den Anfangsbuchstaben, ah ja,
dann guckt man schon mal an der richtigen Stelle und dann weiß man,
oh, der zweite Buchstabe, dann weiß man schon mal ungefähr, wo man blättern muss.
Also so, man kann also relativ,
man kann so gezielt so, das ist Hashing,
mehr oder weniger
Geht aber nur, wenn es notiert ist. Oder Fahrpläne,
auch da wird ihr ziemlich genervt,
wenn jetzt ein Bahnhof kommt und ihr wollt irgendwie jetzt das
Gleis finden für einen Zug, von dem ihr wisst,
wann er abfährt und ihr müsstet die jetzt irgendwie durch eine Liste aller abfahrenden Züge durchsuchen,
wo denn ist.
Wenn es sortiert ist, nach Abfahrtzeit ist das sehr,
sehr angenehm. Man kann man sehr schnell suchen.
Gut, also sortieren ist wichtig, brauchen wir.
Klar, wir brauchen sortierte Sammlungen. Und das ist auch
eine klassische Informatikaufgabe. Also das ist etwas,
wo man sehr schön Informatik dran üben kann.
Also es soll eine Sammlung von Objekten in eine geordnete Reihenfolge
gebracht werden, was man dafür braucht,
ist eine Ordnungsrelation
Eine strenge, schwache Ordnung, eine strenge, schwache Ordnung ist eine Ordnung,
die zulässt, das Elemente auch gleich sein können.
Also es muss nicht sein,
die müssen nicht total geordnet sein, sondern es reicht,
wenn sie streng schwach geordnet sind und bezogen auf ein Merkmal von Objekten.
Also nehmen wir mal an, ihr habt die Zeichenketten,
Hund, Katze, Maus und ihr solltet jetzt sortieren nach der Länge der Zeichenkette.
Ganz einfach, Hund hat vier, Katze hat fünf,
Maus hat vier Zeichen,
also ist wohl die Sortierung so,
dass die Katze
irgendwie nach hinten kommt
Aber es ist natürlich jetzt erstmal,
also man unterscheidet dann den Sortierschlüssel und das Sortierkriterium.
Der Sortierschlüssel ist also eine Eigenschaft von dem, was da sortiert wird.
Also zum Beispiel die Länge der Zeichenkette. Und das Sortierkriterium baut
auch dem Schlüssel auf, aber das Kriterium könnte ja auch sein, aufsteigend oder absteigend.
Da muss man unterscheiden. Das ist die Ordnungsrelation. So,
und die Algorithmen, die, nach denen Sammlungen sortiert werden,
heißen Sortierverfahren, die gucken wir uns gleich mal ein bisschen an.
Hier nochmal so ein bisschen, was eine Aufsteigende anordnet,
was war eine absteigende Ahnung, bla bla. Ich bin vor zwei Jahren mal darauf hingewiesen worden,
dass das ja total schön ist,
dass wir hier in SE1 uns angucken, wie man Sortieren implementiert,
aber eigentlich haben wir ja immer die Logik,
Ihr sollt zwar lernen, wie man so Sortieralgorithmen schreibt,
aber eigentlich sollt ihr ja die Dinger nicht selber implementieren,
sondern ihr sollt die Sortieralgorithmen, die schon da sind,
benutzen.
Und erstaunlicherweise gibt es auch im Collections-Framework für Java schon Sortieralgorithmen.
Die sind schon da. Und eigentlich ist es für euch
viel wichtiger zu wissen, wie man die benutzt,
als dass ihr wisst, wie man die implementiert.
Genau wie mit den Sammlungen. Es ist zwar ganz hübsch,
dass wir hier verkettete Listen gebaut haben, aber eigentlich nur,
damit ihr nochmal richtig intensiv mit Referenzen in Kontakt kommt.
Ihr werdet später hoffentlich nicht mehr eure Sammlungen selbst bauen,
sondern immer die verwenden die da sind.
Genauso beim Sortieren verwendet das, was da ist.
Und ich wollte jetzt hier einfach nochmal demonstrieren, das ist übrigens eine Vorliebe,
die nicht im Skript ist, nicht, dass ihr jetzt überrascht seid.
Es gibt im Collections Framework einmal etwas,
um zum Beispiel Listen zu sortieren, also hier Java,
you till collections.saw, die heißen wirklich so diese Klassen,
das ist also eine Klasse mit lauter statischen Methoden.
Können wir uns auch mal angucken,
aber wir haben ja hier die Doku, also Java,
you till, und dann gibt es eine Klasse Collections,
da ist sie, und die hat hier unter anderem,
die hat ganz viel Zeug, ihr seht,
das ist richtig viel zu euch,
Und jetzt gibt es aber irgendwo auch ein Sword da.
Sword mit einer Liste, Sword mit einer Liste und einem Comparator.
Okay, das gucken wir uns gleich mal genauer an.
Also hier wird irgendwie sortiert nach der natürlichen Ordnung der enthaltenen Elemente in der Liste.
Und hier kann man noch einen Comparator angeben.
Was das bedeutet, zeige ich gleich mal.
Ich habe da ein Bluejay-Projekt dabei. Sortieren, konsumieren.
Das ist das hier. Das ist Eigentlich ein relativ,
das sieht auch relativ aufwendig auf,
aber es ist eigentlich nur eine Implementation von folgendem.
Ich weiß nicht, ob ihr das schon mal gesehen habt,
aber unter Windows hier, das ist so ein Explorer-Fenster, ein Windows-Explorer.
Und ich habe ja hier eine Sicht auf Einverzeichnis.
Ich habe also hier ein verzeihenes, sind immer diese
gelben Ordner, Verzeichnis, Ordner, wie auch immer.
Und ich habe jetzt hier so einen Ordner geöffnet und dann
sehe ich jetzt hier, was in dem Ordner drin ist.
Also irgendwie Verzeichniseinträge in so einem Verzeichnis. Und ich kann
das Ganze sortieren. Ich kann also hier sagen,
sortiere das mal nach Namen. Jetzt ist es nach Namen
absteigen,
nach Namen aufsteigend
Ich kann es aber auch nach Änderungsdatum sortieren oder nach dem Typ.
Er setiere ich das so ein bisschen. Hier ist es
jetzt fast schon sortiert, aber wenn es nach dem Typ sortiert,
dann ist es irgendwie Microsoft-PowerPoint, liegt nach Textdokument.
Also man kann das hier unterschiedlich sortieren. Und das ist
tatsächlich eine Sicht auf das Verzeichnis. Das heißt also,
wenn ich jetzt dieses Verzeichnis hier in einem weiteren Fenster nochmal aufmache,
in neuem Fenster öffnen, dann habe ich zwei Verzeichnisse.
Das eine ist, nach Änderungsdatum sortiert, das andere ist nach Name sortiert.
Zwei Sichten auf dasselbe Verzeichnis. Das heißt,
was in dem Verzeichnis drin ist, ist Das Gleiche,
das ist das gleiche Verzeichnis.
Ich habe nur zwei verschiedene Sichten da drauf.
Das ist etwas, was sehr viel vorkommt in grafischen Benutzungsoberflächen.
Ich mache hier mal wieder eins zu. Und da habe
ich mir gedacht, das können wir doch mal nachimplementieren.
Also haben wir hier, folgendes, habe ich
sehr schnell mal so zusammengehackt letztes Jahr.
Verzeichniseintrag, einen Eintrag in einem Verzeichnis,
habe ich mal versucht sozusagen hier so einfach zu modellieren, objektorientiert.
Da habe ich einfach gesagt, naja,
ich habe einen Namen und einen Typ und ein Datum,
also sehr einfach modelliert.
Jeder Eintrag hat einen Namen,
einen Typ und ein Datum
Dann gibt es hier Operationen für so einen Eintrag,
gibt mir mal das Datum des Eintrags, es gibt mir den Namen,
gib mir den Typ, mein to string.
Und dann gibt es hier noch ein Compare to,
das gucken wir uns dann gleich nochmal an.
So, dann habe ich ein Verzeichnis modelliert, also genau,
was ich gerade gesagt habe, so einen Ordner mit Verzeichniseinträgen.
Es ist also nicht weiter aufregend, das ist einfach eine Liste von Verzeichniseinträgen.
Und da kann ich eben halt ein verzeichnis Eintracht einfügen,
füge ein. Und ich kann mir die Einträge,
die da drin sind, einfach alle geben lassen.
Da gibt es also eine Methode,
gib mir alle Einträge, liefert mir eine Einträge,
liefert mir eine Kopie,
der irgendwie intern gehaltene Liste der Einträge
Da wird also hier eine Liste der Verzeichniseinträge geliefert.
Total einfach. Und dann gibt es Verzeichnisansicht.
Das modelliert jetzt, was ich gerade gezeigt habe,
dass man verschiedene Sichten haben kann, auf dasselbe Verzeichnis.
Ich kann also hier bei dieser Verzeichnisansicht,
da stecke ich ein Verzeichnis rein.
Ich kann also verschiedene Sichten erzeugen, auf dasselbe Verzeichnis.
Also hier um Konstrukte stecke ich einfach dasselbe Verzeichnis rein.
Und dann kann ich hier sagen, sortiere Ansicht nach Name.
Also genau wie mit diesem Klick auf diesen Reiter,
wie ich das gemacht habe unter Windows.
Oder sortiere Ansicht nach Typ.
Das sind einfach jetzt hier
zwei Möglichkeiten
So, und jetzt einfach mal, wie man das benutzen kann.
Hier ist der Klientencode, der ist wirklich sehr einfach.
Ich erzeuge mir ein Verzeichnis. Dann mache ich so
ein paar Verzeihniseinträge und füge die jeweils ein.
Immer hier Verzeihniseintrag an so was. Ich habe hier
so mal versucht, das so ein bisschen nachzubilden,
was ich hier in diesem Verzeichnis drinsteht.
Also hier irgendwie Choreografie, Folien, PowerPoint, irgendwelche Ordner.
Dann sieht man also hier Projekte, Ordner, Folien,
Folien und so.
Also das ist so ein bisschen sehr einfach.
So, und dann erzeuge ich mir so
eine Verzeichnisansicht
Und sage dann, schreibe auf Konsole,
kann also dann hier tatsächlich das mal anzeigen lassen,
damit man einfach sozusagen eine einfache Visualisierung von dem bekommt,
was diese Ansicht jetzt anzeigt.
Das ist also dieses Schreiber auf Konsole gibt einfach nur die Elemente aus, unsortiert.
Also hier werden die Elemente ja irgendwie unsortiert eingefügt.
So, das kann ich jetzt einfach mal ausführen.
Das sieht dann so aus, zeige Benutzung. Man sieht hier Choreografie,
Folienprojekte, das sind also so fünf Einträge in meinem hier imaginären Verzeichnis.
Und das ist nach keiner der Kriterien sortiert. Weder hier nach Namen,
Man sieht hier, das P kommt vor dem F,
auch nicht nach Typ sortiert und auch nicht nach Datum.
Das Datum ist hier relativ einfach, modelliert durch ein Int. Wirklich sehr simpel. Okay,
und jetzt habe ich hier diesen Klienten,
da habe ich jetzt hier noch so ein paar andere Operationen,
ich kann also hier mal das wegnehmen und kann sagen,
ja, mach doch mal das so, sortiere das mal nach Namen und schreib das dann auf Konsole.
Okay, machen wir das mal, zeige Benutzung,
da sieht das dann so aus.
Ja, also wie man sieht, funktioniert das anscheinend.
Ich kann tatsächlich die Sachen hier sortieren nach den Namen.
Dann habe ich hier auch noch implementiert, Dass man das
auch nach dem Typ sortiert. Also auch das kann ich
übersetzen. Kann ich ausführen, zeige Benutzung. Und dann sieht man,
das ist hier nach dem Typ, also OP,
T, also Ordner, PowerPoint, Text, sortiert.
So, also ihr seht, das funktioniert. Und was natürlich
jetzt viel spannender ist, wie habe ich es denn implementiert?
Das ist ja das Entscheidende. Und das ist eben
halt jetzt hier nur mal angedeutet. Wie gesagt,
das ist dann doch etwas, was ihr nicht unbedingt jetzt hier implementieren sollt in SE1.
Hier ist einfach Java You Till Collection Sorpan, also Sortiere,
Ansicht nach Name
Da es einfach benutzt Java, You Till Collections, Sword.
Und es wird reingereicht, diese gezeigten Einträge.
Ich lasse mir hier von dem Verzeichnis einfach die Einträge geben,
das ist eine Liste, wie ich da bekomme.
Und so eine Liste kann ich ja einfach in diese Sword-Operation reinstecken.
Und dann sortiert mir Sword diese Liste.
Und zwar nach der natürlichen Ordnung der Elemente.
Die Elemente, die da drin sind, sind ja Verzeichniseinträge.
So, und da habe ich ja hier dieses Bluejay-Projekt,
da habe ich ja hier diesen Verzeichniseintrag.
Und die implementieren die Operation Compare
Und dadurch, dass sie Compare 2 implementieren,
implementiert tatsächlich diese Klasse, die den Typ comparable.
Also ich sage, dieser Element Typ Verzeihungseintrag ist comparable.
Man kann die also miteinander vergleichen. Deswegen hier auch Verzeichniseintrag als
Typ Parameter. Das heißt, die sind miteinander vergleichbar.
Ich kann also fragen, welcher Verzeichniseintrag ist größer als der andere?
Und das ist implementiert einfach über die lexikografische Ordnung von dem Namen.
Das ist also hier drin implementiert. Das heißt, der
Element Typ hat eine natürliche Ordnung. Und wenn ich einfach
nur Sword aufrufe, dann wird nach dieser natürlichen Ordnung sortiert.
Wenn ich jetzt einen Sword of Intigern aufrufe, dann würde eben halt,
die Intiger würden irgendwie nach ihrer aufsteigenden Ordnung sortiert.
Spannender ist jetzt, wie das hier mit dem Sortieren nach dem Typ ist.
Jetzt habe ich also diese Elemente, da kann ich fragen,
welcher Verzeichniseintrag ist denn größer und dann sagt er, ja,
der mit A ist kleiner als der mit B zum Beispiel.
Aber wenn ich die jetzt nach dem Typ sortieren will,
dann muss ich das ja irgendwie anders machen
Und das ist hier wirklich schon ein bisschen fortgeschrittenes Zeug.
Was ich dann brauche, ist,
und ich habe das hier in der Doku ja eben schon gezeigt,
das Sword hier bekommt nur einen Parameter, eine Liste von Elementen.
Das Sword hier bekommt noch einen Comparator. Und da ist es also so,
ich gebe noch ein weiteres Objekt dazu und dieses Objekt
entscheidet dann für zwei Elemente von diesem Typ,
von dem Elementtyp der Liste, in welcher Ordnung die zueinander stehen.
Das heißt, ich kann das beliebig programmieren,
in welcher Ordnung das erfolgen soll.
Ich muss mich nicht nach der natürlichen Ordnung der Elemente richten.
Also das ist dann total flexibel mit so einem Comparator und so einem Comparator zu implementieren,
ist eben halt so ein bisschen umständlich in Java.
Also hier wird so ein Typ-Vergleicher mal gebaut und der implementierte
ein Java You Till Comparator. Das ist also dann hier
so ein Comparator. Und dann musste man eine Operation Compare implementieren,
die dann für zwei Elemente entscheidet, in welcher Reihen vor dir liegen.
Da muss also ein Int geliefert werden. Und dann ist irgendwie so,
wenn das negativ ist, dann ist das eine kleiner,
wenn es positiv ist, das andere kleiner.
Und wenn null, dann sind sie gleich.
Das kommt da raus. Und ich benutze hier einfach jetzt wieder,
ich lasse mir von den beiden Verzeihniseinträgen den Typ geben.
Ist ja nichts aber da als ein String.
Comparedy, Compare to of String ist auch implementiert
Also liefert mir das irgendeinen Wert, also eine ganze Zahl minus plus oder null.
Und dann wird das hier als Ergebnis-Typ zurückgeliefert.
Und dann tatsächlich rufe ich dann hier auf Collection Sword,
wieder mit der Liste, aber dann übergebe ich hier noch diesen Typvergleicher,
den ich hier on the fly definiert habe.
Ich habe also tatsächlich, und das ist auch etwas,
was ihr noch gar nicht kennt, ich definiere hier eine Klasse innerhalb einer Methode.
Ich brauche die nur lokal hier,
weil ich nur an dieser Stelle jetzt diesen Exemplar,
ein Exemplar dieser Klasse brauche,
um das zu übergeben für den Sortieralgorithmus,
dass ich sage,
pass auf,
der soll jetzt entscheiden,
wann in welcher Ordnung
die Elemente zueinander stehen
Das ist ein bisschen fortgeschrittenes Zeug. Nur,
dass ihr es mal gesehen habt, ihr könnt also tatsächlich
eure Elemente in einer Liste nach beliebigen Kriterien sortieren.
Da seid ihr ganz flexibel. Gut. Ja. Beide
Operationen sind Klassenoperationen, also Static deklariert, uns sind überladen
, haben wir gerade gesehen.
Das heißt, die haben den gleichen Namen, haben aber unterschiedliche Parametalisten.
Also das Sword für Arace ist noch viel stärker überladen.
Das ist dann für alle primitiven Typen überladen und so weiter.
Schrecklich. Gut, das ist sortieren, konsumieren.
Gibt es dazu Fragen, also wie man so
die Sortieralgorithmen aus dem Collections-Framework verwenden kann
Da sieht das jetzt nicht so aus,
als ob da irgendwie große Neugierde entstanden ist. Gut,
dann würde ich sagen, können wir jetzt fünf Minuten Pause machen.
Okay. Sortieren, konsumieren haben wir jetzt gesehen. Also ihr wisst,
es gibt implementierte Sortieralgorithmen im Collections Framework für Race und für Listen.
Jetzt gucken wir uns mal Sortierverfahren an. Und da ist
es einfach hier so als erster Einstieg einfach fundamental so,
dass man die unterscheiden kann.
Man kann die klassifizieren. Man kann die nach ihrer Komplexität
klassifizieren. Also nach dem Aufwand, den sie benötigen,
um eine Menge an eine Liste mit N-Elementen zu sortieren,
wie viele Schritte sind notwendig, um L-Elemente zu ordnen?
Und das ist dann auch auf Unterschieden nach,
im besten Fall, im durchschnittlichen Fall und im schlechtesten Fall.
Und das dann teilweise auch abhängig von der Vorsortierung.
Dann ist bei Sortierverfahren auch ziemlich wichtig zu wissen, wie der Speicherbedarf ist.
Also wenn ein Sortierverfahren einer riesigen Liste mit mehreren Millionen Einträgen,
wenn dieses Sortierverfahren den gleichen Platz nochmal braucht,
kann es sein,
dass das nicht funktioniert
Also es gibt Sortierverfahren, die sortieren einfach innerhalb des Speicherplatzes,
wo die Sammlung schon vorliegt,
also sortieren innerhalb der Sammlung, meistens über Vertauschen,
brauchen also keinen Zusatzplatz oder nicht viel Zusatzplatz.
Und das nennt man eben halt in SITU oder auch inplace.
Also vor Ort wird sortiert, also in dem Speicherplatz,
wo die Sammlung schon liegt, oder es wird zusätzlicher Platz benötigt.
Dann ist ein Begriff die Stabilität. Ein Sortierverfahren ist stabil,
wenn es die ursprüngliche Reihenfolge zweier gleicher Elemente beim Sortieren nicht verändert,
sonst ist es instabil.
Ich hatte eben das Beispiel Hund,
Katze, Maus
Drei Zeichenketten, Hund, Katze, Maus, sollen sortiert werden,
aufsteigend nach der Länge der Zeichenkette. Da ist es klar,
dass die Katze nach hinten wandert, aber Hund,
Katze, Maus, da ist jetzt die Frage,
ist dann hinterher die Reihenfolge Hund,
Maus oder ist die Maus Hund?
Hund, Maus würde dann die gleich,
die gleich sind bezüglich des Sortierkriteriums, würde die dann unangetastet lassen.
Die Reihenfolge wäre gleich. Wenn sie verdreht werden, ist
das Verfahren nicht stabil. Okay. Und dann gibt es noch
die Art des Algorithmus, also die meisten Sortierverfahren sind Boah,
Leute, vor die Unruhe hier.
Gut, die meisten Satirverfahren sind vergleichsbasiert, aber nicht alle Sortierverfahren sind vergleichsbasiert.
Einige der Verfahren brechen tatsächlich aus den Elementen direkt Sortierschlüssel
und können dann direkt die Reihenfolge berechnen.
Gucken wir uns ganz zum Schluss nochmal an.
Was ziemlich spannend ist, ist die Komplexität von Sortierverfahren.
Also der Aufwand, der benötigt wird,
um eine bestimmte Anzahl von Elementen
zu sortieren
Und wenn man das ein bisschen formal betrachten will,
dann braucht man halt eine obere Schranke für die Anzahl von Schlüsselvergleichen
oder was da ähnliches an elementaren Operationen passiert.
Und führt vergleichsbasierte Verfahren. Und das sind die, die
wir uns gleich angucken. Also Bubble Sword und Quickshort kommt gleich.
Da ist es so, da gibt es einfach eine Schranke.
Das heißt, im besten Fall sind die von der Komplexität
O von N-Mal-Log-in. Ist erstmal jetzt so eine Aussage.
Typische Komplexitätseigenschaften sind eben halt bei guten Verfahren O von N-Log-in.
Und bei einfachen Verfahren N-Quadrat. Und wir gucken uns jetzt
gleich zwei an, die genauso diese beiden Komplexitätsschuhe Stufen implementieren.
Und dieses mit der Ordnung, das ist ja was,
was wir schon letztes Mal so besprochen hatten,
als wir uns Listen angeguckt haben, wie kann ich denn bemessen,
wie ist der Aufwand für eine bestimmte Operation?
Die Operation war da immer suchen in einer Liste.
Da gab es irgendwie abhängig von der Größe der Liste,
also O von N oder mit einem Hash-Verfahren kriege ich es
auf O von 1 oder mit einem Baum auf O von Login.
Und hier ist es jetzt so ähnlich,
nur dass man es eben jetzt für Sortieren betrachtet und allgemein
ist diese Idee mit der Ordnung nochmal hier etwas dargestellt,
dass man F von N ist, der tatsächliche Aufwand,
den irgendein Verfahren benötigt
Und dann will man wissen, unter welcher Schranke bleibt das denn,
nämlich gehe ist sozusagen die Grenzfunktion,
dass das immer da drunter bleibt oder sagen wir fast immer darunter bleibt,
dass also diese Grenzfunktion, diese tatsächliche Funktion irgendwie dominiert.
Davon spricht man auch. Das heißt also,
dass ab einem bestimmten N 0 alle Funktionsergebnisse,
die hier geliefert werden,
von dieser tatsächlichen Aufwandsfunktion unterhalb einer dieser Grenzfunktion liegen,
dann multipliziert mit einem konstanten Faktor
Ja, das ist hier mal so ein bisschen veranschaulicht.
Also ich habe hier irgendeine Aufwandsfunktion geht von N und F von N
ist die tatsächliche Aufwandsfunktion. Geh von N ist eine Grenzfunktion.
Und die sei jetzt hier zum Beispiel auch von N Quadrat,
das ist O von N Log N, dann ist das tatsächlich so,
ab einem bestimmten Punkt liegen die Werte der Funktion F immer unter denen von Gay.
Das ist hier tatsächlich verdreht zum Skript, also aufpassen,
die Folien sind hier aktueller. Also es kann sein,
dass bis zu diesem Punkt F noch ein bisschen höheren Aufwand hat zu Anfang,
aber ab einem bestimmten Punkt gilt einfach,
dass egal was da noch kommt,
diese G-Funktion, die F-Funktion dominiert.
Das heißt, ich kann eine klare Aussage treffen,
der Aufwand für, der hier von einer bestimmten Funktion benötigt wird,
unterhalb einer Grenzfunktion liegt.
Und da möchte ich natürlich immer eine möglichst niedrige Grenzfunktion benennen können.
Ich möchte also sagen, mein Algorithmus schafft das in O von N mal Login.
Ein schlechterer schafft es vielleicht in O von Endquadrat. Gut,
dann gibt es eben halt verschiedene Prinzipien, nachdem vergleichsbasierte Sortierverfahren laufen.
Und das ist einmal hier mit Vertauschen, dann einfügen,
stellt eine sortierte Reihenteilfolge her und fügt die verplanenden Elemente an
eine richtige Stelle ein,
Ausfall und so weiter
In allen Fällen ist beim Berechnen des Aufwandes für Sortierverfahren zentral wichtig,
der Vergleich zweier Elemente und es muss ein Element bewegt werden.
Irgendwie innerhalb der Sammlung oder außerhalb. Aber diesen Vergleich muss man ständig machen,
man muss ja immer mit einer Ordnungsrelation jetzt gucken,
welcher ist jetzt größer und welcher ist kleiner.
Gut, wir gucken uns mal einen Sortieralgorithmus an,
der wirklich sehr einfach ist. Und das ist der Sortieralgorithmus,
den ihr, wenn ihr irgendwann mal in die freie Wirtschaft geht,
in sämtlichen Bankenversicherungen und so weiter vorfinden werdet.
Der ist einfach über als vorzufinden, weil er sehr einfach
zu verstehen ist und auch einfach zu implementieren ist.
Und für die meisten Zwecke reicht er tatsächlich aus.
Der ist schlecht, das werden wir gleich sehen,
wie schlecht er ist, aber er ist einfach zu verstehen.
Und zwar geht es einfach so, dass ich beim Bubble Short
immer zwei benachbarte Elemente mir angucke und die in die richtige Reihenfolge bringe.
Also ich habe hier eine Folge von Zahlen, 55,
77, 78, 12 und 42.
Und jetzt ist der Algorithmus ganz einfach.
Ich gucke zuerst mir die 55 und die sieben an.
Und ich sehe, die sind nicht in der richtigen Reihenfolge.
Wenn ich das jetzt aufsteigend sortieren möchte, Also tausche ich die aus.
Dann betrachte ich als Nächste die 55 und die 78. See,
ja, passt, die sind in der richtigen Reihenfolge zueinander.
Dann gucke ich mir die 78 und die 12 an, stelle fest,
die 78 ist größer, also musste ich da jetzt austauschen.
Also wird dann die 78 darüber wandern. Zum Schluss
habe ich dann noch die 78 und die 42, die ich vergleichen muss.
Ich stelle fest, jo, muss ich auch wieder tauschen.
Und schon habe ich die 78 ganz nach rechts gebabbelt.
Also sozusagen durchgekaut wie so ein Kaugummi. Deswegen heißt es
auch Bubblesort, das ist eine Sichtweise,
warum das Ding Bubblesort heißt
Und dann habe ich den ersten Durchlauf gemacht. Das heißt,
ich bin jetzt einmal komplett durch die Liste durch und kann
jetzt sagen, das größte Element steht ganz rechts.
Das heißt, jetzt kann ich den zweiten Durchlauf starten und
muss aber jetzt natürlich eigentlich nicht mehr durch alle Elemente durchgehen.
Weil ich weiß ja, das größte steht jetzt rechts.
Also betrachte ich nur noch diese Teilfolge, die aus vier Elementen besteht.
Jetzt geht das wieder genauso los. Ich gucke mir zuerst
die 7 und die 55 an, passt.
55 und 12, passt nicht, muss ich tauschen.
Also kommt die 55 darüber, 55, 42,
vergleichen, passt nicht, muss ich tauschen, zwapp.
Schon habe ich die
An der richtigen Stelle stehen. Das heißt, die zweitgrößte
Zahl steht nach dem zweiten Durchlauf an der richtigen Stelle.
Jetzt kommt der dritte Durchlauf und ich muss wieder nur eine
kürzere Liste betrachten. 7 und 12, ja, passt,
dann 12 und 42, ja, passt, wunderbar.
Ich weiß, die 42 steht jetzt an der richtigen Stelle.
Also habe ich das hier,
die Situation und kann jetzt einen vierten Durchlauf starten,
betrachte nur noch diese ganz kurze Liste, stelle fest,
7 und 12 sind in der richtigen Reihenfolge und dann bin ich fertig.
Habe also hier tatsächlich jetzt einmal so eine relativ einfache Folge
sortiert.
Darum ist eine Frage
Wir haben dann fangen wir an, wenn man zwei Seiten taucht,
würde man dann nicht einfach einmal das nach vorne tauschen,
denn dann wird dann Vorgänger begleitet.
Und die mit dem Vertroffenen würde man nicht alles in einblutet,
auch eigentlich durchbeworben, muss man durchbekommen.
Die Frage habe ich leider nicht ganz richtig verstanden, glaube ich.
Fürchte ich. Also nochmal. Also was ich jetzt gemacht habe,
ich habe immer das größte Element der Teilfolge,
die ich betrachte, identifiziert.
Und dein Vorschlag ist welcher? Wenn man jetzt zum Beispiel,
die sieht man kein Problem hinter, dann macht es keinen Sinn.
Das heißt zum Beispiel bei der 78 und 12 im ersten Suchau
, wenn ich die getaucht habe.
Ja. Vielleicht dann einmal 12 mit dem Vorgänger,
vielleicht auch 12 Minuten
Das ist schon begleitet. Und das beginnt dem Kreis nochmal
für Tausch. Okay, also er schlägt vor,
ich muss es erstmal wiedergeben, jetzt auch für die Aufzeichnung.
Er schlägt vor, dass an dieser Stelle hier,
wo die 78 und die 12 verglichen werden,
dass da die 12 dann quasi gleich schon wieder,
wo sie ja so nach links wandert,
quasi dann auch noch weiter und wieder nach links verglichen werden könnte.
Hört sich jetzt, sagen wir,
ein bisschen wahllos an sozusagen an der Stelle,
wo ich jetzt feststelle, da wandert was kleineres runter,
das noch wieder runter zu sortieren.
Da ist die Formulierung des Algorithmus möglicherweise nicht ganz so einfach.
Ich glaube auch nicht, dass es wahnsinnig viel bringt,
aber es könnte vielleicht ein bisschen effizienter sein,
ich weiß es nicht
Aber der Algorithmus, wie er hier beschrieben ist, ist relativ einfach.
Hier ist eine Implementationsskizze. Wie könnte man das bauen?
Und hier ist tatsächlich jetzt einfach dargestellt, wie das
in einer emperativen Sprache laufen könnte, wie da war.
Ich mache also eine Schleife über verschiedene Durchläufe. Das ist
jetzt hier eins basiert. Also erster Durchlauf und dann N-minus eins,
weil ich muss ja nur bei N-Elementen,
am Ende habe ich ja eine zweielementige Liste noch gehabt,
muss ich nur betrachten.
Also ich muss sozusagen nur N-minus eins Durchläufe machen.
Und dann mache ich für jeden Durchlauf,
mache ich eine Schleife über die Position von 1 bis N-Durchlauf.
Also immer eine kürzere Liste
Und dann einfach ganz hier, ganz effizient,
falls das Listenelement an der Position größer ist als das Element
an Position plus 1, dann vertausche ich die beiden Elemente.
Und was seht ihr hier für eine Optimierungsmöglichkeit,
was jetzt hier schon drin ist, ist,
dass jetzt nicht jedes Mal durch die komplette Liste durchgegangen wird,
also dass immer durch eine immer kürzere Liste durchgegangen wird,
aber was gibt es noch für eine Optimierungsmöglichkeit,
wie mir ist auf der vorigen Folie was aufgefallen,
ja?
Ich habe eine Hautkarte, wenn ich keinen einzelnen Dank,
gar nichts mehr taucht, Ah.
Okay, er sagt das Richtige, also hier,
auf dieser Folie sieht man das.
Beim dritten Durchlauf habe ich festgestellt, sieben und zwölf
nicht vertauscht und dann zwölf von 42 nicht vertauscht.
Ich bin also in einem Durchlauf, habe ich einmal komplett nichts vertauscht.
Wenn ich mir merke, dass ich bei einem Durchlauf nichts vertauscht habe,
dann weiß ich, das Ding ist sortiert.
Dann kann ich aufhören. Das heißt,
der vierte Durchlauf war eigentlich gar nicht notwendig.
Das wäre also eine relativ einfache Optimierungsmöglichkeit.
Okay. Okay, okay, okay. Das ist also
relativ einfach zu implementieren. Das werdet ihr auch in
den Übungen implementieren
Der hat eine quadratische Komplexität. Das sieht man schon daran,
dass hier zwei Schleifen ineinander geschachtelt sind. Also das ist immer so,
wo man dann so eine Komplexität so auch sehen kann in einem Java-Programm,
wenn zwei Vorschleifen ineinander geschachtelt sind und beide laufen prinzipiell über alle N-Elemente,
prinzipiell im Schnitt ungefähr.
Dann weiß man, dass es irgendwie Endquadrat vom Aufwand.
Sehr einfach, aber quadratische Komplexität. Wir können mal uns hier
das Projekt angucken, was ihr in den Übungen auch ansehen werdet.
Das ist Relativ einfach. Auch wieder sieht ihr es wilder aus.
Was wir haben, ist, wir haben eine bestimmte Listentyp postuliert,
mit einem Interface, eine Inplace sortierbare Endliste.
Also wir sagen einfach, der Elementtyp der Listen ist Int,
um es einfach zu halten. Und die sind Inplace sortierbar.
Inplace heißt, man kann irgendwie Elemente tauschen innerhalb der Liste.
Deswegen sind die Schnittstelle von dieser Liste nämlich genau so aus.
Man kann sich die Länge geben lassen,
man kann ein Element einer halben Stunde Position sich geben lassen,
um die irgendwie vergleichen zu können.
Und man kann vertauschen, genau zwei Elemente in der Liste
Zwei verschiedenen Positionen. Und man kann fragen,
ob ein Element überall schon Positionen überhaupt existiert,
also ob das tatsächlich die Liste so groß ist.
Das ist relativ einfach. Und dann haben wir da
eine Implementation dazu gebaut, eine sogenannte Visual Intlist.
Und die Visual Intlist heißt Visual, weil sie das Ganze visualisiert.
Ich kann also hier mal so ein Exemplar erzeugen,
dann sieht das so aus. Das hier ist eine Visual
Intlist. Das ist eine Visualisierung der Zahlen von,
in diesem Fall jetzt,
wenn ich eine Visual Intlist mit Default-Konstrukt erzeuge,
hat die 100 Elemente,
100 Zellen
Und in diesen 100 Zellen sind jetzt die Zahlen von 1 bis 100
oder von 0 bis 99, weiß ich nicht ganz genau.
Und die Visualisierung sieht so aus, dass quasi auf der
X-Achse könnt ihr euch vorstellen, sind die Zellen abgetragen.
Und auf der Y-Achse ist der Wert abgetragen,
der in der Zelle drinsteht. Das heißt,
zum Beispiel hier in der ganz linken Zelle ist irgendwo ein Wert,
der hier so, was weiß ich, 40 ist ungefähr.
Und in der Zelle hier ist der größte Wert eingetragen,
also 99 oder 100 oder sowas.
Wenn das jetzt sortiert wäre, dann würden alle Punkte
auf einer diagonalen liegen
Das geht tatsächlich auch, kann man sich auch erzeugen lassen.
Ich kann jetzt hier sagen, initialisiere aufsteigend, dann sieht das so aus.
Dann sind tatsächlich auch diese Punkte dann grün visualisiert.
Also wenn ein Element an der richtigen Stelle steht in der Liste,
juhu, dann wird es als grün gekennzeichnet.
Okay, und wir machen das jetzt wieder mal hier initialisiere absteigend.
Und dann haben wir, Quatsch, absteigend gemischt,
also irgendwie hier gemischt. Also irgendwie wild durcheinander. Und
jetzt haben wir ein weiteres Interface postuliert, das heißt Sortierer,
und das ist nun wirklich super einfach,
definiert, nämlich eine Methode,
eine Operation sortiere und bekommt als Parameter
eine In-Place sortierbare Endliste
Also in diesem Fall kann ich jetzt mal,
das sind jetzt hier Implementationen, die ihr erst bauen sollt in den Übungen.
Also ihr solltet einen Bubble-Sortierer bauen und einen Quick-Sortierer.
Und ich nehme mal hier so einen Bubble-Sortierer. Und sage ich jetzt mal,
der Visual-Ind-Liste, ach nee, gar nicht umgekehrt,
ich sage dir den Bubble-Sortierer, sortiere doch mal,
eine Inplay-Sortierbare Endliste und übergebe die Referenz auf diese Visual-Ind-Liste.
So. Und jetzt sortiert der hier und jetzt sieht man hier
so richtig schön, wie ein Bubble-Sort visualisiert funktioniert.
Also man sieht hier so richtig schön,
ein Element nach dem anderen wird an die richtige Position gebubbelt.
Das ist jetzt, sagen wir, nicht besonders schnell. Das ist künstlich ausgebremst,
also diese Liste, die wir hier verwenden,
die hat eine Verzögerung drin, damit es einfach sichtbar ist,
aber ihr werdet gleich sehen, wenn ich das vergleiche mit dem Quicksort,
der hat dann die gleiche Liste mit der gleichen Beschränkung und dann geht es deutlich schneller.
Also hier sieht man, so ein Quicksort ist,
also ein Bubble Sort, der macht es, ja,
also er ist solide, aber ist eine ziemliche Gurke.
Gut, Vorteile geringer Speicherbedarf, bei Machts Inplace,
er vertauscht innerhalb des Speichers und er ist potenziell stabil,
also wenn man da so macht wie hier,
wo er gefragt wird, ist das Element größer?
Echt größer als das andere? Nur dann wird vertauscht,
dann heißt das, wenn Elemente gleich sind, werden sie nicht vertauscht.
Also der Bubble Sword würde auf Hund, Katze,
Maus angewendet. Tatsächlich liefern Hund, Maus, Katze.
Wenn es um die Länge der Zeichenkette geht.
Das würde also tatsächlich stabil sein. Gut,
einfach und für kleine Elemente wären ganz gut geeignet.
Also klein ist so ein paar hundert, paar tausend.
Okay. Jetzt gucken wir uns den Quick Sword an und
der ist cool. Das ist eine ziemlich spannende Geschichte.
Der gute Herr Horr, den wir ja auch schon mehrfach
hier in SA1 kennengelernt haben
Hat dem schon 1962 mal aufgeschrieben. Muss man sich vorstellen,
zu Zeiten, als es gerade mal so Sprachen wie,
ach, was gab es denn da?
Allgäu und PL1, Cobol, Liz gab es natürlich auch schon.
Vordran gab es auch schon. Also ganz alte Sprachen.
Und da hat er eben halt diesen Quickshot definitiv.
Das ist ein schneller Sortieralgorithmus. Er ist schneller als Bubble
Sword, werden wir gleich sehen. Er ist rekursiv definiert
. Gut, dass wir unsere Rekursion schon angeguckt haben.
Das werden wir dann gleich gebrauchen können. Er ist nicht
stabil. Also er sorgt nicht dafür, dass Elemente,
die gleich sind, auch in der Reihenfolge bleiben.
Wie ursprünglich
Und funktioniert nach dem Prinzip Teile und Herrsche. Das ist ein Konzept,
das es in der Algorithmik sehr ausgeprägt gibt,
devide and conquer, also so Teile und Herrsche dividiert,
Imperath, bla bla, immer das Gleiche.
Also ich zerlege ein Problem in kleinere Teilprobleme und irgendwann sind
die Teilprobleme so klein, dass sie trivial zu lösen sind.
Das heißt, die Grundidee ist für ein Quick-Sort.
Ich wähle einen prinzipiell erst mal beliebigen Schlüssel X aus der Schlüsselmenge aus.
Ich habe also eine zu sortierende Liste und nehme jetzt
den Schlüssel irgendeines Elementes und das wird mein Pivo-Element
Das wird einfach so genannt. Und jetzt baue ich zwei Folgen oder Reihen,
die in zwei Kategorien zerfallen, nämlich in die,
die bei denen der Schlüssel kleiner ist und bei denen der Schlüssel größer ist.
Oder größer gleich, je nachdem. Wo die gleich sind,
können wir beliebig aufgeteilt werden. Dann muss es so sein,
dass beide resultierende Teilreihen sich um mindestens ein Element kleiner sein
als die ursprüngliche Liste. Also es nützt mir nichts,
wenn ich jetzt zwei Teillisten erzeuge und die eine ist leer,
dann habe ich keinen Fortschritt geschafft in meinem Algorithmus.
Ich mache einfach eigentlich immer so, ich teile die Liste in
zwei Listen auf und idealerweise sind die Listen ungefähr gleich groß.
Und anhand des Bivo-Elements entscheide ich eben halt,
wer kommt in die eine, wer kommt in die andere Liste rein.
Und wenn ich dann meine Liste so aufgeteilt habe,
dann kann ich den ja rekursiv an, wenn den argumentieren,
dann habe ich einfach die Teilliste, mache ich wieder.
Nämlich irgendein Pivo-Element, spalte es auf in zwei Teillisten und
hier genauso. Und irgendwann sind die Listen so kurz,
dass es trivial ist und dann sind wir fertig.
Da muss ich nur noch sozusagen die gebildeten Teilketten wieder konkatinieren und habe dann eine sortierte Gesamtreihe.
Das ist hier mal so funktional beschrieben,
wie man ihn auch im Internet findet, so wie eine Beschreibung für Quicksart,
wie ihn auch der googelt, möglicherweise findet.
Also der Algorithmus ist so definiert, wenn die zu sortierende Folge nur ein Element enthält,
dann ist das Ding sortiert, trivialer Fall.
Und ansonsten wähle irgendeinen Schlüsselwert aus der Menge aus,
berechnen den nicht leere Teilfolge S1 aus S mit den Elementen,
deren Schlüsselwert kleiner gleich X ist und einen nicht leere Teilfolge S2 mit Elementen größer,
gleich X. Conker ist dann,
wenn du Quicksout rekursiv auf die beiden Teilfolgen an.
Und dann das Merch ist hier,
dass ich dann diese beiden Teilfolgen, die ich da wieder zurückbekomme,
die ich ja geliefert bekomme, die dann hintereinander hänge,
konkatiniere und dann habe ich sortiert.
So, das ist hier funktional. Also das heißt,
deshalb funktional, weil Quicksart ist eine mathematische Funktion angesehen wird.
Also im Sinne von Quicksart auf eine Teilfolge S1 liefert mir
eine Teilfolge S1-Strich. Das ist so im mathematischen Sinne.
Steck was rein, Argument und kriege was raus.
Und das ist in der funktionalen Programmierwelt ist das eine angenehme Definition.
In der imperativen Welt ist das ein bisschen anders.
Aber wir können es erst mal veranschaulichen, einfach mal an einem Beispiel.
Das sei die Zahnfolge. Ich wähle ein Schlüsselelement,
also als Pivolelement hier
Dann teile, also aufspalten in zwei Listen. Dann gehe ich
jetzt also tatsächlich einfach durch die Liste durch und sage,
sechs ist kleiner, also kommst du in die linke Liste,
drei ist kleiner, kommt in die linke Liste,
zehn ist größer, kommt in die rechte Liste und so weiter.
Und schon habe ich hier schwupps zwei Teile.
Hier wähle ich jetzt wieder, also herrsche rekursive Anwendung.
Hier genau, sie will ich als Pivo-Element, die sechs.
Dann sind die drei, die fünf und die eins kleiner und die acht ist größer.
Und hier habe ich jetzt nur noch die zehn und die zwölf,
zwölf ist größer, es gibt kein kleineres.
Hier habe ich wieder das Pivo-Element, fünf ist größer,
drei ist kleiner. Also wenn das dann rekursiv angewendet ist,
hat man hier sozusagen quasi so einen Baum erzeugt.
Jetzt muss ich quasi diesen Baum nur noch geeignet wieder traversieren.
In diesem Fall würde man einen tiefen Durchlauf machen,
also irgendwie immer zuerst mal runterlaufen, dann eins,
drei, fünf, sechs, acht, neun, zehn.
Also würde sozusagen genau die sortierte Folge rauskommen.
Das ist erst mal nur, um die Idee zu veranschaulichen.
Vergesst diese funktionale Darstellung, zumindest für die Übung.
Den Übungen werdet ihr das anders machen, da werdet ihr Inplace sortieren.
Diese funktionale Sichtweise sagt irgendwie, ich liefere jeweils neue Listen,
Speicherplatz spielt keine Rolle,
In der imperativen Konkretisierung ist es ein bisschen anders.
Also wenn wir das imperativ uns überlegen, wenn wir einfach sagen,
wir haben irgendwie ein Array oder eine Liste und da sind Zellen
und ich muss jetzt innerhalb dieser Zellen den Quicksaw anwenden,
dann ist das ein bisschen anders beschrieben.
Und hier tatsächlich Inplace on Imperativ. Also es wird nicht
jedes Mal eine neue Sequenz geliefert, sondern man arbeitet vor Ort
tatsächlich auf einer eigenen Liste und kann den Zustand der Liste verändern.
Und das ist dann das Prinzip aber wieder gleich.
Ich wähle ein Element der Folge als Pivo-Element
und setze dann den Index I auf das erste und J
auf das letzte Element der Folge
Eine Folge, dann J und J. Und dann wiederhole ich hier,
solange I keiner gleich J ist,
also solange die beiden in die CSI und J nicht aneinander vorbeigelaufen sind,
also wenn die anderen vorbeigelaufen sind,
dann habe ich sozusagen einen Teilschritt erreicht.
Solange ich das mache, dann erhöhe ich den Index,
bis ich ein Element finde,
dass von dieser Seite suche ich ein Element,
das größer ist als das Pi-Volement, von dieser eins,
das kleiner ist.
So, und dann habe ich diese beiden Elemente irgendwie gefunden,
dann tausche ich die aus und mache von da weiter.
So, bis dann irgendwie der Index vorbeigelaufen ist.
Und das, dann habe ich irgendwann sozusagen zwei Teillisten,
wenn die Indizisten dann vorbeigelaufen sind und dann kann ich
da wieder rekursiv den Quicks auch drauf anwenden
Das ist tatsächlich, das ist eine sehr prägnante Beschreibung von Quicksort,
die ihr direkt umsetzen könnt in ein Java-Programm.
Wird euch nicht so leicht fallen,
haben wir die Erfahrung gemacht die letzten Jahre immer.
Selbst obwohl das hier so ausführlich und klar beschrieben ist,
also wenn ihr googelt nach Quicksort, dann kriegt ihr
unglaublich viele Lösungen angeboten im Internet und unglaublich viele falsche.
Das ist wirklich super. Bei dem könnt ihr
euch darauf verlassen, dass der wirklich funktioniert.
Ich habe es hier nochmal veranschaulicht, also nochmal dieses Inplace sortieren,
dass ihr das nochmal gesehen habt
Also hier die gleiche Folge wie eben.
Ja, wähle das Pivo-Element jetzt wieder die neuen.
Also ich sage ja einfach, mein Pivo-Element sei die neun.
Dann setze ich diese Indizes, die auf dem Algorithmus,
auf der Vorfolie benannt wurden, also I und J. Den
einen am I ganz am Anfang und J ganz am Ende.
Jetzt lasse ich diese beiden Indizes durchlaufen, bis zu jeweils
bis zu einer Stelle einmal das Ibis zu einem Punkt,
wo ein Element größer ist als das Pivo-Element.
Also Indizes werden nicht verschoben tatsächlich
Und jetzt, weil ich habe das J,
also anders gesagt, das I zeigt auf die Position 9,
der Indexier zeigt auch Positionen auf da,
wo die 9 steht und 9 ist, größer gleich dem Pivo-Element.
Und 3 ist auch kleiner gleich dem Pivo-Element, also bleiben die beide stehen.
Und jetzt sind die noch nicht aneinander vorbeigelaufen, die Indizien,
also vertausche ich jetzt die 3 und die 9.
Man sieht hier, dass jetzt tatsächlich die 3 und die 9
über ziemlich große Distanz bewegt werden. Das ist einer der Gründe,
warum der Quicksort so viel schneller ist als der Bubble Sort,
weil der größere Distanzen zurückgelegt werden.
So, und jetzt und versetze I und J jeweils um eine Position.
Da steht in dem Algorithmus auf der Vorvorfolie wirklich drin,
auf jeden Fall I und J eine Position weitersetzen. Also hier,
I und J werden jetzt da, jetzt suche ich wieder,
I ist jetzt hier und jetzt suche ich wieder und mein Pivot-Element ist immer noch die Neuen.
Die Fünf ist kleiner, die Eins ist kleiner, die Zwölf nicht. Hier haben wir die Sechs,
die ist nicht größer, also kann ich da gleich stehen bleiben,
verschiebe Indizis, nur I wird verschoben,
also I wird genau bis zur Zwölf verschoben und dann habe ich also zwölf und sechs und die werden jetzt als nächstes,
die sind noch nicht aneinander vorbeigelaufen,
also das ist immer die Bedingung,
die Abbruchbedingungen,
I und J sind noch nicht vorbei
Gelaufen. Also habe ich hier die sechs um die zwölf
und tausche die aus. Und dann auf jeden Fall wieder
einen weitersetzen. Jetzt stehen die schon ziemlich dicht beieinander,
hier und J. Jetzt mache ich das tatsächlich,
dass ich die Indizes, jetzt habe ich wieder,
ich gucke, ist das hier kleiner?
Ja, ist es. Also gehe ich mit I noch einen weiter,
dann stehe ich auf der zehn, ist größer als das Pivo-Element.
Genauso J ist größer als das Pivo-Element, also bewege ich
es einen weiter. Also da sind Sie tatsächlich aneinander vorbeibewegt.
Jetzt sind also die Indizes aneinander vorbeigelaufen und jetzt habe ich
zwei Teillisten und in dieser Teilliste gilt, dass alle Elemente kleiner,
gleich neun drinstehen und hier stehen alle Größer gleich,
neun, also dem Pivo-Element.
Ja, und jetzt kann ich hier einfach wieder auf diese Teillisten den Quickshort rekursiv anwenden.
Ganz einfach, ja. Wie läuft die Leute jetzt auf der rechten Seite,
nicht auf der linken Seite, wenn die linke Seite so auf,
kleiner Fleisch ist, dann bleibt es ja neun.
Ja, ganz einfach,
weil das sich hier durch den Algorithmus so ergeben hat,
also die Neuen ist an dieser Stelle, wurde gefragt,
die Prüfung war immer größer gleich.
Ja, also kleiner gleich bzw. größer gleich.
Auf der Vorfolie kann man das auch sehen, 28,
ja, Höheindexi bis zum nächsten Element mit AI
größer,
So, und entsprechend hier auf der Folie war es dann
so, hier ist das i stehen geblieben.
Das i ist tatsächlich hier stehen geblieben.
Und dann wurde die 9 einfach vertauscht. So,
und dann ist die 9 einfach da rechts gelandet.
Das kann auch mal passieren, dass die Zahl,
dass die entsprechend, wenn die woanders steht,
wenn die erst dasteht, dann nach links wandert.
Das ist also wurscht. Das ist eigentlich egal für den Algorithmus,
wo die Elemente landen, die gleich im Pivo-Element sind.
Ja, da ist eine Frage. Element,
jetzt mal ein Überleben, das Pivot,
oder eine Banane zuschnitt
Okay, er fragt,
nimmt man da jetzt ein Pivo-Element oder nimmt man den Durchschnittswert?
Das ist eine gute Frage, weil die Wahl des Pivo-Elements,
die hat einen ganz großen Einfluss darauf, wie effizient der Quicksort ist.
Und das zeige ich jetzt mal. Also ich habe jetzt hier nochmal
den Quicksort. Ich mache hier mal so einen Quicksortierer.
Und lasse den jetzt mal, diese Visual-Int-Liste sortieren.
Also ich mache das hier nochmal, Initialisiere gemischt.
Und ihr habt eben gesehen, wie langsam das war.
Wenn ich jetzt dem Quicksortierer sage, sortiere doch mal
diese Visual-Int-List,
dann ist das
deutlich schneller
Und das ist also tatsächlich so schnell, dass ich jetzt hier
auch eine Schliste machen kann, die ein bisschen größer ist.
Also sagen wir mal hier 200 Elemente,
dann sieht das noch ein bisschen reizvoller aus.
Dann kann man den Quicksilver besser dann zugucken, wie er das macht.
Also sortier doch mal hier die Visual End Liste 2,
diese hier. Und da sieht man so richtig,
wie der sich da so rekursiv reinfaltet in diese Liste.
Also es ist schon hübsch anzusehen. Das Gute ist an dieser Visualisierung,
ihr seht sofort, wenn er nicht richtig sortiert.
Hier sieht man jetzt, dass er relativ schnell sortiert.
Was ich tatsächlich mache, und das ist das,
deswegen komme ich auf die Frage jetzt auch, gehe ich jetzt ein,
Die Wahl des Pivo-Elements hat ziemlich einen Einfluss auf die Effizienz.
Idealerweise soll das Element gewählt werden,
das bei einer sortierten Liste in der Mitte stünde.
Nun weiß man ja, bei einer Liste,
die unsortiert ist, nicht welches Element jetzt der Median wäre.
Könnte man suchen, aber dieses Suchen des Medians ist natürlich auch auf Watt.
Und dann werdet man wieder linearen, wäre man bei einem linearen Zusatzaufwand.
Nicht so eine gute Idee. Deswegen ist es häufig so,
dass irgendein Element genommen wird. Bei den Funktionalen Lösungen ist
es oft so, es wird das erste Element genommen.
Es wird einfach das erste Element Weil da kann man direkt auf zugreifen oft.
Bei den funktionalen Lösungen ist eine Liste irgendwie so definiert,
dass man das Erste auf den Kopf einer Liste ganz schnell zugreifen kann.
Das kann aber zu Problemen führen. Und das kann ich mal
eben zeigen. Ich kann hier bei dieser Visual Intlist,
dieser hier, initialisiere absteigend. Kann ich die mal hier
zeigen? Diese Visual Intlist ist jetzt absteigend sortiert.
Also das ist genau sozusagen das größte Element ist in der
kleinsten Position und so weiter. Und wenn ich jetzt,
ich muss aber den Quicksortiere erst noch eben ein bisschen kaputt machen,
dieser Quicksortierer,
der macht Folgendes,
der nimmt tatsächlich das
räumlich mittlere Element
Also hier, er berechnet als Position für das Pivo-Element.
Liste gibt Mitte, also das ist der Index hier Mitte,
der gewählt wird. Also bei der Liste räumlich das mittlere Element.
Das ist jetzt einfach eine willkürliche Wahl. Man könnte auch
einfach sagen, Liste gib I. Das könnte ich hier
so mal einfach machen. Dann nehme ich das erste Element. Und wenn ich das jetzt mache,
dann ist das irgendwie ganz lustig zu sehen,
ich mache hier mal eine Visual Endlist,
erzeugt mir einen Quicksratierer, der jetzt das erste Element nimmt,
dann nehme ich jetzt mal, also der ist jetzt,
der ist jetzt schnell, wenn er das Ding hier sortiert,
wenn das durcheinander ist.
Da ist er wieder
so erwartungsgemäß schnell
Aber wenn ich jetzt die Visual-Int-Liste mal sortiere,
also könnt ihr ja aufsteigend sortiert sein,
aber man kann das besser sehen, wenn ich sie absteigend sortiere.
Und jetzt den Quick-Sortierer anwende und den nimmt ja immer das erste Element.
Und das erste Element ist dann ziemlich unglücklich,
weil dann die Liste nur um ein Element kleiner wird.
Da habe ich eine eine elementige Liste und der Rest ist dann der Rest der Liste.
Also wenn ich das jetzt sortiere hier, dann ist der ziemlich lahm.
Plötzlich ist der Quick-Sort genauso langsam, wie der da bis Ort.
Das heißt, wenn das Pivo-Element ungeschickt gewählt wird,
Und das ist eben halt bei einer sortierten Liste,
ist es ungeschickt, das erste Element zu nehmen.
Dann ist, gut wäre der Median, aber das heißt,
wenn wir tatsächlich den Quick-Satirer jetzt hier nochmal patchen,
wieder zurückpatchen und ich sage hier wirklich, ich nehme die räumliche Mitte.
Und bei einer sortierten Liste ist das Element,
was immer räumlich in der Mitte steht, ja auch,
dass der Median, dann wird das Ding richtig schnell.
Dann sieht man nochmal, kann man wirklich sehen,
wie das Ding richtig, richtig schnell ist,
dann ist es sozusagen optimal für den Quick-Sort.
Ja, ich kann das auch hier jetzt initialisiere mal absteigend,
so. Und dann nehme ich hier den Quick-Sortierer und lasse
den das sortieren
Sortiere mal diese Intliste und dann geht das flap.
Also richtig schnell. Und dann sieht man einfach auch,
dass dieser Quicksalt eben halt,
der ist so ein bisschen wie mit dem Hashing,
kann total schnell sein, aber er kann auch in bestimmten Fällen degenerieren,
wenn er ungeschickt implementiert ist.
Also er besitzt im durchschnittlichen Fall Uhr von N-Login.
Im schlechtesten Fall kann aber die Generieren zu Uhr von N-Quadrat.
Wenn die Implementierung zulässt, dann schneller Indexzugriff,
also wie wir es gerade gesehen haben,
ich kann auf das räumlich Mittlere zugreifen,
dann sollte man auch wirklich das räumlich Mittlere wählen,
dann kann er eigentlich nicht degenerieren.
Trotz dieser Eigenschaften gilt Quicksalt als einer der schnellsten
Gut, eine Folie muss ich hier noch mal eben zeigen.
Die Frage, ob sich diese Mühlen überhaupt lohnt.
Wir haben jetzt gerade gesehen,
Sortierverfahren, also wenn ihr so ein Quickshort baut,
der ist schon ein bisschen schwieriger zu bauen als ein Bubble Sword.
Also in der Erstellung und dann auch in Erwartung.
Warum nicht also einfach immer mit Bubble Sound sortieren?
Also wir gucken mal diese beiden Komplexitätsklassen an,
wovon er ein Quadrat und N-MyLogin. Das hört sich so
harmlos an. Das eine ist quadratisches anderes N-MyLogin.
Wie viel bedeutet das denn jetzt in der Praxis? Okay,
nehmen wir mal an, wir haben so einen jemanden,
der im Klimarechenzentrum sitzt und macht jetzt irgendwie,
muss mit sehr großen Datenmengen umgehen
Also Geophysiker hat da irgendwie zehn Millionen zahlen wert.
Und die muss man sortieren. Zehn Millionen Zahlenwerte. Ist
jetzt für heutige Big Data-Szenarien, ist das ein Witz.
Ja, ist wenig. Und wir nehmen an, er hat einen Rechner,
der zehn hoch acht elementare Schritte pro Sekunde aushören kann.
Das ist schon ein ziemlicher Bolide, der kann schon ein bisschen was.
Wenn wir das jetzt mit quadratischem Aufwand machen würden.
Nehmen wir mal an, er nimmt jetzt einen Bubble Sword,
startet den Sortieralgorithmus und geht einen Kaffee trinken, den so,
desertiert jetzt, kann ich vielleicht nur eine Viertelstunde wiederkommen.
Jetzt kommt er zurück und der Rechner ist noch nicht fertig
Und der wird auch so schnell nicht fertig, weil,
wovon Endquadrat heißt, 10 Millionen zum Quadrat,
das ist 10 hoch 7 mal 10 hoch 7. Das ist 10 hoch 14.
Durch 10 hoch 8, wenn wir sagen 10
hoch 8 Operationen, werden pro Sekunde bearbeitet.
Das heißt, das sind 10 hoch sechs Sekunden.
Und 10 hoch sechs Sekunden sind ungefähr elf Tage.
Elf Tage hört sich jetzt, okay,
es ist ja eine wichtige Berechnung. Wir wissen ja,
diese Klimarechenzentrumsleute, die brauchen ja unheimlich Rechenpower.
Deswegen haben die ja so riesige Dinger da stehen.
Die machen das dann eben ein bisschen schneller.
Aber elf Tage ist schon scheiße.
Der Vergleich, N mal Login, ja,
da ist das 10 Uhr siebenmal Logarithmus-Duales von 10 hoch 7.
Und das ist ungefähr 24. Das ist also 10 Uhr
siebenmal 24, durch 10 hoch 8 ist 2,4 Sekunden.
Und ich finde so, der Unterschied zwischen 2,4 Sekunden und
11 Tagen, der würde mich doch irgendwie ärgern.
Da könnte ich wahrscheinlich keinen Kaffee trinken gehen. Also es ist,
ja, es ist gewaltig. Bei großen Mengen ist es
auf jeden Fall wichtig, dass man den vernünftigen Sortieralgorithmus nimmt.
Gut, Sortieren gibt es auch mit linearer Ordnung.
Ganz wichtig, es gibt tatsächlich Spezialfälle, da kann man
die Position in der sortierten Liste direkt berechnen aus dem Schlüssel.
Kann funktionieren. Es gibt also da randraffinierte Verfahren,
Bucket Sword zum Beispiel, könnt ihr euch nochmal angucken.
Das ist tatsächlich ein Verfahren, das ist linear in N.
Es sortiert tatsächlich linear. Nicht en mal Login, nicht
End Quadrat, sondern M. Das ist dann natürlich cool,
aber das ist eben halt oft ein Spezialfall, der fast nie greift.
Dann gibt es noch reichlich mehr Sortierverfahren, also da sind die,
wenn die Informatiker sich wieder ausgetobt über die letzten 40 Jahre,
Bubble Sword, Chaker, Search,
Election Short und so weiter.
Toll, toll, toll. Das sind immer
so Angaben, die sich auf die Durchschnittskomplexität beziehen
Quick Sound steht hier so mittendrin, äh,
ist aber eigentlich schon einer der Besten, so.
Die ist da für vergleichsbasierte gibt. Bucket Sword, wie gesagt,
Ohr von N. Gut, dann noch kurz eine Storesi-Frage,
dann sind wir auch für heute durch.
Ja. Welche der folgenden Aussagen ist richtig? Erste Aussage
, Quick Sword ist ein stabiles Sortierverfahren. Zweite Aussage,
es gibt Sortierverfahren, die schneller sind als Ohr von N-Log-N. Oder C
, ein Sortierverfahren heißt stabil, wenn es auch eine Elementmenge sortiert,
in der einige Elemente den gleichen Sortierschlüssel haben
Oder D, Quickshout kann auch bei idealer Wahl des Pivo-Elements zu Ofern-Endquadrat degenerieren.
Gebt eure Stimme ab. Sobald wir über 100 sind,
können wir dann auch das hier mal beenden.
Die Zusammenfassung steht in den Folien, kommt gleich zum Schluss
noch, aber bildet euch mal eben eine Meinung.
Typische Klausurfrage
Kommt. 100 Leute kriegen wir da zusammen. Ja.
Wunderbar. Ich halte es mal an. Ich gucke mal
, wie das hier verteilt ist. Ihr seht,
das ist schon nicht ganz so eindeutig, das Meinungsbild.
Also die Hälfte ungefähr dafür, dass es zur Tierverfahren gibt,
die schneller sind. Aber immerhin ein Viertel ist auch dafür,
dass es Quicksart degenerieren kann. Bei idealer Wahl des Pivo-Elements
. Richtig ist natürlich das hier. Quicksart bei idealer Wahl
des Pivo-Elements gibt die Geräte nicht. Der degeneriert bei ungeschickter
Wahl als Pivo-Elements. Und das mit dem Stabilen ist,
habt ihr ja auch schon gesehen
Gut, das war's für heute.
Dann vielen Dank.
Viel Spaß