Wow. Alle wach. Hallo, hallo, hallo.
Herzlich willkommen zur elften Vorlesung SE1. Ich hoffe,
ihr seid alle gut in das neue Jahr reingerutscht.
Diesmal ganz ohne Eis und Schnee. Das ist ja auch mal ganz nett.
Ja, es ist immer noch Level 4. Das heißt,
wir schauen uns ein bisschen an wie Sammlungen gebaut werden.
In der letzten Woche haben wir Arrays kennengelernt,
also in der letzten Vorlesungswoche, das war vor Weihnachten,
falls ihr euch noch dunkel an das letzte Jahr erinnert.
Und wir haben auf Level 3 haben wir uns erstmal nur angesehen,
dass es ja sowas gibt wie Sammlungen und dass man diese Sammlungen sehr,
sehr gewinnbringend einsetzen kann, dass man da sehr viele Sachen mitmachen kann,
die sehr praktisch sind.
Also Mengen und Listenmengen, keine Duplikate, Listen,
Reihenfolgebenutzer definiert und so weiter. Also unterschiedliche Organisationsprinzipien. Und
was wir uns jetzt in den nächsten zwei Wochen intensiv und in
der Gründer mit der 13. Woche auch noch ansehen werden,
Das sind so Implementationen für solche Sammlungen.
Das heißt, wir werden uns jetzt auf die andere Seite begeben.
Bisher habt ihr nur gewusst, es gibt Sammlungen,
Interfaces im Jarba Collections Framework. Es gibt Klassen,
die diese Interfaces implementieren und die könnt ihr wunderbar benutzen.
Sowas wie Linklist oder Arraylist oder Hash-Set oder so weiter.
Das war sozusagen die Klienten-Sicht. Da seid ihr diejenigen gewesen,
die das Zeug benutzt haben. Jetzt wollen wir über die Sachen reden,
die ihr wissen müsst, wenn ihr solche Dinger selbst bauen wollt.
Das heißt, ihr werdet jetzt hier Dinge lernen,
wie man Solche Sammlungen, ja, implementieren kann.
Das machen wir aber nicht, damit ihr jetzt losgeht und
nächste Woche das nächste Javac Lections Framework schreibt.
Das ist ja schon da und das ist auch gar nicht
schlecht, was da ist. Darum geht es nicht,
sondern wir wollen tatsächlich jetzt mit dem Blick hinter die Kulissen von Sammlungen,
eigentlich nur das, was wir bisher gemacht haben, wiederholen.
Das heißt, es ist eine gute Möglichkeit bei solchen Implementationen,
von solchen Datenstrukturen, wie wir sie uns jetzt ansehen werden,
nochmal vieles zu wiederholen, um zu üben, was wir bisher gemacht haben.
Also Referenzen auch Arrays
Den Umgang mit diesen ganzen elementaren Dingen. Was da jetzt aufkommt als Begriff hier,
ist auch auf der Folie zu sehen, dynamische Datenstruktur.
Das ist so ein klassischer Begriff in der Informatik.
Das ist ein richtiges Teilgebiet, könnte man sagen.
Und da gibt es auch ganz viele Bücher dazu.
Es gibt dann so Algorithmen und Datenstrukturen,
üblicherweise so eine Vorlesung an vielen Universitäten im zweiten Semester.
Wir machen das hier so ein bisschen anders.
Habe ich ja schon ein paar Mal versucht zu erklären.
In SE1 machen wir viele Inhalte,
die klassisch auch in Erstsemesterveranstaltungen gemacht werden
Eben halt weniger Sprachumfang. Also wir reden zum Beispiel nicht über Vererbungen,
nicht über Exceptions und so weiter, ganz bewusst.
Der Platz, der dadurch entsteht, im ersten Semester,
weil wir machen ja hier kein langsames Tempo.
Den nutzen wir dann, um Themen anzusprechen,
die an anderen Unis eben halt üblicherweise so im zweiten Semester behandelt werden.
Algorithmen und Datenstrukturen machen wir jetzt hier so relativ elementar.
Wie gesagt, das machen wir nicht, damit das irgendwie,
damit wir irgendwelche Inhalte von anderen Veranstaltungen vorwegnehmen.
Es gibt nämlich auch tatsächlich Algorithmen und Datenstrukturen eine Vorlesung hier in Hamburg an der Uni,
an der Informatik, um es ganz genau zu sagen.
Aber Das ist, glaube ich, trotzdem ganz gut,
dass man hier jetzt schon mal, sagen wir so,
die grundlegenden Konzepte davon mal gesehen hat.
Ich denke, das ist ganz gut,
aber bisher haben noch wenig Leute sich beschwert,
dass wir das hier im ersten Semester gemacht haben.
Gut, also dynamische Datenstruktur,
jetzt muss ich erst mal ein bisschen erklären,
was heißt denn dynamisch und was heißt Datenstruktur?
Und das werde ich jetzt mal versuchen. Erstmal nochmal zur Wiederholung,
hatte ich eben schon eingangs kurz gesagt, im Java Collections Framework.
Das ist das, was ihr überwiegend benutzen sollt.
Da sind die Dinge, die ihr kennen solltet.
Die Interfaces solltet ihr sehr gut kennen.
Die Implementation solltet ihr zumindest wissen, dass sie da sind
Interfaces, List on Set und so weiter. Und dann eben halt
die passenden Implementationen dazu. Was für alle diese Implementationen gilt,
ist, dass sie auf zwei ganz elementaren Strukturen basieren.
Und das ist wirklich, man kann die alle letztendlich darauf zurückführen.
Das ist das, was hier genannt ist. Einmal Arace
und einmal verkettete Strukturen. Und Verkettung findet statt über,
hat da Referenzen. Also falls ich bisher nicht gesagt habe,
dass Referenzen wichtig sind, dann fürchte ich,
dann muss ich es jetzt nochmal sagen.
Aber ich glaube, ich habe das schon ein paar Mal gesagt.
Also diese Verkettung von irgendwie Objekten, Das ist etwas,
was über Referenzen in Java passiert. Das ist ein absolutes
Grundkonzept von solchen dynamischen Datenstrukturen. Tatsächlich ist es so,
dass die Implementationen, die im Java-Collections-Framework existieren,
also Linklist, Arraylist, Hashset, Treeset,
dass die von den Prinzipien genau das machen,
was wir jetzt hier beschreiben werden.
Die sind ein bisschen, sagen wir, allgemeiner,
weil wir gucken uns das jetzt hier an in diesen zwei Wochen für relativ festgeschriebene Elementtypen.
Also nicht generisch, aber Großen und Ganzen ist das genau das,
was im Collections Framework an Implementationen da ist.
Das werden wir von den Konzepten her jetzt behandeln.
Also es ist auch dann nützlich, sich hier damit zu beschäftigen,
wenn man einfach weiß, was diese Implementationen so leisten.
Wir werden uns nämlich auch ansehen, einfach für euch auch
wieder aus Klientensicht, wie unterschiedlich leistungsfähig die Implementationen sind.
Und das ist ganz spannend, dass man dann eigentlich sehen kann,
es gibt verschiedene Möglichkeiten, ein Interface zu implementieren.
Das ist ein Konzept, was wir hier relativ früh eingeführt haben.
Und häufig ist es so, dass es nicht die beste Implementation gibt, sondern mehrere Implementationen.
Und je nachdem, für welchen Kontext man die Implementation braucht,
Ist mal die eine, mal die andere besser geeignet.
Und auch das will ich hier versuchen,
mal sogar ganz grundsätzlich mal einfach die Idee zu vermitteln.
Gut, und diese Implementation aus dem Java Collections Framework,
sind jetzt endlich welche, die so auf diesen vier Grundkonzepten basieren,
also verkettete Listen, wachsende Arrays, balancierte Bäume, Hash-Verfahren.
Und das sind mehr oder weniger genau die Themen,
die wir in den nächsten zwei Wochen behandeln werden.
Heute verkettete Listen und wachsende Arrays. Nächste Woche balancierte Bäume und Hash-Verfahren.
Und das sind eben halt so richtig klassische Beispiele für dynamische Datenstrukturen.
Während Listen eigentlich, sowas sind wie abstrakte Datentypen. Also das sind Dinge,
mit denen kann man umgehen, die haben einen Umgang,
also diese Konzepte von Duplikate zugelassen oder nicht,
Ordnung irgendwie manipulierbar, das gehört sozusagen zum Umgang.
Und das hier, jetzt geht es wirklich darum, wie ist denn sowas?
Wie kann man sowas denn implementieren? Also dynamische Datenschulturen haben
den Begriff einfach mal so ein bisschen zu beleuchten,
bezeichnen die Organisationsformen von veränderbaren Sammlungen von Objekten ganz allgemein gesprochen.
Und mit veränderbar ist ja auch gemeint, dynamisch,
dynamisch heißt tatsächlich, das kennt ihr, die Sammlungen,
die ihr bisher benutzt habt,
Set und List,
die konnten
beliebig viele Elemente aufnehmen
Das ist extrem bequem, wenn man sich nicht darum kümmern muss,
wie viel man da so reinstopfen wird.
Man muss nicht vorher wissen, wie viele Elemente man in so eine Sammlung einführt.
Man macht immer da rein und das passt dann auch alles wunderbar.
Aber das ist natürlich eine Leistung, die irgendwie erst mal gebaut werden muss.
Also erst mal muss jemand sich die Mühe gemacht haben,
so eine bequeme Datenstruktur anzubieten. Also hier ändern bedeutet üblicherweise hinzufügen,
modifizieren und löschen von Elementen und auch ändern von Beziehungen zwischen diesen Elementen.
Die sind meist rekursiv aufgebaut. Wir haben Rekursionen schon kennengelernt,
als Konzept hier dass Methoden sich selbst aufrufen können.
Dass es dann sozusagen funktionale Rekursion, was wir jetzt hier
uns ansehen werden, ist dann strukturelle Rekursion.
Das ist zwar sehr ähnlich, aber es ist tatsächlich nicht
das Gleiche. Gut. Und dynamisch eben halt Datenstrukturen,
wenn man beliebig Elemente hinzufügen kann oder entfernen kann.
Und ja, es gibt dann so verschiedene Ausprägungen.
Und hier ist einfach nochmal so ein kleiner Überblick über das,
was in den nächsten Wochen kommt.
Also heute gucken wir uns hier Listen an. Erstmal so,
wie man die denn so bauen kann, zwei verschiedene Art und Weisen.
Dann gucken wir uns an, wie man Mengen implementieren kann,
also insbesondere so Die Frage, ob ein Element schon enthalten ist.
Das ist ja sehr zentral bei Mengen.
Da gucken wir uns unter anderem auch eine Baumrealisierung an.
Für Mengen gucken wir uns auch an, wie man es
mit Hash-Verfahren machen kann. Sehr, sehr faszinierende, dynamische Datenstruktur.
Und in der letzten Woche so als Bonbon für diejenigen,
die noch richtig Interesse haben,
gucken wir uns auch noch mal Grafen an.
Also allgemeine Grafen. Bei Bäumen ist es ja so,
dass die üblicherweise gerichtet sind. Und bei Grafen,
da gibt es dann auch Möglichkeiten, dass sie zyklisch sind und so weiter.
Ja, gut. Also heute, jetzt hier
Fokus auf Listen.
Listen implementieren
Zwei Implementationsformen. Eine basierend auf Verkettung. Also
dieses Prinzip, das Überreferenzen, Objekte miteinander verkettet sind.
Objektgeflechte. Und eine zweite mit Arace. Das Schöne ist,
wenn wir, wenn wir das hier erstmal können,
dann können wir auch relativ leicht andere nützliche Datenstrukturen bauen.
Also zum Beispiel Stacks und Cues, die kommen auch noch in zwei Wochen dran.
Aber auch tatsächlich Hash-Verfahren basieren intern wieder so auf letztendlich kleineren Listen.
Also Listen sind tatsächlich sehr elementar und man sollte gut mit ihnen umgehen können.
Und was heißt jetzt gut mit einer Liste umgehen?
Man muss jetzt nochmal sich, Ich betone das immer wieder gerne.
Wir sagen ja, es gibt zwei Seiten von jedem Typ oder jeder Klasse.
Nämlich die Klientensicht, also der Umgang, da benutzt man das Ding.
Und es gibt dann die Art und Weise,
wie es implementiert ist. Wir gucken uns nochmal an,
wie der Umgang mit einer Liste ist.
Also einfach nochmal, um das in Erinnerung zu rufen.
Es ist wahrscheinlich für eigentlich auch inzwischen langweilig,
aber es ist immer wieder gut, das nochmal deutlich zu sagen.
Also erste Eigenschaften der Liste kann beliebig viele Elemente enthalten.
Das ist sozusagen unsere Forderung an Listen. Über den Index
kann direkt auf beliebige Positionen in der Liste zugegriffen werden.
Es gibt also einen Index, einen Index basierten Zugriff.
Das ist etwas, was elementar zu Listen dazugehört
Das Einfügen eines Elementes erhöht den Index der nachfolgenden Elemente,
ihr erinnert euch vielleicht noch daran,
als ich diese Listen eingeführt habe mit dieser Pausenaufsicht,
da hat man irgendwo ein Element eingefügt,
also ich wurden die Elemente danach in ihrem Index tatsächlich erhöht.
Genauso, wenn ich was entferne, rutscht alles wieder ran,
das ist sozusagen alles so, das ist so unser logisches
Verständnis von den Elementen in der Sammlung oder in der Liste.
Und was man auch häufig braucht, ist die Frage,
ob ein gegebenes Element bereits in der Liste enthalten ist.
Haben wir mal so Testaufenthalten sein genannt. Das wiederhole ich jetzt,
glaube ich, schon zum zweiten Mal hier, wie man mit Listen umgeht,
das hört sich möglicherweise für eben euch ein bisschen langweilig an.
Ein bisschen theoretisch. Und damit das nicht ganz so theoretisch daherkommt,
habe ich mal hier iTunes dabei. Ihr kennt möglicherweise iTunes,
man kann es benutzen, man muss es nicht benutzen.
Ist eine Software, mit der man unter anderem Musikdateien verwalten kann.
Man kann auch Videos verwalten, alles Mögliche. Eigentlich ist
das relativ flexibel. Und was ich hier zeigen will,
ist, dass in so einer Software,
das kennt ihr möglicherweise auch von ähnlichen Programmen, Kann man sich Wiedergabelisten definieren?
So, das ist eine Liste. Eine Wiedergabeliste ist
ein Paradebeispiel für eine Liste. Also wie ich sie
gerade hier so beschrieben habe, allgemein so bla bla
bla. Das ist tatsächlich bei Wiedergabelisten genau das Gleiche.
Ich kann also jetzt hier in iTunes sagen,
ich brauche eine neue Wiedergabeliste, die nenne ich jetzt mal irgendwie SE1-Vorlesung,
da bin ich immer sehr kreativ.
So, und jetzt habe ich hier also eine neue Wiedergabeliste,
die ist erstmal leer. Genau, werden wir eine
neue Liste anlegen im Java Collections Friend Work.
Und jetzt kann ich hier sagen, ich möchte jetzt mal
Titel in diese Liste reinziehen.
Also ich plage hier jetzt, was nehmen wir denn mal?
Charlotte Sometimes, wunderbar, ziehe ich jetzt hier mal rein.
Füge ich also zur Liste dazu. Oder 1015,
dann ist Saturday Night, ein bisschen hier die Altmeister,
das Alternative, die guten Cures.
So, und dann vielleicht eine Love Cats, wunderbar,
kommt auch noch mit rein. So,
was ich jetzt gemacht habe, ich habe jetzt einfach Elemente,
nämlich Titel, irgendwelche Musikstücke in eine Wiedergabeliste eingeführt.
Die kann ich mir jetzt auch anzeigen lassen,
dann sieht man hier,
juhu,
die Dinger haben eine Reihenfolge
Genauso wie man das so haben will. Das heißt also,
ich kann jetzt hier auch sagen, naja,
vielleicht will ich Love Cats zuerst hören, dann stehe ich das nach oben.
Also genau diese Index-basierten Operationen. Ich kann jetzt irgendwie sagen,
das soll irgendwie nach oben und nach unten.
Und was man auch noch sehen kann, ist,
wenn ich jetzt zum Beispiel 1050,
und das hat dann halt wirklich ein grandioses Stück,
das ziehe ich da jetzt nochmal drauf.
Jetzt kommt hier was, was auch interessant ist.
Es werden Duplikate zur Wiedergabeliste hinzugefügt. Möchten Sie die Duplikate
hinzufügen oder überspringen? Das heißt, der Begriff ist genau,
wie wir ihn ja auch hier verwenden, etwas,
was schon drin ist, wird als ein Duplikat angesehen.
So, jetzt wird hier also gefragt,
willst du wirklich Duplikat in deiner Wiedergabelliste haben. Klar,
warum soll ich einen Song mehrfach in meiner Wiedergabeliste sein,
wenn ich ihn mir besonders gut finde?
Also sage ich jetzt hier hinzufügen. Und meine Wiedergabeliste sieht jetzt so aus,
das habe ich 1015 und das hat dann halt jetzt hier doppelt dran.
Ist natürlich jetzt ein bisschen dicht beieinander,
also schiebe ich das mal wieder nach oben.
So, also tatsächlich Reihenfolgebenutzern manipulierbar. Duplikate sind zugelassen,
wie man sieht. Es ist also eine echte Liste.
Und um sozusagen die Nähe zu diesem Anwendungsbeispiel ein bisschen beizubehalten,
haben wir Diese Woche in den Übungen ein Projekt,
in dem wir so ein bisschen unser eigenes iTunes bauen,
nämlich wir nennen das SA1-Tunes.
Und das sieht hier so aus, das ist einfach nur,
wir wollen Wiedergabelisten bauen. Also wir bauen eine sehr spezifische Listen jetzt.
Die Collection Framework Listen, die ihr kennt, sind generisch.
Das heißt, ihr könnt einen beliebigen Elementtyp reinstecken,
solange das ein Referenztyp ist. Hier ist es jetzt so,
wir haben jetzt hier sehr dedizierte Listen, nämlich Titellisten.
Wir sagen einfach, naja, so Musikstücke,
das sind so Titel, mehrere Titel und wir wollen Titellisten verwalten können.
Das heißt, wir wollen genau diese Wiedergabeliste aus iTunes
genauso jetzt hier mal selber bauen können.
So, dann ist das Erste, was wir uns angucken,
die Schnittstelle. Juhu, weil wir wissen ja,
die Schnittstelle ist das Entscheidende, die legt den Umgang fest.
Also gucke ich mir mal hier das Interface an.
Das ist hier auch in der Dokumentationsansicht. Also eine Schnittstelle für Titellisten,
eine Titellist, der enthält eine Reihe von Musiktiteln einer Bundeswehr definierten Reihenfolge.
Da die Reihenfolge von Titeln sehr wichtig ist,
stehen indexbezogene Operationen zur Verfügung. Duplikate sind zugelassen,
weil an der Titelliste derselbe Titel durchaus mehrfach gewünscht sein kann.
Habe ich alles schon erzählt. So,
und hier sind die Methoden, also jetzt mal auf
Java abgebildet
Ich kann also zum Beispiel einen Titel einfügen. Ihr seht hier,
es gibt irgendwie so einen Typtitel, gucken wir uns gleich mal an.
Das ist also ein Musiktitel und dann kann man hier
eine Position angeben, an der das eingefügt werden soll.
Dann kann ich mir die Länge der Liste geben lassen,
wie viele Musikstücke sind denn drin?
Bei iTunes zum Beispiel wird auch gerne angezeigt, wie lang
die Liste ist. Also hier zum Beispiel vier Titel,
wie viele sind drin, aber auch,
wie lang das ist, das wäre natürlich auch cool,
wenn wir das jetzt weiter ausbauen, unsere tolle Liste hier,
dann würden wir das vielleicht auch noch mit reinbauen.
Dann hier geht gibt einen bestimmten Titel an einer bestimmten Position
und kann also sagen, jetzt gib mir mal den Titel
an Position 3 und dann kriege ich den auch.
Liefert mir also einen Titel zurück. Ich kann die ganze Liste einfach lehren,
werde wegschmeißen. Ich kann auch Titel entfernen. Ja,
auch das geht natürlich in iTunes, brauche ich glaube ich jetzt nicht demonstrieren.
Und man kann auch prüfen, ob ein Titel enthalten ist.
Das war ja eben sogar notwendig, dass ich das prüfen kann. Ich muss ja,
also iTunes muss ja irgendwie sowas intern auch haben, damit,
wenn ich einen Titel reinschmeiße und der fragt mich, hey,
das ist schon drin, muss er ja vorher geprüft haben,
ob er drin ist.
Also irgendwie muss dieses Enthält auch implementiert werden.
Also wenn wir das hier selber bauen,
da sind wir schon
ziemlich gut dabei
Und wenn die das bauen könnt, dann habt ihr eine Menge gelernt. Tatsächlich,
als wir das hier angefangen haben mit SE1 vor,
jetzt weiß ich nicht, sieben oder acht Jahren,
da haben wir genau dieses Interface vorgegeben und haben gesagt,
so, in den Übungen,
baut mal eine Array-Titelliste und baut mal eine Link-Titelliste.
Also baut mal, ich habe nach der Vorlesung mit denen
so, jetzt könnt ihr das ja bauen.
Und das war tatsächlich ein Schock für uns, dass es doch relativ schwierig ist.
Also dass wir doch relativ, da steckt relativ viel drin.
Und deswegen haben wir über die Jahre dann hier immer mehr
gelernt darüber, wie viel wir vorgeben müssen.
Also da sind jetzt schon Klassen, die sind fast fertig.
Ihr müsst nur noch so ein paar Methoden implementieren.
Also das sind die Operationen hier, in dem Interface stehen die Operationen,
der Umgang, einfügen, enthält und so weiter.
Und hier drin sind jetzt unfertige Implementationen. Die sind insofern unfertig,
als tatsächlich hier die Tests, die wir dazugeliefert haben.
Wir haben also hier relativ dicke Testklassen,
die sind extra so ein bisschen dick hier dargestellt,
um deutlich zu machen, dass da wirklich ziemlich viele Testfälle drinstecken.
Also wenn ich das jetzt ausführe, dann sind das in jeder Testklasse 20 Testfälle.
Also die sind mehr oder weniger identisch. Das eine ist halt nur,
die einen testet halt eine Arrailist, der andere testet eine Linklist.
Also das ist einfach, dass die unterschiedliche Konstruktoren aufrufen.
Ansonsten sind die Tests gleich. Und es sind hier,
wie gesagt, 40 Tests, also 20, 20 20 fachliche Tests.
Ja, und ihr werdet in dieser Woche dann diese Implementation in den Übungen bauen.
Und ihr werdet so nach und nach sozusagen immer mehr Testfälle bestehen.
Das ist das Schöne. Man hat hier sozusagen immer das Feedback,
ob eure Implementation dann wirklich fertig ist oder ob dann auch
das eine oder andere vielleicht nicht stimmt.
Und unsere Tests sind über die Jahre gewachsen
Die sind also inzwischen ziemlich gut. Die decken eigentlich fast alles auf,
was ihr so an klassischen Fehlern machen könnt bei der Implementation.
Das ist auch dann nochmal eine Möglichkeit, sich klarzumachen,
wie gut das sein kann, solche Tests zu haben.
Ihr könnt diese Testklassen hier eigentlich auch ansehen, als hausimplementierte Spezifikationen.
Hier in dem Interface, da stehen so ein paar Kommentare drin.
Alle folgenden Elemente werden um eine Endexposition verschoben. Also hier steht so
ein bisschen im Klartext drin, wie sich das Ding verhalten soll.
Aber es ist natürlich nur semi-formal. Also es ist ja
nur natürlich textlich. Aber hier stehen tatsächlich Aufrufe drin,
an dieses Interface, also das ist extra so gemacht,
dass hier in den Testklassen eigentlich primär nur diese Schnittstelle benutzt wird.
Das heißt, das hier sind Blackbox-Tests. Die wissen nicht unbedingt,
also die Tests gehen nicht davon aus,
dass es entweder eine Array-Titel ist oder eine Linktitel-Liste ist.
Technisch müssen natürlich die beiden Klassen hier jeweils ein Exemplar erzeugen.
Deswegen ist diese Verbindung dann doch da. Aber die Tests
sehen wirklich gleich aus. Also wenn ihr euch die Testklassen anguckt,
dann sieht das so aus, dass ihr also relativ viele
Testklassen sind und dann sieht man hier irgendwie Teste enthält.
Also hier werden irgendwie wird eine Titelliste erstmal erzeugt,
dann werden hier irgendwie irgendwelche Testtitel aus einem Array
in einer Vorschleife reingefügt in diese Titelliste.
Und dann wird hier geprüft, ob dann da irgendwelche Dinge
zutreffen, die zutreffen müssten. Also ihr seht hier,
das ist ausprogrammierte Benutzung eigentlich dieser Schnittstelle. Und damit ist
ganz ziemlich genau spezifiziert vorgegeben, wie sich die Implementation verhalten soll.
Sehr schön. Das gucken wir uns dann vielleicht gleich nochmal an.
Das ist eigentlich relativ harmlos hier. Das ist nur eine Klasse,
die für Convenience, also damit ein bisschen einfacher ist,
uns Titel erzeugt. Die liest, ich habe irgendwie das
Gefühl, das wird schon wieder ziemlich laut gequatscht.
Bitte ein bisschen ruhiger sein. Das ist eine Klasse,
die einfach nur aus einer Textdatei Beschreibungen von Titeln einliest.
Wir haben nicht wirklich MP3s hier dahinter, sondern einfach nur so Beschreibungen von Titeln. Das heißt,
hier sieht das auch, man sieht das so ein Titel,
hat irgendwie sowas wie ein Album, ein Interpret, eine Länge,
einen Titelnamen und das übliche Hashcode to String und ein Equals wird auch noch implementiert.
Genau, das ist einfach,
um deutlich zu machen,
wie solche Titel implementiert sind
Genau, dann kann man hier tatsächlich sagen,
ich möchte hier Titelbibliothek, jetzt kann ich hier, glaube ich,
irgendeine Textdatei angeben, dann liest er die auch ein.
Und dann kann ich mir hier einen zufälligen Titel geben lassen
und dann kann ich mir hier das mal anzeigen lassen,
das ist dann irgendwie irgendwas von Diana Kral, okay,
irgendwelche Jazz-Titel, die da jetzt irgendwie in dieser Pete-Bibliothek drin sind und mit,
gibt zufälligen Titel, kann ich mir immer einen Titel geben lassen.
Also ich kann tatsächlich auch, wenn ich das nochmal mache,
hier, mir den jetzt hier auf die Leiste legen,
da unten
Zack. Und dann habe ich hier diesen Titel auch.
Irgendwie Herbie Hancock. Sehr schön. So, also das
ist ein Titel. Ihr wisst, das ist eine Referenz
letztendlich. Das ist also unser Elementtyp für die Sammlungen,
die wir jetzt hier bauen werden.
Beziehungsweise ich werde jetzt hier nicht so viel bauen.
Ich werde euch einfach nur sagen, wie man sie baut
und dann dürft ihr sie in den Übungen bauen.
Okay, also das war jetzt die Sache mit dem Umgang.
Es ist jetzt klar, wofür man solche Listen braucht.
Jetzt geht es ans Eingemachte. Jetzt gucken wir uns an
, wie die gebaut werden. Und wie gesagt,
zwei Implementationen. Zwei Klassen, nämlich Linklist und Arealist,
die habt ihr schon teilweise benutzt.
Linklist basiert auf verketteten Listen
Und Arylist basiert auf wachsenden Arrays. Gut, wir gucken uns
zuerst verkettete Listen an. Verkettete Listen sind, ja,
wie soll man sagen, die, eine der grundlegenden Datenstrukturen.
Und auch da gilt wieder, ihr sollt nicht jetzt hier lernen,
verkettete Datenstruktur oder verkettete Listen zu bauen,
weil die sind meistens schon da.
Es geht darum, dass ihr mit Referenzen umgehen lernt.
Und was ich vielleicht jetzt auch noch kurz vorweg sage,
ist, ihr werdet euch wundern,
wie einfach das,
was ich jetzt hier erzähle,
Und umso mehr werdet ihr euch wundern,
wie schwierig ist es, das zu implementieren.
Das ist auch eine Erfahrung, die wir immer wieder gemacht haben.
Das, was ich hier erzähle, ist glasklar, simpel und einfach.
Ja, man muss da so ein paar Referenzen umbiegen und fertig.
Aber wenn man es dann implementieren muss, ist es tatsächlich richtig schwierig.
Also insbesondere für Programmieranfänger, aber auch für Leute,
die wirklich schon viel Programmiererfahrung haben, die müssen sich immer wieder echt,
müssen das Gehirn anschalten und irgendwie kapieren AR die Referenz so und so.
Also es ist tatsächlich nicht so einfach. Also das kann ich
euch versprechen. Alles, was jetzt hier einfach aussieht,
wird trotzdem in der Implementation euch ein bisschen fordern.
Also, verkettete Liste, dynamische Datenstruktur sequenziell.
Endliche Folge eines endliche Folge eines Grundtyps, also Listenelemente,
besitzen eine Reihenfolge und können mehrfach enthalten sein.
Sequenzelemente, jedes Elistenelement mit dem Nächsten verbunden. So,
das ist jetzt etwas, was ganz wichtig ist.
Wenn ihr eine Liste programmiert, dann habt ihr üblicherweise eine Referenz.
Das ist der Listenanfang. Und von dieser am Anfang könnt ihr
dann alle Elemente besuchen. Das ist hier diese Idee.
Irgendwie habe ich hier so eine Referenz. Und dann,
weil ich die eine Referenz habe,
habe ich auch alle Elemente
sozusagen im Zugriff
Das ist erstmal die Grundidee. Das heißt,
wenn ich zu irgendeinem Element hinkommen will,
muss ich tatsächlich über den Einstieg hier mich so durchhangeln,
durch so eine Objektstruktur, die so verkettet ist,
bis ich an der richtigen Stelle bin.
Das heißt also, man muss das Ganze tranversieren.
Ein tolles Wort für Durchlaufen. Okay, aller
einfachster Fall ist die einfach verkettete Liste. Das heißt,
jedes Listenelement hat nur eine Referenz auf sein Nachfolgerelement.
Genau eine Referenz hier. Und die Liste kann nur
elementweise durchlaufen werden
Von der Darstellung hier sehr nachvollziehbar, ganz einfach.
Die doppelt verkettete Liste sieht dann so aus,
dass hier immer ein Element sowohl eine Referenz hält auf seinem Nachfolger,
als auch auf seinen Vorgänger.
Doppelt verkettete Listen haben bestimmte Vorteile.
Man kann sich schon vorstellen, welche Vorteile es gibt.
Ich kann nicht nur von vorne nach hinten durchlaufen,
sondern ich kann auch wieder von hinten nach vorne durchlaufen.
Das hat manchmal Vorteile. Ich werde gleich dann
auch Beispiele nennen, wo das vorteilhaft ist.
Tatsächlich ist das, was ein Collections Framework ist,
auch eine
doppelt verkettete Liste
Das ist ja schön. Einfach verkehrteslöst du einfach, doppelt verkettet
auch einfach. So, aber jetzt kommt die Frage,
wie baut man denn sowas? Wenn ich euch jetzt sagen würde,
baue mir mal eine doppelt verkettete Liste,
dann ist das eben halt tatsächlich nicht so einfach,
wie das klingt. Man muss eigentlich so ein,
also gerade wenn man es mit einer objektorientierten Sprache macht,
muss man halt so ein bisschen sich überlegen,
ja, was für Klassen sind denn da jetzt beteiligt?
Wie viele Klassen brauche ich denn? Auf jeden Fall braucht man
die Elemente. In unserem Fall sind das ja diese Titelobjekte,
Elemente, also Exemplare, der Klasse Titel.
Das sind dann die Referenzen, die da irgendwie drin gehalten werden.
Das sollen mal unsere Elemente sein. Also die sind hier
so blau angedeutet
Die brauchen wir auf jeden Fall. Die sind auf jeden Fall
irgendwie beteiligt, weil die Dinger müssen ja irgendwo rein.
Dann ist es üblicherweise, dass man ein Objekt,
also eine Klasse modelliert, ein Objekt hat,
das die Liste insgesamt repräsentiert.
Das heißt, wenn ihr euch eine verkettete Liste,
eine Linked-List-Exemplar erzeugt, dann habt ihr genau eine Referenz auf dieses eine Exemplar.
Und dann gibt es aber außerdem noch diese Kettenglieder.
Das heißt also so technische Hilfsobjekte, die dazu da sind,
diese Dinger miteinander zu verketten.
Die machen also wirklich diese Verkettung möglich und die sind eigentlich
für einen Klienten gar nicht sichtbar
Das heißt, ihr seht das auch hier in diesem Beispiel hier.
Hier sieht man eine verkettete Listin-Implementation und dann gibt es hier einen Doppel-Link-Knoten.
Also das ist genau so eine Knotenklasse. Eigentlich für einen Klienten
ist das ein Implementationsdetail, dass das doppelt verkettet ist.
Das interessiert einen Klienten eigentlich gar nicht. Gut,
also der Klient will das erstmal gar nicht wissen.
Der Klient will ja nur den Umgang,
das wisst ihr ja, irgendwie was einfügen und was entfernen.
So, wenn wir jetzt uns überlegen,
wie denn jetzt diese einzelnen Klassen hier gebaut sind,
dann brauchen wir was für die Liste und für das Kettenglied.
Und das Kettenglied ist eigentlich das Spannendste. Das heißt,
Wir definieren ein Typ, eine Klasse, Kettenglied.
Hier ist es jetzt Kettenglied in unserem Blue Jay-Beispiel.
Es ist Doppel-Link-Knoten. Und jedes Kettenglied hält eine Referenz.
Erst mal auf das eigentliche Element. Also tatsächlich hier,
wenn ich hier Personen habe, dann eben halt eine Referenz auf eine Person.
Oder wenn ich Referenzen auf Titel halte,
habe ich hier eine Referenz auf dem Titel drin.
Das ist hier immer so gelb angedeutet. Das hier ist sozusagen
ein Objektdiagramm. Also dieses Objekt hält eine Referenz auf eine Referenz auf ein Element.
Und mindestens eine Referenz auf ein weiteres Kettenglied.
Also meine einfach verketteten Liste Da brauche ich noch irgendwie so
eine Referenz auf das nächste. So, das heißt also,
jetzt habe ich schon so eine Vorstellung,
wie so diese Ketten, die da aussehen müssen.
Im Objektdiagramm, jetzt muss ich mir überlegen, wie sind
denn die Klasse dazu aus? Dann sage ich halt,
es gibt eine Klasse Kettenglied und da gibt es hier
ein Nachfolgerreferenz vom Typ Kettenglied und dann die Person,
die dann tatsächlich hier das eigentliche Element darstellt.
So, wenn ich das jetzt mir genau angucke,
dann seht ihr hier, das ist nicht durch Zufall rot hervorgehoben.
Hier haben wir eine Klasse, die hat eine Exemplarvariable die
vom gleichen Typ ist wie die Klasse selbst.
Das haben wir bisher noch nicht gesehen.
Das ist was Neues. Das nennt man auch strukturelle Rekursion,
weil eine Klasse in ihrer Struktur, also in ihrer Zusammensetzung,
eine Exemplarvariable hält auf Objekte der eigenen Klasse.
Das ist kein Problem, weil wir wissen ja,
von einer Klasse kann es ja mehrere Objekte geben.
So ist einfach die reale Verkettung. Zur Laufzeit sind einfach
mehrere Objekte derselben Klasse. Man kann natürlich auch eine Verkettung
auf sich selbst machen, zur Laufzeit.
Das ist meistens nicht so eine schlaue Idee.
Das heißt, üblicherweise ist das hier so gebaut
Okay, das ist einfach ganz allgemein, wie man objektorientiert sowas modellieren würde.
Und dann haben wir natürlich noch eine weitere Klasse,
die eigentlich diese Liste als Gesamtheit repräsentiert.
Das heißt, diese ganzen Kingglieder, die sind sozusagen so
hinter den Kulissen, die sind so Implementationsdetail.
Und die müssen aber ja irgendwie sozusagen geeignet verwaltet werden.
Und ich habe eben schon gesagt, wenn ich eine verkettete Liste habe,
reicht es eigentlich, wenn ich eine Referenz halte.
Und das ist genau das, was hier so eine Linkedlist macht.
Also hier diese Klasse Linkedlist, die hat eigentlich nur
eine Exemplarvariable und die heißt Listenkopf.
Und das ist eine Referenz auf das erste Kettenglied
Ja, und damit haben wir schon eigentlich komplett unseren objektorientierten Entwurf gemacht.
Das heißt auch, was man sich klar machen muss,
deswegen, ich habe das hier mal in diesem Objektdiagramm,
ist dieses Listenobjekt so ein bisschen länglich dargestellt,
um klarzumachen, dass die repräsentiert sozusagen die ganze Liste.
Ich habe explizit nicht diese Ketten wieder da reingemalt,
weil das den falschen Eindruck erwecken könnte,
als wären diese Dinge ineinander geschachtelt.
Das ist nicht der Fall. Ihr wisst ja,
es gibt keine Schachtelung für Objekte in Java,
sondern es ist immer nur irgendwie über Referenzen.
Das heißt, die Dinge liegen alle irgendwo im Hieb
und auch irgendwie diese Klasse,
dieses Exemplar von der Klasse Linklist
Ist ein Exemplar, das irgendwo umspeicherlich und dann irgendwo eine Referenz hält,
auf irgendwelche Kettenglieder. Aber ihr könnt euch das logisch so vorstellen,
als wenn diese Kettenglieder hier eigentlich Teil dieser ganzen Liste wären.
Die sind sozusagen da drin versteckt. Okay. Ja,
und dann haben wir halt diesen Listenkopf und das ist dann
üblicherweise etwas vom Typ. Könnt ihr euch jetzt überlegen,
die klasse Link Cliss hat eine Exemplarware,
aber vom Typ Listen,
also ein Name Listenkopf unter Strich Listenkopf und das
muss natürlich vom Typ Kettenglied sein in diesem Beispiel
Ja. Und jetzt kommen genauso typische Operationen,
die wir jetzt ja uns auch überlegen müssen,
wie sie implementiert werden müssen, nehmen wir an.
Wir haben so eine verkettete Liste. Wir haben also irgendwie
den Listenkopf und dann haben wir hier diese verketteten Kettenglieder.
Jetzt soll ich ein Element eingefügt werden. Ja,
in einer bestimmten Position. Also ich habe hier irgendwie
einen neuen Listeneintrag. Ist hier so angedeutet,
dass es jetzt wieder irgendein Elementtyp, also ein Objekt von,
was weiß ich, eine Person oder einen Titel.
Und das Gelbe ist hier wieder angedeutet,
das ist ja die Referenz darauf.
Wir haben ja immer eine Referenz letztendlich, die wir brauchen.
Das Ding liegt irgendwo im Hieb,
die Referenz liegt irgendwo,
wo wir sie gerade brauchen
So, das wird ja wahrscheinlich jetzt als Parameter übergeben.
Wir stellen uns vor, jemand ruft als Klient
an einer Liste auf, Insert an einer bestimmten Position.
So, dann muss ich zuerst mal ein neues Kettenglied erzeugen.
Das heißt also, innerhalb der Implementation muss ich jetzt tatsächlich schubs
ein Kettenglied bauen, also ein New machen auf ein Kettenglied.
Und üblicherweise wird man dann gleich schon bei dem Kettenglied die
Referenz auf das einzufügende Element auch dann da drin ablegen.
Das heißt, so sieht das Ding aus.
So, jetzt haben wir sozusagen unser neues Kettenglied.
Wir müssen das jetzt nur noch einketten in diese verkettete Liste.
Das ist relativ einfach
Erstens sage ich hier, das Nächste ist das Element.
Noch ist die ursprüngliche Liste völlig unberührt. Und dann muss ich
als nächsten Schritt hier den Vorgänger auf das neue Element verweisen lassen.
Und mit zwei Zuweisungen von Referenzen habe ich dann hier tatsächlich
das Ding eingekettet in diese verkettete Liste.
Wie gesagt, total offensichtlich, total einfach.
In der Implementation immer so ein bisschen weniger einfach.
Tja, entfernen sieht da nicht viel anders aus.
Ich habe wieder meine Liste irgendwie als Gesamtheit mit dem Listenkopf drin.
Das hätte ich wieder auf der Folie davor auch mal machen können.
So ein schönes Kästchen drum, um deutlich zu machen,
das ist die ganze Liste. Da sieht man nämlich auch deutlich,
dass hier die Elemente eben nicht Teil der Liste sind.
Gut, und jetzt möchte ich ein Element entfernen,
naja, das ist auch wieder ganz einfach.
Ich muss einfach nur die Referenz,
also wenn ich das Element hier entfernen will,
dann muss ich einfach nur die Referenz von hier irgendwie da vorbeibiegen.
Also einfach nur die Referenz auf dieses Objekt.
Ich nehme diese Referenz hier und weise sie dieser Exemplarvariabel zu.
Zack, ganz einfach. Und dann ist es auch so,
dass der Garbage Collector dieses Element irgendwann entfernt
Das heißt, dieses Objekt wird nicht mehr referenziert, dieses Kettenglied.
Und irgendwann wird der Gabelstractor das wegrollen. Das ist tatsächlich relativ nett,
dass man sich im Java darum nicht kümmern muss.
Ja. Ich denke, bisher war das soweit nachvollziehbar.
Gibt es da Fragen dazu? Ich denke, das ist,
ja, wie gesagt, so ein paar Dinger umbiegen und so,
alles nachvollziehbar. Wie gesagt, das gibt uns Gelegenheit,
nochmal so alle Begriffe, die wir so bisher kennengelernt haben,
nochmal so ein bisschen zu wiederholen, zu üben.
Zum Beispiel kann man sich ja jetzt mal überlegen,
wie sieht denn das Klassendiagramm aus
Für so eine einfach verkettete Liste, wie wir sie gerade dargestellt haben.
Wir haben gesehen, es gibt irgendwie den Element-Typ, irgendwas.
Dann gibt es diese klasse Kettenglied und es gibt die klasse Linklist.
Wir wissen auch, in der Klasse Linked List gibt es irgendwie
diese Exemplarvariable Listenkopf, die natürlich vom Typ Kettenglied ist.
Und beim Kettenglied gibt es irgendwie eine Referenz,
die auf das Element zeigt und eine Referenz auf.
Das Kettenglied. Ja, das ist nur erstmal die Klassen
jetzt hier benannt und so ein bisschen in ihrer Struktur dargestellt.
Aber es gibt dann ja auch noch die Pfeile in
solchen Klassendiagrammen und die werden ja gerne mal unterschätzt.
Das ist aber immer wieder interessant zu sehen,
dass selbst im zweiten Semester dann bei uns in SE2 noch Leute
so nicht so genau wissen, was die Falle denn so bedeuten.
Also einmal sehen wir ja hier,
die Klasse Linklist hält einen Verweis auf die Klasse Kettenglied.
Im Attribut Listenkopf. Das heißt, das hier ist eine durchgezogene Linie.
Das heißt, das ist eine echte strukturelle Beziehung.
Das heißt, in seinem Aufbau hat ein Exemplar dieser Klasse,
hält es einfach in den Exemplar, weil ja eine Referenz.
Das ist was Solides. In Exemplar war ja dann abgelegt. So,
dann ist es so, dass natürlich die Klasse Linklist den Elementtyp benutzt,
weil die Klasse Linklist hat ja eine Schnittstelle,
wie für ein Entferne und der Parameter von diesen Operationen ist ja ein Titel oder eine Person oder sonst irgendwas.
Das heißt, dieser Typ ist auf alle Fälle der Linklist bekannt,
aber die Linked List, diese Klasse selbst, hält ja kein Exemplar,
keine Exemplarvariable, irgendwie keine Referenz, auch ein solches Element,
weil diese ganzen Referenzen sind ja mal in den Kettengliedern abgelegt.
Das heißt, das hier ist nur eine etwas schwächere Beziehung,
nämlich nur eine Abhängigkeit. Da besteht eine Abhängigkeit und das
machen wir dann üblicherweise in unserer Deutung der UML,
sodass wir sagen,
das ist eine gestrichelte Linie
Also etwas weniger, solide, keine strukturelle Abhängigkeit, sondern einfach eine Abhängigkeit.
So, dann habe ich gesagt, klar,
so ein Kettenlied hält natürlich in seiner Exemplarvariable ein Element,
eine Referenz auf ein Element.
Also ist das wieder eine echte strukturelle Abhängigkeit.
Und so ein Kettenlied hält eine Referenz auf sich selbst.
Also kann man hier noch ergänzen, tatsächlich hier,
dass es eine rekursive Struktur ist. Ja, und somit
ist eigentlich relativ offensichtlich wie so ein Klassendiagramm aussehen kann.
Ja, okay, das nur mal so als kleinen Exkurs.
Jetzt gucken wir uns nochmal die doppelt verkettete Liste an
Auch das, weil das auch in den Übungen ja drankommt und
weil es eben halt auch im Collection Spraingwork so gebaut ist.
Also hier steht es ja auch. Linklist basiert auf
diesem Konzept. Da ist es eben halt so,
dass man nicht nur eine Referenz hat auf den Nachfolger,
sondern jedes Kettenlied hat auch eine Referenz auf den Vorgänger.
Das ermöglicht, dass ich in beide Richtungen durch die Liste
durchlaufen kann und einfügen und entfernen werden vereinfacht.
Das könnt ihr mal euch klarmachen, wenn ihr irgendwann mal,
sagen wir mal, eine müßige Sekunde habt,
dass ihr nicht das mit einer Doppeltverkettetenliste baut,
sondern zum Vergleich auch mal mit einer einfach verketteten Liste.
Dann werdet ihr sehen, dass das an manchen Stellen nicht
so ganz einfach ist
So, okay, also doppelt verkettete Liste. Ja,
ich kannte ja mal hier so ein bisschen live was zeigen.
Ihr seht hier diesen Doppellink-Knoten. Das ist so das,
was auf den Folien hier Kettenglied heißt. Kann ich
mal zeigen. Das sieht eigentlich auch relativ harmlos aus.
Hat einfach nur verschiedene sondierende Operationen, aber auch durchaus verändernde Operationen.
Also man kann hier den Nachfolger sich geben lassen oder man kann ihn setzen.
Das heißt, entweder übergibt man eine Referenz auf einen
doppelten Knoten
Oder man lässt ihn sich geben. Man kann sich einen Titel geben lassen,
also den Titel, der in dem Knoten gespeichert ist.
Man kann sich den Vorgänger geben lassen, also alles da,
was man so braucht, gib und Sätze.
Also es ist eigentlich ein relativ,
also es ist ein Datensack, kann man sagen.
Da steckt man Sachen rein und man kann sie wieder rausholen.
Relativ unspektakulär. So, und dann sieht man aber hier,
gibt es irgendwie verschiedene Konstruktoren, einmal ohne Con,
ohne Parameter und einmal mit Parametern.
So, jetzt kann ich hier mir einfach mal so
einen Knoten erzeugen. Zack. Ja, da habe ich
ihn und der sieht dann so aus
Also genau wie eigentlich wie auf der Folien dargestellt.
Ich habe eine Vorgängerreferenz und eine Nachfolgerreferenz und eine Referenz auf ein Element.
Nur habe ich hier erstmal noch nichts drin,
ist alles auf Nall. Kann er jetzt mal sagen,
hallo, setz doch mal den Titel und ich habe ja
hier diesen wunderbaren Titel, den ich hier schon habe.
Die Referenz setze ich da mal rein. Und der Effekt
ist, dass da jetzt die Referenz sichtbar wird.
Ja. Okay. Und jetzt komme ich auf die glorreiche Idee.
Ich mache noch so einen Knoten und da kann ich jetzt hier einfach mal zeigen.
Ja, ich kann auch dann jetzt diesen Konstruktor benutzen.
Ich kann denselben Titel auch mehrfach eintragen, ist ja
kein Problem
Ja, also hier irgendwie Titel 1. So,
und dann der Vorgänger soll jetzt mal sein,
der andere Knoten, also den trage ich da mal ein.
Und der Nachfolger, der ist nall. Da habe ich
einfach nichts. So. Wunderbar. Jetzt habe ich hier diesen
einen Knoten und jetzt habe ich hier den zweiten Knoten und man sieht jetzt hier schon,
beide haben einen Link, eine Referenz auf den Titel.
Also wenn ich jetzt Doppelklick mache, dann kommt hier irgendwie dieser Herbie Hancock-Titel.
Und wenn ich davon da aus irgendwie komme, kann man mal zeigen,
wenn ich jetzt von hier aus komme und sage,
gebe ihm mal den Titel, komme ich auch auf dieses Objekt drauf.
Wunderbar. Und ich komme aber auch jetzt von hier,
also von diesem doppelten Knoten zum Vorgänger, und das dürfte ja der Knoten sein.
Also der hier. Genau. Bei dem Vorgänger würde ich ja jetzt gerne mal,
bei dem ersten hier, als Nachfolger diesen anderen Knoten reinsetzen.
Zack. Und siehe da, genau das haben wir jetzt hier.
Jetzt haben wir zwei Knoten, wechselseitig miteinander verknüpft über Referenzen.
Ich kann also jetzt in beide Richtungen navigieren.
Ich kann jetzt also von hier zum Vorgänger kommen,
also ich zeige das nochmal hier man sieht das ja immer so schön mit dem,
also wenn ich jetzt dieses hier, wie komme ich zum Vorgänger,
wenn ich von dem zum Nachfolger will,
komme ich eben zu dem anderen.
Ich kann also richtig schön hin und her nahe gehen.
Selbst mit zwei Knoten kann ich schon unglaubliche Dinge tun.
Und das ist einfach nur, um es hier zu zeigen,
wie sowas tatsächlich dann auch aussehen kann. So,
und jetzt ist es so, wenn man so etwas dann
implementiert und dann will man zum Beispiel das Insult programmieren,
dann ist das bei einer doppelt verketteten Liste nicht so ganz einfach.
Und es gibt dann nämlich so ein paar Möglichkeiten,
wie man das, sagen wir, einfacher gestalten kann.
Also es ist zum Beispiel so, und das ist auch tatsächlich die Implementation,
die jetzt hier bei uns in dem SE1-Tunes-Projekt vorgegeben ist.
Wir verbinden sogenannte Wächterelemente. Das ist ein Trick,
ein schlauer Trick, den sich irgendwelche Informatiker mal haben einfallen lassen.
Um den Umgang mit solchen verketteten Listen ein bisschen zu vereinfachen.
Ich werde gleich zeigen, warum das vereinfacht. Also das,
was wir bis jetzt gesehen haben, ist irgendwie,
man hat einen Listenkopf, eine Referenz und dann hat man
hier irgendwie einfach verkettet oder vielleicht auch doppelt verkettet,
irgendwie solche Elemente.
Dann gibt es die Möglichkeit, ein Wächterelement zu haben.
Und das Wächterelement ist tatsächlich genau so ein Doppel-Link-Knoten oder
genau so ein Kettenglied, wie die anderen auch.
Und man kann auch einen Wächter am Anfang haben.
Und, oder. Man kann ein beides haben. Wir haben
hier in der Implementation in dem SE1-Tunes vorn und hinten ein Wächterelement.
Das heißt, wenn eine leere Liste erzeugt wird,
enthält die schon genau diese beiden Wächterelemente und dazwischen ist nichts.
So, warum ist das jetzt ein Vorteil?
Ich will jetzt einfach mal kurz versuchen darzustellen,
warum das Vorteile bringt bei bestimmten Sonderfällen
Also, Listenanfang. Nehmen wir mal an,
wir haben jetzt sozusagen unsere Liste mit irgendwie mehreren Knoten.
Kann ja so aussehen. So, Listenkopf und mehrere Knoten
. Und jetzt habe ich ja,
und das ist auch immer ganz wichtig, muss man sich klarmachen,
es gibt jetzt in dieser Liste drei Elemente.
Das seht ihr. Das heißt, gültige Zugriffe auf Elemente
sind 0, 1 und 2 als Indizes.
Aber Einfügepositionen gibt es vier. Es gibt vier Möglichkeiten,
in dieser Liste etwas einzufügen. Nämlich hier,
das ist Position 0, dann wird es als erstes Element
in der Liste geführt anschließend.
Oder hier oder hier oder hier
Das ist auch was, was man sich klarmachen muss und
was man auch in dem Element so bedenken muss.
Also welcher Index ist denn überhaupt zulässig? Also wenn ich drei Elemente habe,
dann sind als gültige Indizes zugelassen für ein Einfügen 0 bis 3.
So, jetzt haben wir die Situation, die haben hier
kein Wächterelement. Und jetzt nehmen wir mal den Fall,
ich möchte an Position 0 ein Element einfügen. Normalerweise laufe ich,
muss ich ja erstmal zu dem Element hinlaufen und dann muss ich irgendwie diese Verkettung irgendwie ummachen,
so wie ich das eben demonstriert habe,
irgendwie einmal die Verkettung umbiegen und so.
Der Listenkopf ist aber eine Ausnahme, weil normalerweise muss ich immer bei einem Knoten
Also bei einem Kettenglied oder bei einem Doppel-Link-Knoten, muss ich
das next besetzen, diese Referenz auf den nächsten Knoten.
Nur am Anfang, wenn ich hier an eine Null was einfügen muss,
dann muss ich den Sonderfall machen, also wenn es die Null ist,
dann muss ich ja diese andere Exemplar-variable Listenkopf,
die muss dann neu gesetzt werden.
Weil der neue Listenkopf und der Listenkopf soll ja dann auf das neue Element verweisen.
Das ist ein Sonderfall, den muss ich im Code ausprogrammieren.
Da muss ich wirklich beim Einfügen sagen, ja,
ist es am Anfang oder ist es am Ende und da
muss ich eben spezielle Dinge tun.
Mit einem Wächter ist es etwas einfacher,
weil ich kann dann in der Methode einfügen,
immer auf das Listen-Element positionieren, das unmittelbar der Einfügestelle liegt.
Ich kann also wirklich genau dahin laufen. Also hier habe
ich den Wächter. Ich kann also immer auf einen,
also eine Referenz haben. Ich laufe sozusagen bei meiner Referenz
so durch und komme irgendwann auf ein Element vor der Stelle,
wo ich was einfügen will.
Und selbst wenn es hier eingefügt werden soll,
also an Position null, habe ich ja direkt davor ein Wächterelement.
Das heißt, ich habe keinen Sonderfall. Ich gehe einfach
genauso weit rüber, wie ich rüber muss.
Und ab da kette ich dann um. Ein Sonderfall weniger
. Das ist einfach nur, um das nochmal klarzumachen,
warum diese Wächterelemente nützlich sind. Auch das ist wieder was,
das merkt ihr am besten, wenn ihr es mal selber versucht zu implementieren.
Mit Wächter ohne Wächter und dann seht ihr,
wie umständlich das ist
Ohne Wächter. Gut, dann gibt es natürlich auch noch
verschiedene Möglichkeiten, sagen wir, Zusatzinformationen zu halten.
Unsere Klasseliste, unsere verkettete Liste,
die hält ja momentan nur den Listen anfangen, den Listenkopf.
Da muss ich nicht viel tun. Die könnte mal
ein bisschen mehr zu tun kriegen. Die könnte zum Beispiel noch
eine Referenz halten in ihren Exemplarvariablen, nämlich auch auf das Ende.
Gerade bei doppelt verketteten Listen ist das sehr hilfreich.
Also wenn ich zum Beispiel bei einem Element,
bei einer Liste mit 5000 Elementen sage,
jetzt füg mal an Position 4.900 was hinzu, gibt es die Möglichkeit,
bei einer Einfachverkehrtnisse muss ich einmal von vorne 4.900 Mal da durchlaufen.
Bei einer doppelt verketteten Liste kann ich einmal kurz rechnen,
aha, ich bin ja damit schon fast am Ende,
dann komme ich von hinten.
Ich habe ja meine Referenz auf das letzte Element und laufe
nur 100 Elemente zurückstand. 4.900 nach vorne. Also
da sind Optimierungen möglich bei doppelt verketteten Listen und wenn man
sich eben halt auch so eine Referenz aufs Ende hält.
Sowieso schlau sich eine Referenz aufs Ende zu halten,
weil häufig bei Listen auch am Ende eingefügt wird.
Also in iTunes habt ihr es gerade gesehen,
wenn ich da irgendwie einen Titel draufgezogen habe auf die Wiedergabeliste,
dann wurde die natürlich ans Ende eingefügt.
Man kann das ja immer noch umschieben,
aber der Default ist erstmal ans Ende
Tja. Gut. Und das vielleicht noch so als kurzen
Hinweis, so bevor wir gleich in die Pause gehen.
Das Zitat habe ich von Fred mal irgendwann bekommen.
Ist aus einem alten Block, der schon gar nicht mehr
zu finden ist eigentlich. Aber ich finde das einfach sehr,
sehr treffend, was die Person hier geschrieben hat.
Das ist was, was wir den Übungen immer wieder beobachten.
Gerade wenn es auch jetzt so bei verketteten Listen darum geht,
sich zu überlegen, was man denn tun will,
eine Zeichnung machen, alle Probleme total klein machen.
Also man muss einfach nur sich so ein paar Böbbel hinmalen,
ein paar Referenzen dazwischen und schon kann man darüber reden, kann darüber nachdenken.
Eigentlich ist es, es steht hier auch,
das humanly impossible to right code to reverse, okay,
es ist jetzt natürlich um eine verkettete Liste umzudrehen.
Das glaube ich wirklich, dass das nochmal eine deutlich schwieriger ist,
weil das verlangen wir ja gar nicht,
dass wir das in Übung machen sollt.
Also sozusagen die Reihenfolge umdrehende Elemente in einer Liste.
Aber es ist tatsächlich extrem hilfreich und das könnt ihr
euch wirklich immer merken, macht euch eine Zeichnung.
Von allem, was ihr so implementiert,
immer irgendwie eine Zeichnung macht, macht einen Klassendealer,
macht ein Objektdiagramm,
macht irgendwas
Aber bevor ihr nicht zeichnet, fangt lieber gar nicht erst
an zu programmieren. Das ist einfach hilfreich und gerade bei
verketteten Listen extrem hilfreich. Ja, gut, dann,
wenn keine Fragen sind jetzt zu verketteten Listen,
hat jemand eine Frage zu verketteten Listen?
Das sieht nicht so aus, dann machen wir jetzt mal
eben fünf Minuten Pause. Dann... Können wir,
glaube ich,
Alle wieder da. Sieht so aus. Jo. Das
war jetzt alles zu vergetteten Listen. Natürlich nicht alles,
aber auch relativ elementare Grundsätze zu vergetteten Listen.
Das, was man auf jeden Fall wissen sollte,
wenn man verkettete Listen bauen will. Darüber hinaus gibt es
ganz viele tolle weitere Möglichkeiten, verkettete Listen zu bauen.
Da haben die Informatiker der letzten 40 Jahre
wirklich extrem viele gute Ideen gehabt und clevere Implementationen gebaut.
Aber das, was wir jetzt hier gemacht haben,
war sozusagen,
liegt allem zugrunde
Gut, jetzt die andere Möglichkeit. Eine Liste zu implementieren
. Es gibt möglicherweise auch noch ganz viele andere Möglichkeiten,
keine Frage. Aber jetzt die zweite zentrale Möglichkeit,
wie Sie auch im Plection Stringwork umgesetzt ist.
Das ist die Idee, mit den wachsenden Arrays. Das heißt,
wir haben wieder, das ist jetzt hier leider auf der Folie,
nicht so gut deutlich, wir haben wieder eine Klasse,
die steht für die ganze Liste.
Also die heißt dann zum Beispiel Arraylist.
Und diese klasse Areallist hat eine Exemplarvariable. Und diese Exemplarvariable
ist ein Ray. Und dieses Array ist dazu da,
um möglichst viele Elemente der Sammlung aufnehmen zu können. Das heißt,
wenn so ein Objekt erzeugt wird, so eine Arrailist,
dann hat das intern ein Array von einer bestimmten Größe und
dann können auch bestimmte Elemente eingefügt werden, ohne Probleme.
Aber es kann natürlich irgendwann sein,
dass dann das Ding voll ist, das Array wird ja voll,
weil ihr wisst ja, eine Ray,
wenn es einmal erzeugt ist,
dann kann es nicht mehr verändert werden in seiner Größe
Das heißt, sobald ich das, urray,
was ich bisher benutzt habe in meiner Implementation,
sobald das voll ist, muss ich einen neuen Surray anlegen.
Das führt zur Unterscheidung zwischen zwei ganz zentralen Begriffen.
Vor drei Wochen, also eigentlich vor drei Vorlesungswochen,
als ich über Sammlungen gesprochen habe, da habe ich gesagt,
ein wichtiger Begriff für Sammlung ist der der Kardinalität.
Also die Frage, wie viele Elemente sind denn so drin
in meiner Sammlung? In diesem Beispiel sieht man hier jetzt,
wenn man sich das hier so vorstellt,
als das Ray,
in dem jetzt
irgendwelche Sammlungen sind
Ah, oh, gut, dass du es sagst.
Okay, ich habe jetzt hier schon, was habe ich denn erzählt?
Das war noch das Bild hier, oder was? Ah, schön. Also,
das hier jetzt, ja, andere Implementationen,
wachsende Arrays, das heißt,
wir haben irgendwie eine Klasse Arraylist und in der Klasse
eine Referenz auf ein Array und das hat eine bestimmte Anfangsgröße,
ich kann das mit Elementen befüllen und wenn es voll ist,
muss ich ein größeres Array anlegen und dann alle Elemente umkopieren.
Hier ist dieses Beispiel, also ein Array hier rot angedeutet
mit Sieben Zellen, also sieben Speicherplätzen für Elemente.
Eigentlich sind das natürlich Referenzen auf Elemente, ihr wisst es.
Und da habe ich hier die gültigen Indizes. Und es ist natürlich so,
dass wenn ich bei einer Air-Ray-List vier Elemente eingefügt habe,
dann kann ich natürlich nur auf Position 0, 1,
2, 3 zugreifen.
Weil nur das sind wirklich gültige Elemente Indizes. Das intern,
das Array größer ist und mehr Indizes zulässt, interessiert den Klienten nicht.
Also das ist ganz wichtig. Für den Klienten ist es so,
er hat irgendwie vier Elemente eingefügt,
deswegen kann er auch nur auf vier Positionen zugreifen
Aber intern kann das Arraya durchaus größer sein.
Das heißt, das hier ist die Kardinalität,
also wie viele Elemente sind wirklich drin in der Sammlung?
Und was ist die Kapazität des Containers, also des Race,
das intern benutzt wird? Und ganz einfach,
es gilt immer, die Kapazität muss größer gleich der Kardinalität sein,
sonst haben wir irgendein Problem.
Ich wüsste nicht, wo die Elemente hin sollen,
wenn ich keine Kapazität mehr habe.
Also muss das immer gelten. Das ist eine Invariante dieser
Implementation. Die Kapazität muss größer gleich der Kalinalität sein
Also ganz wichtig, wenn ihr euch so dieses Konzept von wachsenden Arrays
anguckt, dann müsst ihr immer diese beiden Dinge unterscheiden.
Die Kapazität, also einfach die Anzahl der Zellen,
die zur Verfügung stehen, um Elemente aufzunehmen.
Das ist die Größe des Arrays. Oder und unterscheiden davon
die Kardinalität, also viele Elemente sind drin.
Das ist erstmal die Grundidee. Jetzt gibt es verschiedene Dinge,
die man beachten muss. Erstens, der Zugriff ist sehr
schnell. Also wenn ich auf eine beliebige Indexposition,
ich wurde gerade darauf hingewiesen,
richtigerweise ist es natürlich nicht eine beliebige Indexposition zugelassen,
sondern immer nur Indexposition aus dem gültigen Bereich der Indizes.
Also wenn ich vier Elemente habe,
dann kann ich auch nur auf vier Elemente zugreifen.
Aber innerhalb dessen ist es eben halt beliebig und wenn ich
jetzt eine große Liste habe mit 5.000, 10.000 Elementen,
dann kann ich halt beliebig irgendwo da zugreifen.
Und das ist bei einem Ray sehr schnell. Ja,
ich sage einfach, gib mir das Element an, Position 4.900.
Und dann wird ein indexbasierter Zugriff auf dem Aray ausgeführt.
Ihr wisst, das ist schnell. Arays zeichnen sich ja
dadurch aus, dass sie effizient sind.
Also ich kann wirklich sehr schnell auf ein bestimmtes Element zugreifen
Was unangenehm ist bei dieser Implementation ist, wenn ich in dem Array
jetzt an einer bestimmten Position ein Element dazwischen schieben will.
Ich will tatsächlich sagen, jetzt, das haben wir auch,
das Beispiel ist ja hier auf der Folie, haha,
also ich habe einen bestimmten Zustand, das hier sei,
da drumherum kann man sich jetzt vorstellen,
das ist die Arraylist und die enthält,
hält eine Referenz auf dieses Array,
mit der Kapazität 7 und Kardinalität sind jetzt vier Elemente drin.
Jetzt? Sagt jemand, ich möchte gerne ein Element einfügen,
also X,
was auch immer
An Position 1. Und ihr wisst ja, wenn etwas an Position eins
angefügt hat, müssen ja die nachfolgenden Elemente verschoben werden.
Und das ist tatsächlich so, dass ich dann in
dem Array jetzt anfangen muss, die hier zu verschieben.
So, das ist eigentlich die Folge, die ich zeigen wollte.
Ich habe vergessen, die andere Folie rauszuschmeißen.
Ja, ich möchte an einer Position das einfügen.
Nee, das ist gar nicht wahr.
Ich habe hier tatsächlich, das ist ja ärgerlich.
Ich habe so schön animierte Folien. Da muss ich mal
eben ganz kurz gucken. Sorry, jetzt muss ich mal
ganz kurz gucken.
Das macht mich jetzt fertig hier
Das kommt selten vor, dass ich irgendwie die Folie,
ich habe die doch heute gerade da reinkopiert, ich bin total verwirrt.
Schade, schade, schade. Gut, ich kann es nicht ändern.
Ich kann es nicht ändern. Also das ist tatsächlich hier,
ich habe eine wunderbare Animation gehabt, die zeigt,
dass ich, wenn ich jetzt hier A,
B, C, D habe,
dann muss ich das eh eine Stelle nach unten schieben,
dann muss ich das C-Einstellen nach unten schieben,
dann das B und dann erst kann das X in
die Lücke rein
Das habe ich animiert, habe ich heute noch gesehen und
ich habe irgendwie jetzt wieder durch Kopieren von Folien, es tut mir leid.
Das entgeht euch jetzt. Könnt ihr euch im Video vom
letzten Jahr angucken? Doof. Gut, also,
dass das erstmal klar ist, das ist das eine Grundprinzip.
Ihr könnt es ja vielleicht so machen. Wir machen es,
ach, wir machen es anders. Das kann man leider nicht ganz so schön sehen,
aber ich habe ja zufällig, habe ich ja hier,
ah ne, wir machen,
also wenn ich jetzt hier schon so viel erzählt habe über meine Probleme,
dann kann ich auch jetzt euch noch richtig einweihen,
dann machen wir es jetzt so
Nehmen jetzt mal hier irgendwie SE1,
da habe ich das auf jeden Fall drin, diese Folien.
Wenn wir mal ein bisschen hier flexibel, flexibel,
flexibel, flexibel, zum, zum, zum,
zum, zum, bum, bum, bum.
Und dann bin ich aber auch gleich spontan Applaus hören für diese wunderbare Animation. So,
also hier, ja, ich habe jetzt die Situation,
ich möchte jetzt etwas einfügen, also, Moment,
Moment, das ist noch gar nicht fit.
Ich habe das Gefühl, ihr nehmt das nicht ernst hier
Also, das E, das habe ich hier sozusagen,
ich möchte das X an Position 1 einfügen.
Also, ha, jetzt müssen die da rüber und dann...
Danke, danke, danke. Ja, ich mache mir
solche Mühe, damit das hier verständlicher rüberkommt.
Und dann ist es nicht da. Da kann ich nicht angehen.
So, jetzt muss ich wieder in die andere rein.
Nee, muss ich gar nicht. So, das ist das eine.
Ja, und jetzt ist dann hinterher die Kanale 5. So,
jetzt habe ich hier die Situation, das seht ihr jetzt hier.
Ich habe sieben Elemente in dem Container drin,
also in dem Array
Und jetzt möchte jemand wieder einfügen. Er sagt wieder,
ich möchte jetzt hier das Element Z an Position 1 einfügen.
Jetzt sieht man, dass der Container voll ist.
Das Array, da kann nicht mehr rein.
Wir haben jetzt die Situation, Personalität ist gleich Kapazität.
Was müssen wir also machen? Wir müssen ein größeres Array
anlegen. Und da kann man jetzt sich überlegen,
wie groß macht man es denn? Man macht es vielleicht
mindestens ein Element größer, damit das Element auch wirklich reinpasst.
Ist relativ unschlau, weil man davon rennt,
damit rechnen kann, wenn da gerade jemand was einfügt,
dann kommt bestimmt auch noch mehr Elemente.
Also dann wäre es doof, jedes Mal,
dass Array nur um ein Element größer zu machen.
Also, was häufig gemacht wird, Es wird ein doppelt so großes Array angelegt.
Das werdet ihr wahrscheinlich auch in der Implementation so machen können.
Und dann werden erstmal alle Elemente rüber kopiert. Sensationell animiert.
Und dann muss natürlich hier wieder das runtergerutscht werden, die Elemente.
Und dann erst kann das Z da rein. Das heißt,
das sind zwei Schritte, wenn das Ding voll ist,
muss ich erstmal größeres Array anlegen, dann verschieben und dann einführen.
Ja, könnt ruhig ein bisschen klatschen.
Ich bin ja auch nur ein Mensch, ne?
Wird ja auch mal gern gelobt. So, ich glaube,
jetzt kann ich aber einfach damit auch weitermachen hier
So, und dann haben wir also tatsächlich Karnalität 8,
aber die Kapazität ist jetzt 14. Und jetzt nochmal hier auf das Beispiel.
Wir haben ja auch die Musterlösung hier. Die Musterlösung.
Jetzt haben wir hier Array-Titelliste, also die Implementation.
Ich zeige jetzt mal, man kann, das ist auch eine Zusatzaufgabe,
dass man die Kapazität, die Anfangskapazität angibt.
Und ich werde das jetzt hier mal machen,
ich will jetzt mal eine Array-List anlegen mit einer Kapazität von 3.
Damit es überschaubar bleibt. So, jetzt habe ich hier
also mein Exemplar
Der Array-Titelliste. Und ich kann ja hier wie üblich
meinen Doppelklick machen und man sieht hier,
dieses Exemplar hält eine Referenz auf ein Array von Titeln.
Und die Anzahl der Titel ist hier auch gezählt.
Das ist also die Kardinalität hier. Wie viele Titel sind drin
? Und das hier ist das Array dazu. So,
und dieses Array ist jetzt, wir können das hier mal
so ein bisschen kleiner machen.
Dieses Array ist jetzt noch leer. Ja, keine Überraschung
. Jetzt kann ich hier wieder mal
Titelbibliothek anlegen
Wieder mal meinen Jazz-Mix hernehmen. Na, komm schon.
So, dann lasse ich mir einen zufälligen Titel geben.
Ja. Herr damit, auf die Objektleiste mit dir.
So. Und diesen Titel füge ich jetzt in diese Arrailiste ein.
Also ich sage, hier füge ein. Gut,
die einzige Position, die ich jetzt wählen darf,
ist die Position null. Die Liste ist leer,
ich kann also nur an Position null was einfügen.
Ich füge also jetzt hier das Ding ein
und sage an Position null
Okay. Zack, und ihr seht hier,
dass in dem Array jetzt tatsächlich hier das Element eingetragen wurde.
Also hier sieht man jetzt, das ist irgendwas von Miles Davis.
So, das ist also das Objekt brauchen wir eigentlich nicht mehr.
So, jetzt füge ich denselben Titel nochmal ein,
füge ein, jetzt mache ich das gleiche Spiel,
ich sage jetzt hier irgendwie Titel eins und an Position null.
Und dann seht ihr,
dass er jetzt hier die eine Referenz nach unten geschoben hat,
das könnte ich nicht so gut sehen, leider,
aber er sieht jetzt, dass Array langsam sich füllt,
dieses dreielementige Array. Wenn ich jetzt das nochmal mache,
Und Widersage macht Titel 1 an Position 0,
dann ist es jetzt so weit, dass jetzt,
wenn wir jetzt ein Element hinzufügen würden,
dann wäre das Array ja voll.
Also machen wir das mal. Für ein Titel 1,
das sind also Miles Davis rund um die Uhr.
So, wir sehen nichts. Was ist denn jetzt,
was ist hier jetzt passiert? Jetzt muss er ja
irgendwie intern ein neues Array angelegt haben. Aber dieses Array
hier, das liegt so, das hängt so rum.
Tatsächlich ist es jetzt so, dass in dieser Klasse
hier Hier sieht man schon, Anzahl Titel ist vier.
Also die Kardinalität ist tatsächlich vier. In dieses Array gehen
aber nur drei rein. Was die Implementation gemacht hat,
sie hat, als sie merkte, dass das Ding voll ist,
hat sie sich ein neues Array.
Das heißt, also diese Referenz ist jetzt hier eine Referenz auf ein neues Array.
Also wenn ich jetzt hier einen Doppelklick drauf mache, zack,
dann habe ich hier mein neues Array und da sind auch die vier Referenzen drin.
Und dieses Array hier wird weggeschmissen. Also der Carbage Collector
wird das irgendwann wegräumen. Da sind ja auch nur irgendwelche Referenzen
drin. Also nichts Besonderes. Das heißt also tatsächlich,
jetzt haben wir hier ein größeres Array. Und wenn ich
jetzt wieder weiter Elemente einfüge und das Ding wird wieder voll,
muss ich wieder größeres Array anlegen und alles rüberschaufeln
Das ist das Prinzip von diesen wachsenden Arrays. So,
sehr schön. Jetzt haben wir noch eine Viertelstunde Zeit,
das finde ich super. Jetzt können wir nämlich noch eine Sache machen,
die ich auch noch mal ganz spannend finde, weil,
was ihr jetzt gesehen habt, sind zwei Implementationen.
Erste wichtige Aussage ist, beide sind korrekt. Das heißt,
es gibt irgendwie Anforderungen, was so eine Liste können muss.
Das ist im Interface beschrieben als Spezifikation in den Testklassen,
ist klar gesagt, ich muss das und
das machen können
Und beide Implementationen machen das auch vernünftig.
Also Linklist und Arealyst aus dem Plections Framework sind gültige Implementationen.
Jetzt kann man sich fragen, warum gibt es denn da zwei?
Reicht nicht eine? Ich meine, das ist ja so eine Frage,
die sich sofort stellt. Warum macht sich irgendjemand die Mühe?
Zwei Implementationen zu schreiben, obwohl ja eine erreichen würde.
Und tatsächlich ist das eine gute Möglichkeit zu sagen,
naja, macht da halt die ganze Bibliothek wird doch kleiner,
wenn man nur eine Klasse hat.
Reicht doch eigentlich eine Implementation. Und um zu zeigen,
dass es tatsächlich dann doch Unterschiede gibt zwischen diesen beiden Implements.
Und obwohl sie beide ja korrekt sind, zeige ich einfach mal zwei typische Operationen.
Eine Operation ist Einfügen eines Elementes. Und die andere Operation
ist Zugriff auf ein Element an einer bestimmten Position.
Also einfach lesender Zugriff auf ein Element und einfügen,
also irgendwie schreibender Zugriff auf die Sammlung, auf die Liste.
Jetzt gucken wir uns erstmal das Einfügen an eine Liste an.
Jetzt einfach in den beiden Implementationen. Bei einer Linklist
ist es so, wie war das noch? Genau,
wenn ich jetzt da was einfügen will, nehmen wir mal an,
wir haben irgendwie 10.000 Elemente in der Liste, 10.000 Knoten.
Und jetzt möchte ich an Position 5.000 was einfügen
Dann habe ich den Nachteil, ich muss die Position,
an der eingefügt werden soll, da muss ich erst mal hinlaufen.
Ich muss tatsächlich durch die Liste durchhühnern, die muss es traversieren.
Also da muss ich im Durchschnitt die halbe Liste durchlaufen.
Außerdem, na gut, Objekterzeugung. Ich muss so ein Knotenobjekt erzeugen,
Buch, das ist jetzt nicht so teuer. Vorteil ist aber dann,
wenn ich erst mal da bin, ist das ein Fing total einfach,
das übliche Umketten, was ihr schon gesehen habt.
Bei der Areleys ist es ein bisschen anders.
Ich muss alle Elemente nach der Position verschieben,
also bei einer Areleys, 10.000 Elemente,
an 5.000 oder was eingeschoben werden,
da muss ich 5.000 Elemente erst mal zur Seite schieben,
damit ich dann diese eine kleine Lücke habe für das
neue Element
Das ist ziemlich blöd. Da muss im Durchschnitt ungefähr die halbe Liste
umkopiert werden. Außerdem, wenn das Ding voll ist,
muss ich ja noch wieder alles umkopieren, also nicht so doll.
Okay, Vorteil, diese Position zum Einfügen,
die kann ich dann direkt anspringen. Also weil das ja
ein konstanter Aufwand ist in einem Array auf eine bestimmte Position zuzugreifen.
Der Zugriff war eine beliebige Position,
da ist eine Arrailist richtig im Vorteil.
Also wenn ich irgendwo in einer Arrailist auf eine Position zugreifen will,
geht das sehr schnell,
während bei einer Linklist muss ich halt erstmal
an die Position
mich durchhangeln
So, und jetzt ist es tatsächlich so,
es gibt nicht eine bessere Implementation. Also selbst,
wenn wir jetzt festgestellt haben, beide sind korrekt,
also in dem Sinne gleichwertig, könnte man ja sagen,
ja, die eine ist immer schneller.
Leider auch nicht. Im einen Fall ist die eine schneller,
im anderen ist die andere schneller. Das heißt,
wenn ich relativ konstante Listen habe,
also die sich in ihrer Kardinalität nicht ändern und bei denen
ich häufig wahlfrei auch bestimmte Positionen zugreifen muss,
dann ist eine Arrailist super geeignet.
Wenn ich aber eine dynamische Liste habe, also auch große Listen,
wo ich ständig irgendwie elemente hinzufüge und entferne,
dann Ist eine Linklist möglicherweise besser,
weil ich diese ganze Kopiererei in deinem Array,
die ich nicht machen muss.
Das heißt, es hängt vom Umgang ab.
Also nicht nur vom Umgang von der Schnittstelle,
was das Ding an Operationen anbietet, sondern auch,
wie man diese Operationen benutzt.
Wie ein Klient, die benutzt. Und die Pragmatik ist eigentlich für Java,
für die meisten Anwendungen mit ihrer kleinen Listen,
ist die Airraylist eigentlich eher so die bessere Wahl.
Aber, und das ist ja auch ganz schön,
das ist ja schön, ich habe ja die Musterlösung hier.
Ich habe hier die Musterlösung.
Ah, toll. Also muss ich jetzt nochmal eben nochmal
aufmachen hier
Ich habe die alte Musterlösung hier noch. Also ich bin
perfekt organisiert heute. Das ist die Musterlösung. Und hier
gibt es eine klasse Effizienz-Tester. Und so ein Effizienz-Tester,
der macht nichts weiter als zwei, also als ein Exemplar,
so einer Array-Liste oder einer, also eine Exemplar,
also etwas, was die Titelliste implementiert zu testen.
Ich zeige mal den Test hier, das ist relativ einfach.
Der benutzt erstmal die Titelbibliothek mit dem Jazz-Mix,
lässt sich einen zufälligen Titel geben, dann wird die Liste gelehrt,
also hier wird eine Liste als Parameter übergeben,
dann wird die erstmal gelehrt,
dann werden hier irgendwie 10.000 Elemente reingefügt mit Füge ein,
ja, dann enthält die Liste 10.000 Elemente.
Jetzt kommt das übliche Spiel, was ihr schon kennt,
jetzt nun mal irgendwie die Startzeit, ja,
und jetzt statte man das Einfügen von 10.000 Titeln an der ersten Position.
Okay, zugegeben, das ist so der Worst Case für eine Arraylist,
ja, dass man immer an Position 1 was einfügt,
da muss ich nämlich alle bestehen,
bereits eingefügte Elemente muss ich ja immer um eins verschieben.
Das wird jetzt 10.000 Mal gemacht, das heißt,
hinterher enthält diese ganze Liste 20.000 Titel. Ja, so,
und dann wird das hier irgendwie gemessen, wie lange das gedauert hat,
und dann wird hier 10.000 Mal Zugriff auf den mittleren Titel,
wobei er mittlere ist, ist hier nicht ganz richtig,
es sind ja irgendwie, hier auf gibt,
Titel 5.000 zugegriffen wird.
Das könnte man eigentlich nochmal ändern. Da macht man hier
mal wirklich 10.000. Weil da sind ja 20.000 drin.
Also ist das hier ein gültiger Index? Ja,
das wird auch gemessen. Und dann sieht man einfach,
wenn man das jetzt mal ausführt, ich übersetze es erstmal.
Das lässt sich übersetzen. Ich kann jetzt mir hier
einen Exemplar der Aerylist bauen lassen. Ja, das ist
auch egal. Der soll erstmal mit irgendeiner Größe anfangen.
Und wenn ich jetzt den Effizienztester hier auf Hochhof und sage,
vermesse doch mal eine Aerylist.
Dann sieht das so aus
Dass das Einfügen von 10.000 Titeln an der ersten Position schon eine Weile dauert.
Also irgendwie 4.300 Millisekunden. Ist ja klar.
Bei einer Arealysmus ja ständig das ganze Zeug verschoben werden.
Aber diese 10.000 Zugriffe auf den Titel in der Mitte,
das geht total schnell. Innerhalb von sieben Millisekunden. Wenn ich
jetzt diesen gleichen Effizienztest dann mal mit einer Linklist aufrufe,
also der gleiche Umgang, dann ist diese Implementation deutlich anders.
Also wenn ich jetzt sage, hier, mach doch mal,
dann kommt da sowas raus
Also hier ist es so,
das Einfügen an der ersten Position ist total schnell,
weil er muss ja immer nur ein Element erzeugen, das vorne einklinken.
Das ist total leicht. Dieses Einketten kostet ja nichts.
Aber der Zugriff auf den mittleren Titel, das dauert richtig lange,
weil er muss jedes Mal durch die ganze Liste sich durchhühnern,
um da hinzukommen zu der Position.
Das heißt also, die sind genau gegenläufig. Das heißt,
je nachdem, wie ich so eine Liste benutze,
ist mal die eine, mal die andere Implementation besser.
Und das ist, glaube ich, ganz spannend,
dass ihr hier jetzt in SE1 zum einen gelernt habt,
naja, der Umgang ist das eine, was ist eine Liste überhaupt?
Duplikate, Reihenfolge und so weiter. Und dann aber auch,
es gibt da unterschiedliche Implementationen und die sind,
je nachdem, was man will,
ist die eine besser mal
die andere
Dieser Vater ist ja allerdings grüßlich so, dass er kauft,
aber das ist so 10.000 Eindrücke, die da drunter standen.
Das hat er jetzt nicht, was er für eine mentale Chance hat.
Die Frage ist, ob es wirklich 10.000 Elemente waren.
Das ist tatsächlich vollkommen wurscht bei einer Liste. Es ist immer das gleiche Element gewesen,
aber es sind tatsächlich 10.000 Elemente drin, ja.
Also davon kann man hundertprozentig ausgehen. Der benutzende Code,
den habt ihr gesehen, in diesem Effizienztester,
der macht wirklich 10.000 Mal Füge ein.
Und dann werden da halt irgendwelche Referenzen eingefügt. Das sind also sehr,
sehr große Listen, wobei sehr groß, ich meine,
wenn ihr wisst, was Google so macht,
dann ist das ein Witz,
dann sind das keine großen Listen.
Da ist auch eine Frage
Und die Mitglieder des Daten, dann schreibt mir noch ein Rätsel,
sagen sie, das kann man noch halten,
wenn man im Lieblingsziel der Termine zueinander sprechen möchte.
Ja, also er schlägt vor bei einer verketteten Liste,
dass man da nicht nur sich Referenzen hält auf den Anfang und das Ende,
sondern vielleicht auch irgendwie Referenzen so in die Hälfte und in die Viertel und so,
sodass man dann schneller an bestimmte Positionen kommt.
So, und da geht's jetzt los, ja, das für studierte Informatik.
Das ist tatsächlich, natürlich gibt es alles. Es gibt sogenannte Skip-Lists,
das heißen die, wo dann genauso Referenzen irgendwie noch dazwischen gehalten werden.
Aber die Referenzen müssen ja wieder irgendwo stehen. Und wenn die Liste größer wird,
dann ist irgendwann ein Viertel auch nicht mehr genug,
dann musst du es auch an Achteln und Sechzehnteln haben und
diese Referenzen müssen ja wieder irgendwo stehen und dann wird es relativ schnell kompliziert.
Also man kann das alles machen und da ist auch alles mögliche gebaut worden,
wie gesagt, die Welt der Algorithmen ist riesig und der dynamischen Datenstrukturen ist eine gute Idee und wie gesagt,
das ist auch von vielen so gemacht,
aber das ist erstmal nur, dass ihr das Grundverständnis habt,
wie solche Verkette das überhaupt funktionieren.
Sonst noch Fragen jetzt, da ist noch eine Frage. Ja,
und die zwei Fragen auch zum Beispiel nicht weiter,
jetzt hier Funky May, die zweitwegs noch als Punkt,
so wie kriegst du das für den Wissen,
die Junge jetzt da.
Er sagt, was man ja auch beachten muss,
ist den Speicherbedarf, richtig? Ich meine,
wir gucken ja in der Informatik nicht nur auf die Geschwindigkeit,
sondern auch auf den Speicherbedarf. Und es ist ja so,
bei einer Ray-List, wenn da irgendwie 10.000 Elemente drin sind,
braucht man einen Array, wo 10.000 Elemente reinpassen, mindestens.
Also die Kapazität muss es haben. Und wenn man dann
wieder Elemente entfernt, dann wird das Array ja da bleiben.
Es kommt dann wieder auf die Implementation an,
ob man beim Löschen auch wieder das Array verkleinert.
Also ob man dann irgendwann wieder feststellt, oha,
das wird man meistens nicht tun aus Effizienzüberlegungen,
aber der Speicherplatz wird dann belegt von dem
nicht benutzten Array
Auch das sind so Überlegungen, die man mit anstellen kann, ganz genau.
Sonst noch Fragen jetzt hier zum Vergleich dieser beiden Listen-Implementationen.
Okay. Dann das hier noch mal so ein bisschen im Vergleich,
also LinkedIst, Knoten, verkettet, indizieren durch Abzählen,
durch Durchlaufen, löschen jeweils nur ausketten.
Arrailist, sehr viel schneller, direkt indizierbar. Aber einfügen
und löschen erfordert immer umkopieren und eventuell sogar alles umkopieren. So,
jetzt haben wir noch, naja, so eigentlich zehn Minuten,
wenn wir die fünf Minuten der Pause noch mit dazunehmen,
Um nochmal uns zu überlegen, was haben wir jetzt hier eigentlich gemacht?
Wir haben jetzt relativ systematisch uns überlegt, wie schnell sind denn diese Implementationen?
Und das ist nichts, was irgendwie wir als Erster hier tun,
sondern das ist halt in der Informatik schon sehr,
sehr viel gemacht worden.
Und die Informatiker haben in 14, in den letzten 40 Jahren
unter anderem den Aufwand für solche Operationen auch formalisiert.
Das ist dann das spannende Gebiet der Komplexitätstheorie in der Informatik
und das werdet ihr auch in den formalen Grundlagen dann kennenlernen.
Und die Ideen will ich jetzt hier kurz andeuten
Also das ist einfach diese, die Grundidee ist, der Aufwand,
was ist denn der Aufwand? Und der Aufwand ist natürlich,
ja, wie viele elementare Operationen müssen ausgeführt werden?
Das sind nicht hier so diese elementaren Schritte. Die zählt man einfach,
um dann sagen zu können, naja,
für die Operation jetzt zum Einfügen brauche ich halt so und so viele tausend Operationen oder elementare Schritte.
Und elementare Schritte können solche Zuweisungen sein,
solches Erhöhen von Zählern oder Vergleiche,
die hier so dastehen oder auch Aufrufe mit konstantem Zeitbedarf,
also zum Beispiel so einen neuen Knoten erzeugen,
das ist eine kleine Operation mit konstantem Aufwand.
Und die kann ich ja
alle zählen
Also zum Beispiel,
wenn ich jetzt einfügen in Position I mache auf einer verketteten Liste,
dann erstmal kann ich die Schritte zählen, die ich brauche,
um sozusagen an an die Stelle zu kommen und dann das umketten,
kann ich auch zählen.
Und das Erzeugen des neuen Knotens. Was man dann üblicherweise macht,
wenn man sich den Aufwand so systematisch anguckt, ist,
dass man sagt, naja,
es gibt einen konstanten Anteil und einen varianen Anteil.
Wieder verkettete Liste, konstant ist immer dieses Erzeugen des Knotenelements.
Das ist immer gleich. Variabel ist,
wie oft ich jetzt diesen Next-Referenz durchlaufen muss,
um an die richtige Stelle zu kommen.
Das hängt davon ab, wie groß die Liste ist und
welcher Index jetzt als Parameter wirklich vorliegt.
Das ist also variabel
Und üblicherweise abhängig von der Größe der Liste,
also von der Anzahl der zu verarbeitenden Elemente. So,
und dann macht man gerne so Abschätzungen im schlechtesten Fall,
also zum Beispiel bei, wenn ich in einer Liste ein Element suche,
dann ist das im Durchschnitt,
muss ich so die Hälfte der Liste durchsuchen.
Im schlimmsten Fall muss ich die ganze Liste durchsuchen,
wenn es gar nicht drin ist. Da muss ich wirklich
jedes Element angucken und feststellen, ob das da ist.
Das heißt also so, man oft macht man so
Abwägungen im Mittel oder eben halt auch im schlechtesten Fall.
Und üblicherweise wird halt von großen Datenmengen ausgegangen. Und das ist ganz wichtig,
dass ich diesen konstanten Anteil relativ schnell vernachlässigen kann und einfach nur,
wenn das irgendwie 10.000 Elemente sind,
dann ist dominierend natürlich dieses Durchlaufen und nicht mehr dieses Einketten.
Und dann wird eigentlich der Aufwand zu einer Funktion,
die abhängig ist von der Anzahl der Elemente, die verarbeitet werden.
Also hier eine Funktion F von N und N sind halt
die Anzahl der Elemente, die da verarbeitet werden.
Und das ist halt alles, was in der Komplexitätstheorie dann intensiv untersucht wird.
Und was ihr dann hier schon mal so ein bisschen jetzt mitbekommt,
ist die sogenannte O-Notation. Das ist einfach eine Notation,
wie die Komplexität von bestimmten Operationen beschrieben werden kann.
Also sowas wie, was einen konstanten Aufwand hat,
bezeichne mal üblicherweise mit einer nur von eins. Das ist diese große O-Notation,
die ich hier nicht einführen werde, weil das, sagen wir,
relativ schnell sehr weit in die Zahlentheorie reingeht und auch in Funktionentheorie.
Aber die Grundidee ist eben, dass man zum Beispiel sagen kann,
es gibt Algorithmen und auch Datenstrukturen, die erlauben einen lorethmischen Aufwand.
Zum Beispiel in einem Baum kann ich ein Element üblicherweise in
lorethmischer Zeit finden, wenn ich es suche.
Oder linearer Aufwand, suche in einer Liste, haben wir gerade gesehen.
Also wenn ich in einem Element in einer Liste was suchen
will, dann brauche ich halt linearen Aufwand.
Also ist das abhängig von der Länge der Liste
Oder gutes Sortierverfahren, haben N-My Login,
nicht so gute Sortierverfahren haben, ein Quadrat, als Ordnung.
Und dann gibt es auch so Dinge hier, exponentieller Aufwand.
Und das sind dann so Probleme, also hier vielleicht als
ein Problem, nicht Depotenzmenge zu bilden von einer Menge.
Das hat als Algorithmus einfach exponentiellen Aufwand. Und da werdet ihr
relativ schnell demütig. Also wenn ihr schnelle Rechner habt,
wenn ihr richtig stolz seid auf den Boliden,
den ihr zu Hause unter eurem Schreibtisch stehen habt,
diese Algorithmen hier unten, auch die quadratischen schon,
aber auch die exponentiellen erst recht,
die knacken euch jeden Computer.
Also ihr könnt ja irgendwie so noch
so schnell sein
Dann muss trotzdem der schnellste Supercomputer dieser Welt immer noch tausende
Millionen von Jahren rechnen. Wenn das Problem nur groß genug
ist. Das ist einfach so. Und das werdet ihr
dann auch in der Komplexitätstheorie so ein bisschen genauer untersuchen.
Nur einfach jetzt noch mal hier Komplexitäten der Listenoperationen.
Also Linked List am Einfügen hat die Ordnung lineare, nee,
Haareaufwand, also O von N. Bei beiden, O von N
, weil in der Linked List muss ich halt durchlaufen.
Bei der Arealist muss ich ja eventuell Elemente verschieben.
Und der Zugriff auf ein Element über einen Index,
da ist natürlich die Linklist wieder lineare Aufwand.
Ich muss erst einmal hinlaufen, um das Element zu kriegen
Aber die Arealys hat einen Aufwand O von 1, also konstanter Aufwand.
Ich kann sehr schnell an das Element ran. So,
und dann gibt es natürlich noch hier Testaufenthalten sein.
Das ist die lineare Suche, habe ich eben schon gesagt.
Ich will wissen, ob ein Element in einer Liste drin ist,
dann muss ich tatsächlich das Element, was ich da drin suche,
mit jedem Element vergleichen.
Es kann natürlich sein, dass ich es finde,
das kann früher sein, wenn es aber nicht drin ist,
muss ich die ganze Liste durch sind.
Nennt sich lineare Suche und auch offensichtlich ist das abhängig von der Größe der Liste.
Das heißt, da ist der Aufwand dann O von N.
Und nur ein kleiner Ausblick jetzt für die nächste Woche,
dann nämlich noch mal richtig spannend.
Es gibt bessere Implementationen für die Suche, um festzustellen,
ob in einer Menge ein Element schon drin ist.
Da gibt es Möglichkeiten, das in logarithmischer Zeit
zu kriegen oder sogar mit konstantem Zeitaufwand, mit Hash-Verfahren.
Und das ist richtig heißer Scheiß und der kommt nächste Woche.
Und das ist die Zusammenfassung und wir können Schluss machen.
Vielen Dank