Alles klar. Hat ja geklappt mit acht Minuten Verspätung.
Ich muss dann vielleicht fünf Minuten länger machen heute.
Ich hoffe, das wird euch nicht zu sehr stressen.
Ich werde versuchen, ich werde versuchen, die Zeit wieder rauszuholen.
Okay. Dann gibt es immer wieder die Frage zur Modulprüfung,
die will ich jetzt mal beantworten.
Also, wir haben eine Modulprüfung für SE1 in schriftlicher Form.
Das heißt, wir schreiben eine Klausur. Es gibt
zwei Klausurtermine für dieses aktuelle SE1-Modul, für diese Durchführung.
Ein Termin ist am 4. Februar. Das ist direkt der
erste Tag der vorlesungsfreien Zeit
Und ein zweiter Termin ist am 7. März. Das heißt,
wer durch den ersten Termin durchgefallen ist, sollte den zweiten möglichst schnell mitschreiben.
Die liegen jetzt relativ dicht beieinander, sonst sind wir eher
so Mitte, Ende März dran, dem zweiten Termin.
Die Terminplanung haben wir nicht im Griff. Dauer jeweils zwei Stunden,
das heißt, ihr solltet dann zwei Stunden mindestens Zeit haben,
diese Klausur zu schreiben.
Ihr dürft nur einen Stift benutzen, keine anderen Hilfsmittel.
Das Ganze hat eine Multiple-Choice-Klausur, das heißt,
ihr habt entweder auf eine Frage 4 Antworten.
Und eine davon ist richtig, oder es gibt N-
Antworten und davon sind M, richtig.
Oder N, aus M oder wie auch immer.
Also das gibt es, das ist auch immer markiert.
Also wir werden bei jeder Frage sagen,
eine richtige Antwort oder mehrere richtige Antworten möglich.
Es gibt eine Probeklausur, das habe ich jetzt hier versprochen.
Ich glaube, die steht noch nicht im Komsi,
die wird aber heute oder morgen im Komsi stehen.
Außerdem haben wir ja in diesem Semester erstmalig selbst Tests angeboten zu den verschiedenen Stufen.
Level 1 haben bisher 150 Leute wirklich abgeschlossen. Wahrscheinlich sind
das ungefähr die 150, die hier sitzen, vermute ich mal.
Dann haben ungefähr 50 Level 2 durchgeführt Und Level 3 kommt
am Montag. Und Level 4 kommt auch noch im Januar.
Nutzt das, weil die Fragen sind sehr ähnlich zu den Klausurfragen.
Aber es ist eigentlich so gedacht, dass ihr das Semester begleitend macht.
Das heißt, dass ihr eigentlich schon am Ende von Stufe 1 merkt,
oh Mensch, ich muss ein bisschen mehr tun.
Wir werden ja hier immer so ein bisschen,
stehen da immer in Konkurrenz zu anderen Lehrveranstaltungen,
die dann so fiese Dinge tun wie Zwischenklausuren anbieten.
Und dann heißt das immer, ah, wir haben gar keine Zeit,
wir müssen für die Zwischenklausur und Mathe lernen.
Das ist ein bisschen schade.
Ja, gut
Was ich auch immer sagen kann, Erfahrung der letzten Jahre,
die SE1-Klausur ist sehr gut schaffbar,
wenn man in den Übungen vernünftig mitgearbeitet hat.
Also wenn man hier wirklich alle Übungswetter bearbeitet hat,
auch selbstständig bearbeitet hat, ist alles ganz gut verstanden hat,
was man da gemacht hat, dann sollte man kein Problem haben,
die Klausur zu bestehen.
Es wird vermutlich nicht so leicht sein,
eine sehr gute Note zu bekommen, aber es bestehen sollte kein Problem sein.
Gibt es noch Fragen zur Modulklausur? Zur Modulprüfung.
Da ist eine Frage. Muss man sich da anwelden oder
sind wir das? Das ist eine sehr gute Frage.
Muss man sich da anmelden oder seid ihr automatisch angemeldet?
Das weiß ich ehrlich gesagt nicht so genau. Das hat
sich nämlich immer wieder geändert, Zeit lang war es so,
dass man, dass die Studierenden automatisch angemeldet waren zum ersten Termin.
Am besten ist, ihr schaut mal in Stine rein,
weil da steht es drin.
Da werdet ihr dann sehen, ob ihr angemeldet seid.
Wenn ihr nicht angemeldet seid, meldet euch an.
Ich würde immer empfehlen, den ersten Termin mitzuschreiben.
Dann habt ihr eine Chance, den zweiten, falls es versemmelt,
noch zeitnah zu machen und nicht erst ein Jahr zu warten.
Das ist nicht so lustig. Der Übungsschein ist übrigens
keine Voraussetzung für die Klausurteilnahme. Das ist aber empfehlenswert,
den Übungsschein zu haben. Weil, wie gesagt,
wenn man die Übung gemacht hat,
ist auch die Klausur
relativ leicht
So. Okay, sonst noch Fragen. Ich habe das Gefühl,
dass da ziemlich viel geredet wird. Alles klar und
so weiter. Alle Unklarheiten beseitigt. Gut. Okay,
dann geht es jetzt weiter mit einem Teil der Informatik,
der relativ wichtig ist.
Es geht nämlich jetzt um Datenstrukturen. Es geht auch so
ein bisschen um Algorithmen. Algorithmen erstmal wenig,
aber vor allen Dingen
dynamische Datenstrukturen
Und zwar ist schon ziemlich wichtig,
dass ihr erstmal Sets und Lists aus dem Collections
Framework kennenlernt und hoffentlich auch schätzen lernt,
dass die eben halt euch sehr nützliche Dienste leisten können.
Ihr solltet die als erstes kennenlernen aus Benutzungssicht. Wie kann man
die sehr leicht, sich Variablen deklarieren mit dem richtigen Elementtyp?
Wie kann ich mir eine richtige Implementierung raussuchen?
Und wie kann ich die auch gewinnbringend einsetzen? Das ist
alles das, was ihr in den letzten Wochen gemacht habt.
Und jetzt die Level 4 Vorlesungen sind ja überwiegend damit beschäftigt,
mal hinter die Kulissen zu schauen,
wie solche Sammlungen gebaut sind
Eine Voraussetzung haben wir letzte Woche gelegt. Wir haben I
Rays durchgenommen. A Rays sind eine sehr,
sehr elementare Datenstruktur, um Datenstrukturen zu implementieren, dynamische Datenstrukturen.
A Rays sind nicht so wahnsinnig dynamisch. Und ja,
das hat in der Informatik eine relativ große Tradition, dieser Bereich, dynamische Datenstrukturen.
Algorithmen und Datenstrukturen, es gibt sogar eine eigene Vorlesung,
die ihr besuchen könnt als Wahlpflichtveranstaltung, wo das alles nochmal vertieft wird.
Wir machen jetzt hier in SE1 so einen ersten Einstieg.
Wir haben ja so ein bisschen die Struktur,
dass wir nicht Java komplett vorstellen in SE1, also zum Beispiel Vererbung, Exceptions.
Es gibt so ein paar Dinge,
die machen wir erst im Zweiten Semester in SE2 und
weil wir das Tempo hier natürlich nicht irgendwie jetzt entsprechend verlangsamt haben,
haben wir halt jetzt hier in den letzten Wochen Luft,
um Dinge zu tun, die häufig in anderen Hochschulen erst im zweiten Semester gemacht werden.
Also so ein bisschen so Grundlagen von Algorithmen und Datenstrukturen.
Wir betonen jetzt hier so,
dass wir das eher dynamische Datenstrukturen nennen und ich werde gleich so ein bisschen erklären,
was damit gemeint ist, dynamisch und Datenstruktur.
Gut. Ihr habt List und Set als Interfaces kennengelernt aus
dem Collections Framework. Da gibt es einige Implementationen.
Ihr habt mindestens vier schon mal irgendwie gesehen.
Also zwei für List und zwei für Set. Und alle,
wenn man jetzt raufguckt, wie die gebaut sind,
basieren auf zwei grundlegenden Konstrukten, nämlich auf Race und auf verketteten Strukturen.
Und verkettete Strukturen, damit es gemeint, verkettet über Referenzen.
Und ja, ich werde nicht müde zu sagen,
ohne Referenzen ist das alles hier schwer verständlich.
Diese elementaren Konzepte von Programmiersprachen,
also Race und verkettete Strukturen über Referenzen in Java,
die werden dann geschickt kombiniert und dann kommen so Dinge raus wie verkettete Listen,
wachsende Arrays,
balancierte Bäume,
Die werden wir uns in den nächsten zwei Wochen genauer ansehen.
Heute gucken wir uns an, verkettete Listen und wachsende Arays.
Und in der nächsten Woche dann balancierte Bäume und Hash-Verfahren.
Und das sind Dinge, die wir dynamische Datenstruktur nennen.
Warum? Naja, also erst mal ist es irgendwie eine Struktur.
Das heißt, es sind mehrere Dinge, die miteinander verknüpft sind.
Ich habe es mal so angedeutet, so gelbe Kästchen mit Feiern verknüpft.
Die Pfeile könnt ihr fast immer auf unseren Folien als Referenzen deuten,
wenn es so solche Veranschaulichungen sind,
wenn es nicht gerade
direkt UML ist
Und naja, eine Struktur ist erstmal ein Gebilde aus Elementen ums Beziehung.
Das heißt, diese Beziehung üblicherweise Referenzen, die Elemente üblicherweise Objekte.
Wir reden ja hier über dynamische Datenstrukturen in Java in einer objektorientierten Sprache.
Damit üblicherweise, die heißen dynamisch, weil sie geändert werden können.
Das heißt, da kann ich ein Element rausnehmen,
ich kann ein Element hinzufügen, ich kann Elemente umarrangieren,
all das macht diese Strukturen so dynamisch.
Und was ganz wichtig ist, ist, dass die häufig,
wie das hier so schön heißt, gleichartig rekursiv aufgebaut sind.
Das heißt, wir werden uns ansehen, dass diese Daten Strukturen strukturell rekursiv sind.
Das ist etwas anderes als die funktionale Rekursion, die ihr bisher kennengelernt habt.
Das werden wir uns aber dann auch heute mal ein bisschen genauer angucken.
Okay, dann kann man das nochmal wieder so ein bisschen
genauer einteilen. Also wir haben jetzt hier,
wie gesagt, so diese Überlegungen von wegen Listemenge,
Baum, Graf, also so Elemente,
die erstmal so keine Relation zueinander haben in der dynamischen Datenstruktur.
Während bei Listen ist das oft so,
dass der irgendwie die Vorgänger und Nachfolger verknüpft sind.
Bäume, da sieht man ganz deutlich,
dass es da irgendwie so Verknüpfungen gibt zwischen den Knoten
eines Baumes und bei Grafen ist es natürlich
auch so
Wir gucken uns als erstes an, wie man Listen implementieren kann.
Listen, grundlegende sequenzielle Struktur, sehr einfach. Alles das,
was ich heute erzähle, klingt in der Vorlesung unglaublich einfach.
Ich kann euch versprechen, wenn ihr dann in den Übungen versucht,
diese Dinge zu bauen, sieht das anders aus.
Die Erfahrung haben wir einfach die letzten Jahre immer gemacht.
Hier auf der Folie so ein paar Böbbel,
die miteinander mit Fallen verknüpft sind, super easy,
ja, mache ich auch.
Und dann ist das in der Implementation oft nochmal so ein bisschen,
also dieses Abbilden in den Code ist etwas,
was ihr erst lernt und auch hier erst lernen braucht.
Also wir erwarten nicht,
dass ihr das jetzt aus dem Ärmel schüttelt
Was wir jetzt vorbereiten hier mit den Implementationen für Listen und
wie man sie implementiert, ist auch,
dass wir uns ähnliche oder Verwandte, Datenstrukturen oder auch Datentypen ansehen.
Also so Sammlungsarten wie Stacks und Cues, Stapel oder Schlangen,
Warteschlangen und ähnliches, die basieren letztendlich auch auf diesen Konzepten.
So, heute, wie gesagt, einmal Verkettung und einmal Race.
Aber ich will zur Erinnerung nochmal jetzt ein bisschen deutlich machen.
Heute bauen wir Listen. Nächste Woche bauen wir Set-Implementationen.
Was ist der Unterschied zwischen einer Liste und einem Set
Ihr habt das von mir schon ein paar Mal gehört.
Eine Liste kann beliebig viele Elemente enthalten.
Das ist bei unseren Sammlungen im Collections Framework allgemein so.
Das ist einfach eine sinnvolle Forderung, dass die Dinger erstmal
konzeptionell unbegrenzt sind. Es gibt einen Index, das heißt,
ich kann auf Positionen in der Liste zugreifen,
wenn ich etwas einfüge, werden die nachfolgenden Elemente im Index erhöht.
Und wenn etwas entfernt wird, entsprechend andersrum.
Und ganz oft braucht man die Informationen, ob ein Element
in einer Liste drin ist, also der Test darauf enthalten sein.
Das klingt immer so schön theoretisch. Ich habe mal ein
etwas praktischeres Beispiel mitgebracht. Möglicherweise kennen einige von euch iTunes,
das ist eine Software, mit der man unter anderem
Musikdateien verwalten kann
Und wenn ich jetzt hier, dieses neue, fiese iTunes,
da muss ich jetzt hier irgendwie neue, so,
ich möchte eine neue Wiedergabeliste anlegen, eine Wiedergabeliste.
Das Ding heißt Liste und es ist tatsächlich genauso eine Liste,
wie wir sie hier haben. Wenn ich jetzt sage,
also SE1, Vorlesung 11, so halt das Ding heißen.
So, dann kann ich jetzt hier anfangen,
irgendwie auf Titel zu klicken und sage jetzt irgendwie, boah,
ich will mal jetzt so ein paar Titel hier reinziehen,
Amy Winehouse, Rehab, okay, back to Black, auch sehr schön.
Buuh, ja, love is an losing game.
So, was ich hier gemacht habe, ist,
ich habe Elemente in die Liste reingeworfen. Und ihr habt auch gesehen,
wen ich jetzt hier dieses Element hier rüberziehe,
dann habe ich hier tatsächlich so blau hervorgehoben,
die Position, an denen ich etwas einfügen kann.
Also ich kann es bei drei Elementen, die drin sind,
an Position 0, 1, 2, 3 einfügen.
Toll. Soweit so gut. Das ist genau das,
was wir von einer Liste erwarten. Das heißt,
ihr könnt davon ausgehen, dass hinter den Kulissen von iTunes
auch letztendlich Listen verwendet werden, die genau diese Eigenschaften haben.
Und was jetzt dazu kommt, ist,
wenn ich sowas mache wie Rehab, hier nochmal rüberzuziehen,
dann kommt nämlich diese Warnung hier
Es werden Duplikate zur Wiedergabeliste hinzugefügt, sensationell.
Unsere Begriffe, ja, haben sie uns geklaut.
Müssen wir gleich mal Apple verklagen. Duplikate,
wie können die einfach unsere Begriffe verwenden? Also tatsächlich,
da ist ein Element schon drin, ich will es erneut einfügen.
Jetzt fragt mich iTunes, soll das eine Wiedergabemenge sein oder
eine Wiedergabeliste? Bei einer Liste sind natürlich Duplikate zugelassen.
Also sage ich hier hinzufügen. Und jetzt kann ich
auch tatsächlich natürlich hier das Ding verschieben.
Ich kann also irgendwo hier die Reihenfolge verändern. So,
manchmal möchte ich einen Titel zweimal hören in meiner Wiedergabeliste,
was auch immer. Also das ist eine echte Liste,
wo ich über Positionen Dinge manipulieren kann.
Das ist realistisch nützlich
So, okay, also jetzt wollen wir so ein Ding bauen,
Listenimplementation im Java Collection Strange Work, da gibt es zwei,
mindestens, aber erstmal diese beiden gucken wir uns an,
Linked List und Arraylist, sind Implementationen vom Interface List.
Wie gesagt, Linked List verkettete Liste, Arraylist basiert auf wachsenden Arrays.
Wir gucken uns zuerst verkettete Listen an und das ist auch so die bekannteste,
das bekannteste Beispiel für eine sequenzielle dynamische Datenstruktur.
Was ich auch noch sagen muss in dieser Vorlesung,
dass ich gerne immer wieder sage in dieser Vorlesung, ist,
dass wir euch das jetzt nicht erklären, damit ihr,
wenn ihr das nächste Mal eine Liste braucht,
denkt, oh, ich kann das jetzt ja selber,
ich baue mir einfach meine eigene Liste, nein,
ihr sollt schon die Listen verwenden, die im Collections Framework sind.
Ja, das ist schon richtig. Wir machen das hier in erster Linie,
damit ihr die Dinge nochmal üben und vertiefen könnt,
die wir vorher eingeführt haben.
Also Race und Verkettung mit Referenzen. Das sind die Dinge,
die hier exzessiv jetzt benutzt werden. Es geht also eher
um den Übungseffekt für euch. Es geht weniger darum,
dass ihr irgendwann dann selber solche Collection Frameworks baut.
Ich will das nicht absprechen. Vielleicht seid ihr irgendwann ein
paar Jahren so weit, dass ihr sowas bauen könnt.
Aber das, was da im Collections Framework von Java
steckt,
ist schon
ziemlich gut
Dass es von ziemlich guten Leuten gebaut und das
noch besser zu machen, ist nicht so leicht.
Und es ist so ein klassisches Berufskrankheit bei Informatikern,
dass sie gerne von Grund auf Dinge bauen,
statt das zu benutzen, was schon da ist.
Und deswegen würde ich euch immer empfehlen, benutzt die Dinge aus
dem Clections-Framework. Also das nur nochmal als kleiner Disclaimer.
Wir gucken uns jetzt trotzdem an, wie sie gebaut werden.
Endliche Folgen eines Grundtyps, ja, klar,
eine Liste haben wir ja schon mehrfach gesehen.
Es gibt eine Reihenfolge und es können Dinge mehrfach enthalten sein, Duplikate.
Was erstmal ganz wichtig ist, ist diese Grundstruktur hier,
wir haben Elemente und die sind irgendwie verbunden über Ach, das ist schön.
Die sind verbunden über Verkettungsmöglichkeiten, über Referenzen in Java.
Die einfachste Möglichkeit ist eine einfach verkettete Liste. Jedes Listenelement
hat eine Referenz auf sein Nachfolgerelement. Und das heißt,
ich kann diese Liste elementweise vom Anfang bis zum Ende tranversieren.
Traversieren heißt durchlaufen. Das heißt also,
wenn ich eine Referenz habe auf dieses Element,
dann kann ich irgendwann auch auf dieses Element zugreifen.
Ich habe aber nicht unbedingt eine Referenz auf dieses Element und auch nicht auf dieses,
sondern wenn ich einen Referenz habe auf das erste Element,
dann komme ich an alle anderen dran,
indem ich durch diese Liste durchlaufe.
Bei einer doppelt verketteten Liste, Überraschung, ist das nicht viel anders.
Da ist es lästig. Da ist es so,
dass jedes Element nicht nur seinen Nachfolger kennt, sondern auch seinen Vorhänger.
Die Liste kann dann elementweise in beide Richtungen travisiert werden.
Auch das ist erstmal so, dass hier steht, relativ einfach.
Wenn wir das jetzt tatsächlich in einem objektorientierten System bauen wollen,
dann muss ich mir ein paar Dinge überlegen.
Das erste ist, ich werde sicherlich irgendetwas haben,
irgendeine Klasse, die dem Klienten gegenüber, als die Liste auftritt.
Wenn ihr eine Listen-Implementation benutzt, zum Beispiel LinkedIst aus dem Clections-Framework,
dann ist das genau die Klasse, die steht stellvertretend für die ganze Sammlung.
Das ist also ein Objekt, das die Liste insgesamt für
die Klienten repräsentiert. Dann gibt es sogenannte,
die sind jetzt hier auf den Folien heißen, die Kettenglieder.
Und diese Kettenglieder, die sorgen dafür, dass man
mehrere Elemente irgendwie verwalten kann. Für jedes Element,
das man in der Sammlung halten will, gibt es ein solches Kettenglied.
Und dann gibt es natürlich noch die Objekte,
die als Elemente in der Liste gespeichert sind
Die sind hier mal ein bisschen anders gekennter. Ihr seht,
das ist hoffentlich erkennbar, das ist so grau und das hier sind blaue,
die Elemente sind eigentlich speziell in Java, ist das so.
Die Elemente sind nicht in der Sammlung drin,
sondern in der Sammlung sind Referenzen auf die Elemente.
Ja, ganz wichtig. Das heißt, wenn ein Eintrag in einer Liste gelöscht wird,
heißt auch noch lange nicht, dass das Element auch gelöscht wird.
Das ist einfach, muss man unterscheiden. Gut, ja,
wenn wir uns das jetzt mal genauer angucken und das ist auch das,
was ihr in den Übungen euch ansehen werdet,
dann wird das so sein, dass ich dieses Kettenglied,
diese Klasse für das Kettenglied,
so entwerfe, dass ich eine eigene Klasse dafür habe,
also ein eigenes Objekt,
ein Objekttyp entwerfe
Jedes Kettenlied enthält eine Referenz auf das eigentliche Element der Sammlung.
Also hier mal so gelb angedeutet, das ist die Person.
Zum Beispiel, wenn es eine Sammlung, eine Liste von Personen ist.
Und außerdem eine Referenz auf ein weiteres Kettenglied,
auf den Nachfolger. Vielleicht zeige ich mal was,
ja, Blue Jay habe ich doch mit dabei.
Das ist das Projekt, das ihr in diese Woche
in den Übungen bearbeiten werdet.
Das sieht relativ groß aus
Ist aber eigentlich relativ überschaubar. Was wir gedacht haben,
ist, wenn ihr schon hier so seht,
wie das in iTunes funktioniert, mit so Wiedergabelisten,
dann sollen wir das eigentlich auch nachbauen können.
Deswegen haben wir sowas gebaut wie eine Titelliste. Also diese Musiktitel,
die nennen wir einfach mal Titel, wir haben also
eine klasse Titel hier modelliert, die repräsentiert einen Song.
Also wenn ich hier irgendwie mir das angucke,
wie gibt es hier eine Beschreibung dazu, klasse Musiktitel für
eine Musikdatenbank und dann gibt es so Operationen wie Album,
Interpret, Länge, Titelnahme und so weiter.
So, das ist also nur einfach so ein Datensack,
der die Informationen zu einem Musiktitel hält.
Und eine Titelliste Ist jetzt ein Interface, eine Liste von Titeln.
Das haben wir deshalb so gebaut, weil wir
euch in den Übungen keine generischen Implementationen bauen lassen wollen.
Das ist ein bisschen kompliziert in Java.
Deswegen geben wir den Element-Typ vor. Generische Sammlungstypen heißt,
der Element-Typ ist variabel. Kennt ihr so aus dem Collections-Ramp
? Ihr könnt beliebige Typen als Element-Typen meiner Sammlung deklarieren.
Bei dieser Liste ist es einfach so,
es gibt nur einen festgelegten Element-Typ, nämlich Titel.
Das heißt, die Operationen, die hier angeboten werden,
sind, füge ein, einen Titel an einer Position.
Gib mir die Länge der Liste,
also wie viele Elemente sind drin,
ist ein Titel enthalten,
da wird ein Titel
mit übergeben
Oder gebt mir den Titel an einer bestimmten Position,
macht das Lehre die ganze Liste und so weiter.
Das ist der Umgang, den wir definiert haben.
Das ist also ein Interface und das definiert,
wie eine Liste sich verhalten sollte. Hier ist es aber
auch nur informell beschrieben, wie diese Methoden sich verhalten.
Ihr seht ja, hier steht natürlich sprachlicher Text.
Hier steht einfach, entfernt den Titel an der angegegebenen Position.
Alle folgenden Einträge werden, um eine Indexposition verschoben und so weiter.
Das sind die Informationen, die ganz hilfreich sind.
Das heißt, wenn ihr so etwas habt, dann könnt ihr
das als eine Spezifikation ansehen, die ihr implementieren sollten.
Und ihr sollt eben genau zwei Implementationen bauen.
Ray-Titelliste und Link-Titelliste
Milliarden in den Anfangsjahren von SE1. Da haben wir noch kühn gedacht,
na gut, wir geben das Interface vor und dann sollen
die Studierenden die Implementation von Grund auf selbst bauen.
Das hat die Leute ziemlich überfordert. Das war erstaunlich und das ist einfach,
muss man sich immer wieder klar machen, ihr seid Programmieranfänger und das,
was hier passiert, ist schon ziemlich anspruchsvoll.
Deswegen haben wir dann in den Jahren danach Implementationsgerüste vorgegeben und
ihr müsst dann in Anführungsstrichen nur noch diese Listen, diese Lücken befüllen.
Das heißt, ihr müsst diese unvollständigen Implementationen vervollständigen.
Dass die unvollständig sind, sowohl in der Ray-Titel-Liste als auch Linktitel-Liste,
sieht man daran, wenn ich jetzt hier die Klassenaufrufe,
dann geht da nämlich ziemlich viel schief.
Also die schlagen alle fehl, 20 Tests,
auch hier 20 Tests, das sind eigentlich die gleichen Tests,
nur dass sie unterschiedliche Exemplare testen.
Einmal Ray-Titel, das ist dann einmal Linktitel-Liste. Was das Schöne ist,
dadurch, dass wir so ausführliche Tests haben,
das könnt ihr euch auch klar machen.
Diese Testklassen sind ja Klienten von diesen Implementationen. Das heißt,
sie treten auf als Klienten der Titelliste. Und das sind eigentlich ausimplementierte Spezifikationen,
weil da steht genau drin, wenn nicht,
das und das und das aufrufe, dann erwarte ich,
dass hinterher das und das dass der und der Zustand vorhanden ist in dieser Liste.
Also wenn ich drei Elemente einfüge, erwarte ich,
dass ich genau in einer bestimmten Reihenfolge die Elemente auch wieder rauskriege und so weiter.
Das ist hier tatsächlich ausimplementiert. Das heißt,
das ist eine deutlich klarere Festlegung dessen,
was da passieren soll, als diese informelle Text hier.
Auch das ist schon für viele ausreichend,
so eine informelle, natürlich sprachliche Spezifikation.
Sowas werdet ihr in eurem späteren Programmiererleben ständig bekommen,
wenn ihr Glück habt.
Manchmal ist es
auch baumal irgendwas
Aber hier ist es eben halt so, dass wir
sowohl die Schnittstelle vorgegeben haben, als auch die Tests.
Und das Schöne ist,
wenn ihr jetzt dann diese Implementation schrittweise baut in den Übungen,
kriegt ihr auch immer wieder Feedback von den Tests.
Also die Tests werden nach und nach dann bestanden,
wenn ihr das Ganze implementiert.
Also ein sehr schönes kleines Projekt. Wir haben auch hier
so eine kleine Titelbibliothek, die ist relativ einfach zu benutzen.
Die liest als Default irgendwie so eine Textdatei ein mit irgendwelchen Jazz-Titeln.
Und dann kann ich mir hier zum Beispiel hier,
gib zufälligen Titel aufrufen.
Das heißt, ich kann mir tatsächlich
einen solchen Titel geben lassen
Und dann habe ich hier eine Referenz auf einen Musiktitel aus dieser Art hier.
Das ist dann irgendwie, was ist das hier?
Espion Svensson, Trio, irgendeinen Jazztitel. Gut. Und
das wollte ich erst mal noch mal kurz gezeigt haben,
weil in dieser, in diesem Projekt,
wenn wir jetzt über verkettete Listen reden,
dann ist das ja hier das Gerüst für die Link-Titelliste,
also für eine verkettete Liste, dann haben wir hier eine Klasse Doppel-Link-Knoten.
Und die ist genauso ein doppelt verkettetes Gerät.
Wir schauen uns das gleich noch mal genauer an.
Das heißt, wir sind wieder hier,
gucken uns aber jetzt erst mal an,
wie das ist mit einer einfachen Verkettung
Und was eben ganz wichtig ist, wenn ich jetzt tatsächlich sowas haben will,
wie eine klasse Kettenglied oder Doppel-Link-Knoten und es soll möglich sein,
dass die miteinander verkettet sind,
dann muss ja der Nachfolger vom Kettenglied sein.
Und das ist was, was ihr bisher nicht gesehen habt,
was neu ist, was eigentlich so nicht weiter schwierig ist,
weil man ja mit den Referenzen sich das ganz gut vorstellen kann.
Aber das ist eine spezielle Form der Rekursion. Das heißt,
hier ist eine Struktur definiert und innerhalb der Struktur wird
die definierende Struktur selbst verwendet.
Das nennt sich strukturelle Rekursion
Also wenn sich eine Methode selbst aufruft, ist das eine funktionale Rekursion.
Das ist also wirklich eine Aufrufrekursion. Hier ist es so
, die Struktur ist rekursiv definiert. Und wie gesagt,
wenn man weiß, dass das hier mit so Verkettungselementen funktioniert mit Referenzen,
wenn man dieses mentale Modell hat,
ist das gar nicht so schwer, sich das vorzustellen.
Dass natürlich hier der Typ dieser Referenz auch wieder Kettenglied sein muss,
damit man da weitere Kettenglieder dranhängen kann.
Ja, gut. Die Liste wird für ihre Klienten als Exemplare
in der eigenen Klasse realisiert, haben Linklist, sagen wir jetzt einfach mal.
Das heißt, wir haben hier so ein Exemplar,
das ist hier immer so angedeutet sehr breit,
weil wir sagen wollen, naja,
die Liste ist ja irgendwie alles zusammen und auch hier blau eingefärbt,
diese komischen Kettenglieder, weil wir eigentlich der Meinung sind,
dass die Kettenglieder Teil der Liste sind.
Also logisch sind sie eigentlich innerhalb der Liste gekapselt.
Man sieht eigentlich gar nicht, dass die da sind.
Als Klient sieht man nur die Elemente, die ja außerhalb
der Sammlung irgendwo gehalten sind oder irgendwo erzeugt wurden und ähnliches.
Und ein Klient hält nur eine Referenz auf diese Klasse,
auf dieses Exemplar dieser Klasse Linklist. So,
und diese Kettenglieder bilden die innere Struktur, die referenzierten Elemente,
nicht Teil der Liste und irgendwie wird es so sein,
dass in der Klasse eine Referenz gehalten wird auf den Listenkopf
Die nennt man meistens Listenkopf oder wie auch immer.
So. Ja, wunderbar. Und jetzt ist es relativ einfach,
sich klarzumachen, wie das denn jetzt passiert, wenn ich ein Element einfügen will.
Nehmen wir an, ich habe diese Liste. Ich habe also hier jetzt vier Elemente,
habe also wenn ich jetzt ein Element einfügen will,
habe ich eins, zwei, drei, vier,
fünf Möglichkeiten, was einzufügen.
Und ich habe jetzt irgendwie einen neuen Listen-Eintrag,
also habe also so eine Referenz auf ein Element.
Und was ich jetzt brauche, wenn ich dieses Element einfügen will
in diese verkettete Liste, ich brauche so ein Kettenglied.
Also erzeuge ich mir erstmal so ein Kettenglied,
hänge da das Element rein und dann haben wir tatsächlich dieses hier,
also ein Kettenglied mit einer Referenz auf, dieses Element,
was eingefügt werden soll, in die Liste.
Und dann ist der nächste Schritt ganz einfach. Ich muss es
jetzt einketten. Zuerst werde ich hier von dem neuen Kettenglied
hier das reinverketten und dann wird diese Referenz umgebogen. Das heißt,
mit Erzeugen einmal eine Referenz eintragen und nochmal eine Referenz eintragen mit drei Operationen,
ist hier das Element an der dritten Position eingekettet.
Das sieht, wie gesagt, sehr einfach aus.
Das größte Problem ist meistens, erstmal dahin zu kommen an
die Stelle. Also wenn man jetzt sagt,
ich möchte gerne an der dritten Position einsortieren,
muss man sich halt genau merken,
welche Referenz hält man denn gerade in der Hand,
die muss also eine Schleife schreiben,
die irgendwie diese Referenz irgendwie die aktuelle Referenz irgendwie weiterschiebt über diese Liste und so.
Ihr werdet das sehen. Beim Entfernen ist es eigentlich noch einfacher. Ja,
wir haben also hier wieder die Situation, Listenkopf mit viel Elementen,
Referenz des Vorgängers wird einfach auf den Nachfolger oben.
Wenn ich also hier jetzt dieses Element, das dritte Element löschen will,
dann biege ich einfach diese eine Referenz um.
Und dann wird vom Garbage Collecture irgendein Garbage Collector irgendwann dieses Kettenlied entfernt.
Das heißt, das wird einfach rausfallen. Auch das ist
so ganz einfach. Gut, Das ist immer eine
gute Gelegenheit. An dieser Stelle kann man sehr schön so
ein paar Dinge diskutieren in Bezug auf UML-Diagramme.
Das heißt, wenn ihr das jetzt hier als UML-Diagramm anseht,
wir haben also hier drei Klassen in diesem Klassendiagramm.
Linklist, Kettenglied und Element. Das sind also genau die
drei Dinge, die ich eben schon so mehrfach diskutiert habe.
Dann ist es so, dass die Klasse Linklist
einen Verweis auf diese Klasse Kettenglied in diesem Attribut-Listenkopf hält.
Also irgendwie gibt es hier eine Beziehung.
Und diese Beziehung ist gezeichnet mit einer durchgezogenen Pfeil,
mit einem durchgezogenen Pfeil
Das heißt, das ist eine etwas stärkere Beziehung in der UML.
Das ist also eine strukturelle Abhängigkeit. Das heißt,
in den Exemplarvariablen, hier dieser Klasse, ist eine Referenz
gehalten auf diese Klasse. Und deswegen kann man sagen,
die hält etwas und das ist etwas Dauerhaftes.
Und deswegen wird das so als durchgezogene Linie gezeichnet.
Dann benutzt die Klasse Linked List die Klasse Element.
Und das ist wieder ganz wichtig,
hier ist es jetzt eine gestrichelte Linie,
weil diese Klasse hält nicht direkt Referenzen auf Elemente
Sondern die Elemente werden ja immer gehalten von den Kettengliedern.
Das sehen wir uns gleich noch an,
da kommt der nächste Fall demnächst noch rein.
Aber der hier benutzt die nur,
der hält aber nicht selber in seinen Exemplar Variablen diese Referenzen.
Deswegen ist das hier nur gestrichelt angedeutet, gestrichelt,
aber es muss trotzdem da stehen,
weil natürlich an der Schnittstelle von Linked List tauchen ja der Typ-Element taucht ja auf.
Also bei der Titelliste haben wir es gerade gesehen,
in dem Interface ist ja dann als Parameter irgendwie beim Einfügen
eine Liste als Parameter und so weiter.
Und deswegen ist diese Abhängigkeit auch wichtig
Dann ist natürlich klar, das Kettenglied tatsächlich in sein Exemplar
war ja nur eine Referenzzeit auf ein Element.
Ja, und dann haben wir noch die Rekursion,
dass nämlich hier tatsächlich so ein Kettenglied auch eine Referenz auf sich selbst hält.
Das heißt also so, das ist mal die Darstellung des Objektnotierten Entwurfs,
den ich auf den vorigen Folien dargestellt habe, in Form eines Klassendiagramms.
Okay, ja, jetzt bei einer doppelt verketteten Liste ist das nicht weiter groß anders.
Das heißt, da ist es dann so,
dass tatsächlich auch jedes Element eine Referenz hat auf seinen Vorgänger.
Bei einer Liste ist es natürlich so,
dass dann das erste Element nirgendwo hin zeigt.
Das ist wahrscheinlich eine Nall-Referenz hier. Und das ist vermutlich
auch eine Nall-Referenz. Also irgendwie, da wird nirgends mehr
hingezeigt, nicht mehr auf ein weiteres Kettenglied.
Gut. Doppelt verkettete Liste ist das,
was auch tatsächlich im Java Collections Framework verbaut ist.
Und weil das eben halt so nützlich ist,
ist es eben halt auch genau das,
was wir in diesem Projekt hier für euch verbaut haben.
Das heißt, ihr habt hier so eine Klasse,
die heißt Doppel-Link-Knoten. Können wir uns auch mal angucken.
Die ist jetzt auch nicht
so weiter sensationell
Die Schnittstelle sieht relativ einfach aus. Es gibt Nachfolger,
es gibt Vorgänger, es gibt Sätze, Nachfolger,
es gibt Sätze, Vorgänger,
doppelt den Knoten jeweils als Parameter und sätze Titel,
also den konkreten Titeln wirklich eintragen.
Wenn wir das jetzt hier mal uns erzeugen,
ich kann mir mal so einen Doppel-Linknoten mal bauen hier, juhu.
Gib doch mal einher. Wir bauen jetzt mal live für eine doppelt verkettete Liste.
Ich gucke mir das erstmal an, das sieht so aus.
Der hat also irgendwie hier Ah, nur neue Referenzen.
Das Ding ist erstmal noch nicht weiter irgendwie belegt.
Jetzt kann ich erstmal sagen, Sätze, Titel.
Und dann nehme ich hier so einen Titel. Zack.
Dann ist tatsächlich hier die Referenz auf den Titel eingetragen.
Das heißt, der haben wir jetzt hier wieder dieser SPUN-21er-Titel.
Gut, gut. Sehr schön. Jetzt kann ich ja
hier so einen doppelten Knoten auch noch mir noch mal waren,
war noch einen weiteren Knoten.
Jetzt kann ich da auch mal diesen Konstruktor benutzen.
Da gebe ich als Element nochmal diesen Titel an.
Das ist ja wurscht jetzt. Es geht ja nicht darum,
dass wir jetzt hier irgendwie spannende Musiklisten bauen.
Als Vorgänger trage ich jetzt mal diesen anderen Knoten ein.
Obst, das war jetzt nicht richtig.
Das soll der Titel sein. Hier soll der hin und
der Nachfolger von dem zweiten Knoten soll nall sein.
So, jetzt haben wir einmal diesen Knoten und wir haben einmal diesen Knoten.
Und die sind jetzt, ich deute das so ein bisschen an, miteinander verknüpft.
In dem Sinn, dass der hier den anderen als Vorgänger hat.
Also wenn ich jetzt mal hier das ein bisschen so verdecke und dann sage,
ah, hier, Vorgänger, verfolge nochmal die Referenz,
dann komme ich genau zu diesem Knoten.
Bei dem Knoten ist jetzt nur das Element eingetragen.
Bei beiden ist das Element eingetragen, sieht man hier,
aber der Nachfolger bei diesem Knoten ist noch nicht eingetragen.
Also sage ich, bei diesem Knoten jetzt mal
sätze Nachfolger
Nämlich genau den zweiten Knoten hier. So,
jetzt sind die so richtig, wie man sich das wünscht,
miteinander verknüpft über Vorwärts- und Rückwärtsreferenzen.
Das heißt, ich komme sowohl von diesem Knoten mit dem
Vorwärts, mit dem Nachfolger zu diesem.
Und ich komme genauso auch rückwärts natürlich zu dem anderen zurück.
Das heißt, ich kann zwischen diesen beiden Knoten wunderbar hin und her navigieren.
Das ist jetzt hier interaktiv, sehr nett.
Beim Programmieren muss man das ein bisschen ausschreiben.
Aber das ist schon mehr oder weniger die kleinste,
doppelt verkettete Liste, die man sich so vorstellen kann.
Die haben wir jetzt hier mal live gesehen.
Ja, okay
Wenn man verkettete Listen baut, dann gibt es so bestimmte Design-Alternativen.
Und eine Design-Alternative ist, wie hält man es mit den Enden
der Liste? Wir haben jetzt gerade in diesem Beispiel,
was ich hier live gebaut habe, gesehen, der erste Knoten,
der letzte Knoten, der Vorgänger vom ersten Knoten,
ist nahe, der Nachfolger vom letzten auch nahe.
Was wir in der Implementation gebaut haben,
hier in dieser Bluejoy-Implementation, ist, diese Implementation von Link-Titelliste,
die ist ja schon ziemlich weit fortgeschritten.
Da haben wir euch es so vorgegeben,
dass wir zwei Wächterelemente eingeführt haben
Und diese Wächterelemente, und zwar ein Wächterelement am Ende,
und ein Wächterelement am Anfang.
Warum das? Das ist tatsächlich so was, wo die Informatiker
über die Jahre dann auch mal ein bisschen was gelernt haben.
Wenn man mit so einer verketteten Liste umgeht,
dann merkt man relativ schnell, man muss relativ viele Sonderfälle programmieren.
Ich kann das mal hier an einem Beispiel zeigen.
Also wenn wir jetzt davon ausgehen, wir haben eine Liste,
wir haben also eine Referenz auf den Listenkopf und haben drei Elemente in unserer Liste.
Jetzt soll an einer bestimmten Position etwas eingefügt werden.
Mögliche einfüge Positionen sind in diesem Fall,
das wisst ihr schon, diese drei, diese vier Möglichkeiten,
weil ich habe drei Elemente und ich kann das neue Element an vier verschiedenen Positionen einfügen.
Wenn ich jetzt kein Wächter davor habe, also keinen Wächterknoten,
dann kann ich nicht in jedem Fall auf das Element vor der Einfügeposition positionieren.
Also ich werde ja vermutlich, nehmen wir mal an,
ich will hier da ein Element dazwischen packen.
Dann muss ich ja bei dem Knoten, der davor steht, die Referenz umbiegen.
Das heißt, ich brauche eigentlich eine Referenz auf den Knoten
davor. Das geht aber bei dieser Position nicht,
weil da habe ich keinen Knoten davor. Das heißt,
ich muss einen Sonderfall programmieren, Also wirklich so,
ist es am Anfang, dann muss ich die Listenkopf umbiegen,
ansonsten lauf durch die Liste durch und dann biege da die Referenzen um.
Ein Sonderfall, den man programmieren muss,
hat man es mit einem Wächter programmiert, dann wird in dieser Methode einfügen,
immer auf das Listenelement positioniert, das unmittelbar vor der Einführgestelle geht,
liegt, weil es gibt immer ein solches Element.
Also auch hier, vier Positionen null, dann ist davor das Wächterelement. Das heißt,
wenn ich den Listenkopf anfangen nehme,
dann mit einer Schleife sage, so,
ich soll bis Position X laufen,
dann muss ich also x-mal diese Referenz einen Knoten weitersetzen,
damit ich das nullmal mache,
habe ich genau die Referenz auf den Wächter
und kann bei dem dann das Element dahinter einhängen
Das ist jetzt theoretisch, ist für euch auch nur am
Rande interessant, weil ihr werdet diese Wächter implementieren.
Ihr werdet das also etwas leichter haben in den Übungen,
weil ohne Wächter wird es dann doch ein bisschen schwieriger.
Ihr könnt es aber, wenn ihr fit seid,
auch gerne mal ohne diese Wächter implementieren,
um zu sehen, wie viel mehr Sonderfälle da auftreten.
Das ist tatsächlich eher ungemütlich. Man muss da eben halt
mehr beachten. Gut, das ist das eine,
was man so an Design-Alternativen hat.
Dann gibt es noch die Sache, mit denen verweisen.
Ich habe gesagt, man hält bei einer verketteten Liste
mindestens immer eine Referenz auf den Listen anfangen
Man kann zusätzlich auch noch eine Referenz auf das Ende halten,
weil bei Listen ist es ja häufig so, dass man am Ende einfügt.
Also ich habe das auch, glaube ich, im List-Interface gesehen.
Es gibt irgendwie einen List. Add. Und wenn man
dann keine Position angibt, wird am Ende eingefügt.
Das ist also ein Umgang, der wohl recht häufig vorkommt,
sonst hätten sie ja nicht extra so eine weitere Operation eingeführt für Listen.
Ist aber ziemlich blöd,
wenn man nur eine Referenz auf den ersten Knoten hat,
weil dann muss ich jedes Mal,
wenn ich am Ende was einfügen will,
durch die ganze Liste durchhühnern,
um dann am Ende
was einfügen zu können
Deswegen ist es schlau, wenn man so eine
doppelt verkettete Liste hat oder auch eine einfach verkettete Liste,
sowohl eine Referenz auf den Anfang als auch auf das Ende zu halten.
Dann kann man zumindest das Anfügen am Ende sehr effizient implementieren.
Ja, genau. Das ist auch schon das,
was ich zu verketteten Listen erzählen wollte. Es gibt da
beliebig mehr Möglichkeiten, Intelligenz reinzusetzen. Also verkettete Listen oder
überhaupt so verkettete Strukturen. Im letzten Jahr hat dann Studenten mal gefragt,
na warum ist das denn so,
wenn man irgendwie da jetzt nur am Anfang, am Ende,
wenn man jetzt in der Mitte was einfügen will,
wäre es nicht besser,
noch eine Referenz in die Mitte
zu haben
Fängt man an, sozusagen mehrere Referenzen zu halten und
die müssen dann auch wieder verwaltet werden.
Also sowas wie Skip-Listen gibt es und also das ist ein Riesenfeld.
Datenstrukturen, dynamische Datenstrukturen in der Informatik, da kann man sich richtig austoben.
Das ist hier erstmal nur der Allerelementarste Einstieg für euch.
Gibt es Fragen noch zur verketteten Listen? Da ist eine
Frage. Sein Kanal, der vor dir ist.
Ja. Soll Folie 20 zeigen, ja, ich kann
es auch so machen. So. Muss man da jetzt,
egal, wenn ich da jetzt eine Liste gehe,
21 gehört auch. Diese? Das UML-Diagramm, oh,
oh, oh, oh, oh, oh, das.
Ich muss mal jetzt gemacht, ein Lied, das programmiert,
muss man dann schon den Typ, die Klasse des Elements zu werden.
Richtig, also er fragt, wenn ich so eine verkettete Liste baue,
muss ich dann nicht schon den Typ des Elementes kennen.
Und das ist eine sehr gute Frage und das ist tatsächlich
Im Collections Framework gelöst mit einem Konzept, das wir nicht behandeln,
nämlich Generizität, die Collections im Collection Framework sind generisch,
das heißt, der Elementtyp ist variabel,
das ist ein Platzhalter.
Deswegen machen wir es in den Übungen so,
dass der Elementtyp festgelegt ist, weil das deutlich einfacher ist.
Wenn man eine generische Implementation bauen will, muss man deutlich mehr,
hat man deutlich mehr Schwierigkeiten im Java.
Da wird man ein bisschen, sagen wir hässlich.
Da will ich aber jetzt nicht drauf eingehen. Ja,
also das ist tatsächlich etwas,
was in der SE1 jetzt hier
zu weit führt
Also ganz bewusst uns entschieden, nicht generische Listen bauen zu lassen.
Das heißt, hier ist es jetzt tatsächlich so,
der Element-Typ ist hier statisch festgelegt.
Wenn man das variabel halten will, gibt es verschiedene Möglichkeiten.
Eine Möglichkeit ist, das Ding ist vom Typ Object.
Dann kann ich wieder alles reinstecken. Das geht natürlich in Java
. Das ist so die Pure Man's Genericity. Und die
etwas schlauere Variante ist, die echte Generizität aus Java,
aber die ist nicht Thema in SE1. Gibt es sonst noch Fragen?
Da ist noch eine Frage. Was wäre denn zum Beispiel,
wenn ich irgendwie drei Elemente, das sind Haftpunkte mit dem dritten Element,
Wieder auf das erste Verwalt, also so eine Fleikebauer, ist das möglich?
Er fragt, wie wäre das denn, wenn ich sowas habe hier,
dass irgendwie das dritte Element nicht auf das nächste verweist,
sondern wieder auf das erste, dass man da eine Schleife reinbaut?
Ich sage, ja, das ist möglich. Also es
kann natürlich passieren, es riecht aber nach einem Fehler.
Also es sei denn, ich habe explizit eine Struktur gebaut,
eine verkettete Struktur, die als Ring gedacht ist.
Es gibt durchaus Fälle, wo man mit Ringpuffern sowas
machen möchte
Normalerweise sollte das nicht so sein. Also bei einer normalen
verketteten Liste sollte das schön ordentlich aufgefedelt sein mit den Referenzen.
Wenn da irgendwo mal eine Referenz quer geht, ist es ein Programmierfehler.
Ziemlich sicher. Och, meistens nicht vor. Nein,
es kommt gar nicht vor. Das kommt gar nicht vor. Es sei denn,
wie gesagt, ich mache irgendwas ganz Raffiniertes mit irgendwelchen Abkürzungen
oder dann brauche ich aber wahrscheinlich mehr Referenzen als,
weil ich brauche ja immer die auf den Nachfolger.
Ich kann vielleicht noch eine Referenz mehr ablegen in einem Knoten und kann sagen,
der verweist auf ein Element,
10 Elemente weiter oder auf irgendwie zurück,
weil ich vielleicht schneller zurückkommen will oder diese Datenstrukturen kann man
beliebig ausbauen
Diese, dieses Grundmodell ist erweiterbar in alle Richtungen.
Mehr Exemplarvariablen, mehr Referenzen, mehr Informationen ablegen,
dann wird es aber auch ganz schnell kompliziert, das zu verwalten.
Also wenn man irgendwie gerade programmieren lernt, ist das vielleicht eine etwas harte Nummer,
aber im Prinzip kann man da ziemlich viel machen.
Sonst noch Fragen jetzt zur Verkettung von Kettengliedern, bei verketteten Listen.
Gut, dann habe ich hier noch diesen einen Satz,
warte mal eben, 23, hier,
den würde ich noch ganz kurz vor der Pause loswerden.
Das fällt mir auch in den Übungen immer wieder auf.
Es ist ganz oft so,
und gerade wenn man so mit dynamischen Datenstrukturen umgeht,
Das ist erstaunlich simpel ist, wenn man sich eine Zeichnung macht.
Sobald man sich eine Zeichnung macht, so ein paar Boxen,
dann so ein paar Pfeile dazwischen und sich einfach klar macht,
was da passiert, dann kriegt man das viel leichter hin.
Viele in den Übungen probieren irgendwie, was loszuprogrammieren,
ohne sich vorher so ein gewisses Bild zu machen.
Und das ist bei manchen Sachen eigentlich fast unmöglich, das hinzukriegen.
Also hier ist so ein Zitat, das habe ich mal von Fred bekommen.
Der Blog ist, glaube ich, gar nicht mehr erreichbar.
Ich fand es trotzdem eine sehr schöne, sehr schöne,
sehr schönes Zitat. Es ist eigentlich für einen Menschen nicht möglich,
im Kopf Code zu schreiben, der eine Linke, eine verkettente Liste umdreht.
Also reverse linked list heißt also einfach die Elemente in ihrer Reihenfolge umdrehen.
Das im Kopf zu machen ist relativ schwierig,
wenn man sich eine schöne Zeichnung macht,
kann man das möglicherweise relativ leicht hinkriegen.
Deswegen mein Tipp macht immer,
wenn ihr irgendwie solche Strukturen versucht zu manipulieren im Code,
macht euch eine Zeichnung und dann kann man sehr gut drüber reden.
Gut, das ist erstmal das,
was ich bis sagen wollte, bis zur Pause,
dann machen wir gleich nach der Pause weiter fünf Minuten Pause bitte.
Wir haben uns jetzt angesehen,
wie man Listen bauen kann als Verkettelisten,
als nächstes schauen wir uns an,
wie man
Listen mit Race implementieren kann
Die Grundidee ist wieder, eine Klasse, zum Beispiel Rail-Liste oder so ähnlich.
Repräsentiert auch hier so Klienten gegenüber die gesamte Liste und bietet
alle Operationen einer Liste an der Schnittstelle an. Das heißt,
ich habe irgendeinen Klient, der Elemente einfügen will,
auf einer bestimmten Position, Elemente aushängen will, feststellen will,
wie viele Elemente sind in der Liste enthalten.
Also die klassische Schnittstelle einer Liste, die bietet die Rail-Liste an.
Dann intern, also das eine ist die Außensicht,
was bietet das Ding nach außen an, an Dienstleistungen.
Das andere ist, wie ist es intern aufgebaut,
intern verwendet so eine Rail-Liste dann ein Exemplar der Klasse Aray
Und in diesem Array sollen alle bisher eingefügten Elemente gehalten werden.
Also wenn mit Insert Elemente eingefügt werden,
stehen die alle in diesem Array drin. Das heißt,
das sieht ungefähr so aus. Arrayliste hält eine Referenz auf,
ein Array-Objekt und dieses Array-Objekt hält dann wieder Referenzen auf Elemente der Liste.
Das ist erst mal ganz einfach. Was natürlich euch klar ist,
wenn ein Array erst mal erzeugt ist mit einer bestimmten Größe,
dann kann es nicht mehr in seiner Größe verändert werden.
Das heißt, als direkte Implementation ist es ungeeignet.
Und was stattdessen gemacht wird, ist, dass Race erzeugt werden.
Also ein Array erzeugt wird, dass eine gewisse Anfangsgröße hat.
Dann wird es befüllt mit Elementen. Also immer,
wenn Füge ein aufgerufen wird oder Insert,
dann werden diese Elemente auch in dieses Array eingefügt.
Und wenn das Array irgendwann voll ist, naja,
was muss man dann machen? Da muss man sich
ein neues Array anlegen, wo mehr Platz drin ist.
Da muss man alle Elemente rüber kopieren.
Und dann kann man da auch das weitere Element einfügen.
Das ist die Grundidee von sogenannten wachsenden Arrays oder Growing Arrays.
Das heißt, das Array wächst nicht, sondern die Implementation drumherum.
Also Array List, die hat eine Idee von von einer gewissen Größe,
die benutzt nötigt wird und fordern entsprechend dann so viel Ray-Platz an.
Was das automatisch bewirkt, ist,
wenn ich einen Ray anlege, nehmen wir mal an,
ich fange mit einem Ray der Größe 10 Elemente an.
Und es sind aber nur, wie in diesem Beispiel,
vier Elemente drin. Dann habe ich natürlich sechs Zellen des
Arrays, die ungenutzt sind. Und wir haben als Begrifflichkeiten
eingeführt vor ein paar Wochen den Begriff der Kardinalität.
Das heißt, bei einer Liste oder allgemein bei einer Sammlung,
wie viele Elemente sind enthalten? Das ist die Kardinalität.
Und in diesem Fall ist die Kardinalität zum Beispiel vier
Und die Kapazität, das ist für Raillisten-Implementationen eben halt ein wichtiger Begriff.
Das ist das, was theoretisch an Platz da ist.
Nicht theoretisch, sondern praktisch an Platz da ist. Das heißt,
hier haben wir jetzt zum Beispiel eine Array der Größe 7.
Das heißt, drei Zellen sind ungenutzt.
Und es muss natürlich immer gelten, das ist quasi eine Invariante.
In Variante heißt, also es kann nicht gebrochen werden,
sonst hat man echtes Problem.
Die Kapazität muss immer größer gleich der Kardinalität sein.
Wenn ich mehr Elemente in eine Ray reinstopfe,
als ich Platz habe,
wird das
irgendwie komisch sein
Das ist erst mal die Grundidee. Da gibt es aber
jetzt so ein paar Aspekte, die man beachten muss.
Also das erste ist, so ein Array ist eine prima Sache.
Warum? Weil ein Array einen sehr schnellen Zugriff erlaubt.
Das heißt, ich kann extrem schnell auf einen beliebigen Index in der Liste zugreifen.
Zugriff auf eine beliebige Indisposition, sehr schnell und einfach.
Das ist einfach ein Array-Zugriff. Und Rays, wisst ihr,
sind so Segmente im Speicher, da gibt es irgendwelche Maschinenbefehle,
die sehr schnell ermöglichen, auf so ein Ding zuzugreifen.
Aber jetzt wird es ein bisschen unangenehmer. Wir haben ja gesagt,
dieses Listenkonzept sieht vor, dass ich an beliebigen Positionen etwas einfügen kann,
dann müssen sich die anderen ja irgendwie verschieben, die Elemente.
Das heißt, ich muss bei jedem einführen oder löschen,
die nachfolgenden Elemente verschieben
Im Extremfall muss ich tatsächlich alle Elemente um eine Position verschieben.
Ich habe das hier mal angedeutet,
in diesem Fall haben wir hier eine Kapazität von sieben.
Das Array hat sieben Zellen und eine Kardinalität von vier,
das heißt vier Elemente sind schon drin.
Wenn ich jetzt ein weiteres Element einfügen will,
also hier sind jetzt noch angedeutet, diese Elemente A,
B, C und E drin und ich möchte jetzt ein Element X einfügen,
an Position
1. Da soll es hin. Hier, da soll es hin.
Dann kann ich da jetzt nichts reinspeichern. Ich muss ja,
da ist ja, diese Zelle ist ja belegt mit dem Element B.
Also sage ich, na gut,
dann muss ich B halt irgendwie wegschieben,
aber wenn ich B wegschiebe,
dann steht C im Weg
Weiter. Also was ich also natürlich mache, ist,
ich mache das von der anderen Seite,
ich schiebe erst mal x das E weg, das C,
das B und dann kann ich das x da reinsetzen in die Lücke.
Das muss ich bei einem Array, bei einer Array-Liste immer machen.
Ich muss also, wenn ich irgendwo vorne was einfüge,
alle Elemente dahinter erst mal verschieben, damit Platz ist.
Im Extremfall, wie gesagt, die ganze Liste.
Das kann schon recht teuer sein, wenn die Liste groß ist.
Dann hinterher ist die Kanalität 5, Kapazität ist immer noch sieben.
So, dann gibt es den Fall,
dass die Kapazität des Arrays nicht mehr ausreicht.
Also ich Ich möchte, auch hier habe ich jetzt Elemente drin,
alle Zellen des Arrays sind belegt.
Das heißt, Kapazität ist gleich Kardinalität. Beide sieben.
Und jetzt wird hier ein weiteres Element eingefügt.
Z an Position 1. Da sagt der Klient,
das will ich da jetzt an Position 1 einfügen.
Also muss die Implementation sich was einfallen lassen.
Ja, was passiert? Es wird ein größeres Array angelegt.
Da ist die Frage, da hat man schon wieder eine Entwurfsalternative,
wie viel größer ist denn das neue Array?
Minimal sollte es ein Element größer sein.
Dann hat man sozusagen immer genau die richtige Menge an Platz
Aber ansonsten beim nächsten Mal das Ding ja wieder voll und
dann muss man dieses Kopieren jedes Mal machen.
Üblicherweise ist es so, dass man ein doppelt so großes Array anliegt.
Also hier waren wir das jetzt irgendwie sieben Elemente,
da hat man jetzt hier ein oder mit 14 Elementen angelegt.
Dann müssen alle Elemente rüber kopiert werden. Das ist irgendwie aufwendig,
muss ja irgendwie gemacht werden in einer Schleife oder wie auch immer,
mit einem Block-Coppy.
Und dann hinterher hat man immer noch wieder dann das Gleiche,
was wir vorher hatten.
Das heißt, ich muss erstmal die Elemente verschieben.
Zack. Und dann kann ich auch wieder hier in die Lücke
das Element reinsetzen. Das ist elementar das Konzept von einem
von einer Arraylist
Ich kann das auch noch mal live zeigen,
weil ich ja zufällig die Musterlösung von der Aufgaben dabei habe.
Ich kann mir also hier mal eine Ray-Titelliste erzeugen.
Und da kann ich eine Anfangskapazität angeben.
Und ich gebe jetzt mal absichtlich eine kleine Kapazität an,
damit wir hier relativ schnell den Effekt sehen,
dass das Ding voll wird.
Also sage ich hier, mit der Anfangskapazität,
genau, also drei. So, jetzt habe ich hier
eine Referenz auf eine Ray-Titelliste. Also das ist mein Objekt
der Klasse R. Ray-Titelliste
Dann habe ich hier eine Referenz auf ein Array da drin.
Das hat das Ding also anscheinend schon gleich angelegt.
Und hier ist dieses Array. Und natürlich ist in diesem Array
noch nichts drin. Alle drei Einträge sind nall. So,
wenn ich mich jetzt hier mal wieder mit der Titelbibliothek einen Titel geben lasse,
einen zufälligen Titel, um mir den hier auf die Objektleiste hole,
dann kann ich jetzt sagen, bei dieser Array-Titel-Liste füge ein,
an Positionen, den Titel, also hier Titel 1,
an Position null.
Ich kann ja nur an Positionen null einfügen, weil es ist
noch nichts drin. So, dann sieht man hier tatsächlich,
in diesem Array ist jetzt hier tatsächlich eine Referenz eingetragen.
Und man sieht auch hier, dass es jetzt irgendwie was von
Christian Rivilson. So, ist ja gar nicht wichtig.
Also habe ich jetzt hier meine Referenz auf das Array,
das Array and Head momentan eine Referenz.
Also Kapazität 3, Kardinalität 1. Ein Element ist erst drin
. So, wenn ich jetzt hier das nochmal aufrufe,
füge ein, nochmal den Titel an Position 1.
Dann haben wir da jetzt zwei Elemente drin. Und wenn
ich dann nochmal ein drittes Mal das aufrufe mit diesem Titel,
ich höre also immer wieder Christian R. Wilson, zack,
dann habe ich jetzt drei Titel drin.
So, und jetzt, was passiert jetzt, wenn ich
den vierten einfüge? Weil das Ding ist ja voll.
Das ist genau das, was wir ja sehen wollen.
Wenn ich jetzt hier diesen Titel nochmal einfüge an Position 2
, meinetwegen. Dann hat sich hier nichts verändert,
aber wir sehen hier jetzt, da steht jetzt Anzahl Titel 4.
Also tatsächlich Kardinalität ist jetzt vier, vier Elemente sind drin und
die Implementation hat jetzt inzwischen sich ein neues Irray angelegt.
Das sieht man daran, wenn ich jetzt hier Doppelklick mache,
dann kriege ich hier das neue Irray.
Das ist jetzt doppelt so groß. Das heißt,
da sind jetzt die vier Referenzen drin.
Und dieses Array, das fällt weg.
Irgendwann wird der Garbage-Collector sich seiner erbarmen und es wegräumen.
Und das ist auch genau das, was hier passiert.
Das heißt, die Kalinalität ist dann acht,
kapaziert ist 14 und das Ding wird irgendwann weggeräumt
Wie gesagt, vom Konzept her keine Hexenkunst,
aber wenn man es dann programmiert, ist es nochmal ein bisschen spannend.
Gut, dann haben wir jetzt ein bisschen Zeit,
um uns mal zu überlegen, warum habe ich euch jetzt zwei verschiedene Implementationen gezeigt?
Also beide Implementationen haben Stärken und Schwächen. Man kann sich
ja fragen, warum gibt es ein Collections Framework?
Zwei Implementationen für eine Liste. Es reicht doch,
wenn man eine Implementation hat. Beide sind korrekt. Beide
erfüllen die Spezifikationen, wie eine Liste sich verhalten soll.
Ich brauche eigentlich nur eine
Von der Korrektheit her gesehen hat. Ich brauche nur eine Implementation,
die sich korrekt verhält. Beide verhalten sich korrekt, erfüllen
die Spezifikation. Aber in der Benutzung zeigen sie gewisse Unterschiede.
Die sind unterschiedlich effizient. Und Effizienz ist eben halt etwas,
was manche Leute wichtig finden und andere Leute nicht so wichtig.
Ich will das jetzt hier mal verdeutlichen,
indem ich einen Vergleich mache zwischen diesen beiden Implementationen,
also Linked List und Area List.
Und wir betrachten den Aufwand für zwei typische Operationen.
Einmal für Einfügen eines Elementes und für Zugriff auf ein Element
an einer bestimmten Position
Einfach mal systematisch durchgehen. Was bedeutet das denn bei diesen
beiden Implementationen? Gut, einfügen in eine Liste.
Nehmen wir zuerst mal die Linklist. Da ist es so,
ich muss wenig an einer bestimmten Position einfügen, will,
muss ich erst mal zu der Position hinlaufen.
Du musst also durch diese Verkettung mich durchhangeln.
Im Durchschnitt wird dabei die halbe Liste durchlaufen.
Klar. Außerdem habe ich den Nachteil, dass ich
ein Objekt erzeugen muss, dieses Kettenglied.
Der Nachteil ist relativ verschmerzbar. Also Objekterzeugung ist in Java
ein Witz. Das ist wirklich, kann man vernachlässigen.
Vorteil, das Einfügen ist dann sehr einfach. Das heißt,
wenn ich schon in der richtigen Position bin,
Da muss ich nur diese Verkettung machen.
Also diese zwei Referenzen irgendwie umbiegen. Das ist ein
konstanter Aufwand, den man dafür treiben muss.
Die Averylist, wie sieht es denn da aus? Naja,
der Nachteil ist, ich muss tatsächlich alle Elemente ab der Einfügeposition verschieben.
Ich muss ja Platz machen für das neue Element.
Also muss ich alles rüberschieben. Im Durchschnitt muss ich dabei
die halbe Liste umkopieren. Nachteil, wenn die Kapazität erschöpft ist,
wenn man da so die Kapazität voll ist,
wenn das Avery einfach voll ist, dann kriege ich da gar nichts mehr rein.
Dann muss ich erst noch alles umkopieren und erst dann kann
ich wieder schieben. Also auch das ist nicht unbedingt
ein Vorteil
Der Vorteil ist, die Position zum Einfügen, die kann ich
direkt ansprechen. Also ich muss da nicht erst hinlaufen,
wie bei der Verkettetenliste, sondern Indexzugriff bei einem Array sehr schnell.
Okay. Dann Zugriff auf eine beliebige Position. Da ist
die Arraylist wirklich richtig schnell. Also da ist es so,
Zugriff erfolgt in konstanter Zeit, also der lesende Zugriff.
Während ich meiner Linklist tatsächlich erst mal die halbe Liste durchlaufen muss.
So, und das muss man sich klar machen.
Es gibt einfach unterschiedliche Benutzungsprofile für solche Listen.
Also die Dienstleistung an der Liste ist wohl definiert,
aber wie jemand einklären, die benutzt,
also was für ein Benutzungsprofil er hat, ist eine zweite Frage.
Und je nach Profil kann die ein oder andere Implementation besser geeignet sein.
Wenn ich zum Beispiel relativ konstante Listen habe,
die sich häufig, bei den häufig, die sich wenig ändern,
aber wenn denen häufig auf bestimmte Positionen zugegriffen werden muss,
da ist die Arrailist besser geeignet, klar.
Wenn ich aber sehr dynamische, große Listen habe,
wirklich riesige Listen,
und wir reden da jetzt bei den Big Data Begriffen von heutzutage,
reden wir ja nicht mehr über tausende oder Millionen,
sondern über Milliarden Elemente in so einer Liste,
müssen wir einfach mal so lesen,
was Google so alles für Daten
durch die Gegend schubst
Und dann ist es tatsächlich so, insbesondere am Listenanfang,
dann ist die Linklist möglicherweise die bessere Wahl,
weil da das Einfügen sehr viel schmerzfreier geht.
Ich muss nicht so viel schieben. Da ist eine Frage dazu.
Habe ich das Falsche daran, wenn ihr Linklist wisst?
Das ist doch gut erklärt. Ja. Das läuft,
man spricht die halbe Liste, dass man auch vorne
Urlaub denkt, wenn man durchlaufen sollte. Okay,
die Frage ist, wenn die Linklist doppelt verkettet ist,
warum durchläuft man dann im Durchschnitt die Hälfte der Liste,
um zu einem Element zu kommen?
Weil man ja von vorne und von hinten
laufen kann
Naja, weil wenn ich beliebige Zugriffe habe,
die können mal am Anfang sein, mal am Ende,
mal in der Mitte, dann muss ich da immer hinlaufen.
Es ist egal, ob ich von vorne oder von hinten komme.
Ich werde ungefähr im Durchschnitt die halbe Liste durchlaufen müssen.
Ich glaube, das beglaubt einfach maximal die halbe Liste von vorne.
Ja, ist richtig. Ja,
es ist dann wahrscheinlich tatsächlich noch nicht mal die halbe,
sondern es ist irgendwie eher, ja, man müsste man,
müssen wir mal ausrechnen.
Also es ist auf alle Fälle nicht nur ein Viertel.
Es ist mehr als ein Viertel. Naja. Also die
müssen aber irgendwie die Liste durchlaufen.
Die müssen die Traversieren.
Okay, das heißt, die Pragmatik für Java,
für euch ist erst mal überwiegend, wenn ihr eine Liste braucht,
eine Listen-Implementation, würde ich immer sagen, nehmt die Arraylist.
Die ist für die kleinen Listen meistens schneller. Sobald ihr
wirklich mit großen Listen arbeitet, müsst ihr euch überlegen,
was ihr verwendet. Aber ihr wisst jetzt,
was für Vor- und Nachteile diese Implementation haben.
Ihr könnt also besser einschätzen, wann ihr was benutzen müsst.
Ich kann das auch noch mal live zeigen,
weil ich hier eine Klasse habe, Effizienz-Tester bei der Musterlösung.
Dieser Effizienz-Tester,
der macht zwei Testfälle und die kann ich hier
kurz mal zeigen
Der bekommt als Parameter eine Titelliste übergeben.
Also wir haben jetzt zwei Implementationen, Ray-Titelliste und Linktitelliste.
Ist aber egal. Wir sind ja hier der Titel,
der Parameter-Typ ist hier Titelliste.
Dann wird hier aus der Titelbibliothek, wird ein zufälliger Titel besorgt.
Dann wird die Liste, die übergeben wird, gelehrt,
weil es könnte ja sein, dass da schon was drin ist.
Also erst mal schön leer machen. So, dann wird
hier 10.000 Mal ein Titel eingefügt. Und zwar an der
Position am Ende, wenn ich das richtig sehe.
Liste gibt Länge, füge ein, listeige Blänge.
Also Liste enthält 10.000 Titel. Füge einte, Liste
wie Blänge. Das ist doch Quatsch. Das sollte eigentlich
Listeposition null sein
Ach nee, ich bin verwirrt. Erstmal füge ich
10.000 Titel ein. So, 10.000 Titel werden eingeführt.
Dann starte ich Einfügen von weiteren 10.000 Titeln an der ersten Position.
Und dann mache ich tatsächlich hier Listefüge ein,
also an der Liste, wo schon 10.000 Elemente drin sind,
füge ich 10.000 weitere Elemente am Anfang ein.
Und ich mache wieder das übliche Spiel, das kennt ihr schon,
hier Long Start, Long Ende und dann kann ich messen,
wie lange das gedauert hat.
Und dann kann ich sagen, wie lange hat dieses Einfügen gedauert?
Und dann kann ich hier Wird das Zweite gemacht,
10.000 Zugriffe auf den mittleren Titel.
Also wenn das 20.000 sind, dann greife ich auf den 10.000. zu.
Also Liste gibt Titel 10.000. Da muss ich bei der einen Liste
immer durchlaufen, weil dann kann ich den Zugriff direkt machen.
So, jetzt können wir uns das mal angucken,
was das für einen Effekt hat, wenn wir so einen Effizienztester mal einsetzen.
Also ich sage hier Effizienztester und ich erzeuge mir zuerst mal
ein Beispiel hier der Ray-Titelliste
Und sage jetzt hier, vermesse, also doch mal die Array-Titel-Liste.
Dann kommt da irgendwas raus, Liste enthält 10.000 Titel,
starte einfügen von 10.000 Titeln. Dann braucht der 80 Millisekunden
dafür und 10.000 Zugriffe auf den mittleren Titel sind nur 3 Millisekunden.
Also man sieht schon deutlich einen Unterschied. Wenn ich jetzt mal eben gucke,
ist das, dass ich hier auch Option,
Konsole löschen, wollen wir mal nicht.
Wenn ich jetzt ein Exemplar der Linktitel-Liste mehr erzeuge und mit dem
das gleiche Spiel mache, vermesse ich jetzt mal die Linktitel-Liste,
dann sieht das so aus
Da sieht man das Einfügen an der ersten Position, geht schneller.
Hier 80 bei der Rail-Liste, weil ich ja immer alles verschieben muss.
Während bei dem hier geht es relativ schnell, 17 Millisekunden.
Andersrum ist natürlich der Zugriff auf ein Element in der Mitte,
bei der verketteten Liste sehr viel teurer,
weil ich jedes Mal mich durch 10.000 Elemente durchhangeln muss und
dann an die 10.000 zur Position zu kommen.
Wenn ich bei der Arrail-List das sehr viel schneller machen kann.
Man kann also hier sehen,
je nachdem, was man häufig macht,
kann das eine oder das andere
besser sein
Also das einfach nochmal so hier als sehr plakativen Vergleich der unterschiedlichen Stärken und Schwächen
dieser beiden Implementation. Also nochmal hier in der Übersicht,
Linklist ist eine Liste von Knoten,
die miteinander verbunden sind und eine Kette bilden,
Indizierung durch Abzählen, Einfügen, neues Element einlegen,
was eingekettet wird, löschen, kettetledigliches Kettenlied aus.
Railless dynamisch wachsendes Rail direkt initiierbar einfügen, erfordern verschieben,
eventuell auch komplettes Umkopieren, löschen erfordert verschieben von Folgelementen auch.
Da findet bei der Arraillist im Clections Framework
keine Verkleinerung statt
Okay. Das ist jetzt ein konkretes Beispiel gewesen von zwei
sehr einfachen Implementationen eines Datentyps. Der Datentyp war Liste,
die dynamischen Datenstrukturen dazu waren verkettete Liste und Arrail-Liste.
Jetzt würde ich gerne nochmal zehn Minuten darüber sprechen,
wie man jetzt eigentlich formalisieren kann, was da an Aufwand entsteht.
Weil wir haben jetzt sehr informell gesagt, naja,
ich muss irgendwie durch die Liste durchlaufen und so.
Und das ist teuer und da haben wir so ein Gefühl dafür vielleicht.
Aber da haben sich die Informatiker in den letzten 40 Jahren wirklich
sehr viele Gedanken gemacht und haben eine ausführliche Theorie aufgebaut,
nämlich die Komplexitätstheorie.
Also wie teuer ist denn eine bestimmte Operation auf einer dynamischen Datenstruktur?
Das Einfügen, das Entfernen, das Suchen und so weiter.
Und um das zu verstehen, was in der Komplexitätstheorie passiert,
ist es schon hilfreich, diese praktischen Erfahrungen,
die wir hier euch vermitteln, schon mal zu haben.
Ich will jetzt hier mal das so ein bisschen andeuten,
nur mal sozusagen so einen ersten Einstieg geben.
Komplexitätstheorie wird in FGI 1 ausführlich behandelt.
Da werdet ihr also euch damit noch
deutlich intensiver beschäftigen
Was wir jetzt erst mal uns klar machen wollen, ist,
wie kriegen wir denn diesen Begriff von Aufwand mal so ein bisschen klarer?
Gut, also unser Aufwand verstehen wir erst mal eine Menge an elementaren Schritten,
die für eine zusammengesetzte Operation ausgeführt werden müssen.
Also zum Beispiel das Einfügen habt ihr gerade gesehen,
da muss ich relativ viel tun, mehrere Teiloperationen, mehrere elementare Schritte.
Also einfügen hier an Position I auf einer verketteten Liste.
Ich muss durchlaufen bis zur Position I, Kettenlied erzeugen, Verkettung setzen.
Allgemein geht man davon aus, bei solchen imperativen Sprachen ist,
dass es so elementare Schritte gibt wie Zuweisung,
also es ist eine Zuweisung,
etwas ist,
was messbar eine feste Länge hat
Vergleiche. Ah, kleiner gleich B, next und gleich nall
und so weiter, dass das auch einen konstanten Zeitaufwand braucht.
Und dass es auch noch andere Dinge gibt,
den konstanten Zeitbedarf haben, zum Beispiel eine Objekterzeugung.
Eine Objekterzeugung sollte im Allgemeinen bei kleinen Objekten relativ wenig Aufwand bedeuten.
So, wenn ich also jetzt für eine Operation feststellen will,
wie viel Aufwand ist es denn, zähle ich diese elementaren Schritte?
Wenn ich anfange, diese elementaren Schritte zu zählen,
dann kann man unterscheiden,
in einen konstanten Teil
einen variablen Teil
Der konstante Teil ist für jede Ausführung der Operation gleich.
Beispiel Einfügen auf der verketteten Liste, Erzeugung eines Objektes,
eines Kettengliedes und dieses Umketten der Dinge.
Das ist konstant. Der variable Anteil hängt ab von
der Menge der zur verarbeitenden Daten. Das heißt,
wenn ich eine Liste mit N-Elementen habe, dann kann es mir passieren,
dass ich N mal irgendwie mich weiterschalten muss,
um irgendwie an den richtigen, an die richtige Position zu kommen.
Das ist tatsächlich das, was üblicherweise betrachtet wird.
Und es ist dann meist so, dass der Aufwand für die wiederholbare Operation.
Also die Abhängigkeit von der Anzahl der Elemente dominiert sehr stark diesen konstanten Teil.
Also was da so an konstantem Aufwand ist,
irgendwie so ein Element erzeugen, das kann man vernachlässigen,
weil das fällt ja sowieso immer an.
Aber in Abhängigkeit von der Elemente in der Liste variiert eben
halt zum Beispiel dieses Durchwandern. Dann gibt es noch bei
der Abschätzung des Aufwandes die Untersuchung vom schlechtesten Fall.
Also Aufwand im Mittel habe ich eben so ein bisschen versucht,
hier vorzutragen. Wie ist es denn mit dem Worst Case
? Wenn ihr mal so darüber nachdenkt,
was ist denn bei der verketteten Liste der schlechteste Fall?
Also zum Beispiel für Einfügen
Nehmen wir mal an, ihr habt eine verkettete Liste.
Was ist dann der schlechtes, denkbare Fall für ein Einfügen?
Ja, genau, in der Mitte. Das ist einfach das Übelste,
weil ich habe vielleicht gerade noch zwei Referenzen auf Anfang und Ende,
aber wenn ich in die Mitte will, muss ich,
komme ich nicht drumherum, ich muss einmal durchlaufen.
Wie ist es bei einer Arraylist? Was ist da
der Worst Case? Was ist da das Unangenehmste?
Ja. Bitte? Wenn das Array voll ist,
ist ein unangenehmer Fall,
Und am Anfang, genau, weil am Anfang muss ich
alle Elemente verschieben. Wenn es dann auch noch voll ist,
muss ich sogar umkopieren und alles verschieben.
Okay. Also diese Betrachtung kann man durchaus anstellen.
Üblicherweise wird von großen Datenmengen ausgegangen. Das habe ich
eben schon so angedeutet. Also der konstante Anteil kann meistens
vernachlässigt werden. Dadurch wird der Aufwand zu einer Funktion,
die abhängig ist von der Anzahl Ende zu verarbeiten Elemente.
Also bei Listen zum Beispiel, wie viele Elemente sind denn drin?
Dann ist Aufwand irgendwie F&N. Und das ist,
wie gesagt, die Grundidee von Komplexitätstheorie,
wenn man also Algorithmen auf solchen dynamischen Datenstrukturen untersucht,
dann wird man genau solche elementaren Operationen messen,
die abhängig sind von der Anzahl der zu verarbeitenden Elemente.
Und das ist dann auch was, was man so unter
der O-Notation fasst. Das ist hier die Landau-Notation.
Das sind bestimmte Komplexitätsklassen für Operationen auf dynamischen Datenstrukturen.
Und typische Komplexitätsklassen sind hier mit dieser großen O-Notation O von 1.
Das ist so informell sowas wie konstanter Aufwand.
Also alle elementaren Schritte haben ein O von
Dann gibt es ein O-Phone-Log-in.
Also ich habe En-Elemente in meiner Liste oder zu verarbeiten.
Dann kann der Aufwand logarithmisch abhängig sein von diesem N. Zum Beispiel,
wenn wir nächste Woche sehen, wenn ich in einem Baum etwas suche,
dann kann ich das in logarithmischer Zeit schaffen.
Linearer Aufwand, das ist O-Phone N. Das kommt ganz häufig
vor bei Listen, dass ich einen Aufwand habe,
der tatsächlich direkt abhängig ist von der Größe der Liste.
Also suche in Listen, aber auch einfügen in Listen,
habt ihr bei beiden gesehen. Ich muss irgendwie entweder
ziemlich viele Elemente verschieben oder ziemlich viel durch Elemente durchlaufen.
Das ist immer abhängig davon, wie groß das Ding ist
Dann gibt es sowas wie Nmy Login.
Es gibt gute Sortierverfahren und weniger gute Sortierverfahren.
Die haben so ein Quadrat als Komplexität. Das heißt,
wenn ich 100 Elemente habe, muss ich davon ausgehen,
dass ich ungefähr 10.000 elementare Schritte brauche, bei Endquadrat.
Und der Unterschied zwischen Nmy Login und N-Quadrat, der kann gewaltig sein.
Das werde ich nächste Woche dann, nee,
übernächste Woche euch erzählen, wenn wir was über Sortieren uns angucken.
Und dann gibt es noch diese ganz fiesen Dinger.
Das ist hier, es gibt natürlich noch mehr Komplexitätsklassen,
das sind so die wichtigsten hier.
Sowas von Ovan 2
Als Beispiel zwei hochenden. Also das ist exponentieller Aufwand,
das heißt, wenn ich irgendwie 100 Elemente habe,
dann habe ich 200, 200 ist eine ziemlich große Zahl.
Also es gibt so Algorithmen, die sind relativ teuer,
die sind exponentiell, was weiß ich, das Erzeugen in der Potenzmenge einer Menge.
Wenn ihr sowas habt, so ein Algorithmus,
der so eine Komplexität hat, so ein Aufwand,
dann können euer Rechner noch so schnell sein.
Da könnt ihr einen Boliden unterm Schreibtisch haben,
der richtig was leistet, das nützt gar nichts.
Also da werdet ihr bescheiden. Wenn ihr diese Komplexitätsklassen irgendwann mal euch anguckt,
dann werdet ihr sehen, kein Computer dieser Welt.
Auch ein Supercomputer ist nicht schnell genug,
um das bei einem genügend großen Problem auszurechnen zu können.
Es gibt einfach Probleme, die sind so teuer in der Berechnung,
dass selbst die schnellsten Rechner tausende oder Millionen Jahre rechnen müssen.
Ist einfach so. Die meisten Probleme, die man so hat im Alltag,
sind eben halt doch eher von kleinerer Natur,
aber es ist eben halt wichtig,
dass ihr diese Grenzen von Computern auch kennenlernt.
Deswegen ist das auch in FGI für euch sehr wichtig.
Gut, also die geben euch letztendlich eine Größenordnung an,
immer in Abhängigkeit von der zu verarbeitenden Datenmenge
Kommt ausführlich noch ein FG1 dran. So,
dann gucken wir uns noch mal eben ganz kurz an.
Also jetzt haben wir so diesen Begriff gelernt, oh,
Notation, Komplexitäten. Also können wir sagen,
das Einfügen an der Liste hat OVNN bei einer verketteten Liste.
Und bei einer Arrailis ist es auch OVNN.
In dem einen Fall muss ich halt einmal durchlaufen.
Beim anderen Mal muss ich ein Elemente verschieben.
Zugriffe von Element über einen Index. Und da sieht man
jetzt einen Unterschied, da ist die Linklist OVN,
linearer Aufwand, während die Arrailist O von 1 hat.
Weil dieser indizierte Zugriff hat einen konstanten Aufwand und deswegen ist das sehr schnell.
Hier schon mal so ein kleiner Ausblick, Test auf, enthalten sein.
Also wenn ich etwas suchen will an der Liste,
lineare Suche, feststellen, ob etwas drin ist in der Liste,
da muss ich bei beiden Implementationen irgendwie durchgehen.
Also ich habe irgendwie die Liste,
die Frage ist ein bestimmtes Element drin,
da muss ich tatsächlich alle Elemente angucken und kann hinterher erst sagen,
ja, ist dran oder nicht.
Deswegen ist die Suche in Listen üblicherweise O von N.
Aber es gibt so Mengen, es gibt so Sammlungen,
bei denen ist dieser Testaufenthaltensein ziemlich wichtig, zum Beispiel bei einem Set.
Bei einem Set muss ich ja ständig prüfen,
ob ein Duplikat schon drin ist. Dann wäre es ärgerlich,
wenn ich da O von N investieren müsste und um das festzustellen.
Es gibt da sehr, sehr schlaue Implementationen für Mengen
Und die sehen wir uns nächste Woche an.
Da geht es Möglichkeiten für Uhr von Login.
Und sogar mit konstantem Aufwand. Also tatsächlich sehr schnell feststellen,
ob etwas schon drin ist. Da ist eine Frage.
Ist das jetzt die Liste von der Ressource Pest oder die größerliche?
Das ist die durchschnittliche Komplexität. Das ist die durch.
Also O von N ist die durchschnittliche Komplexität. Weil der
Worst Case, naja, das ist auch der Worst,
ist auch der Worst Case.
Das ist auch der Worst Case. Also es kann ja
nicht schlechter werden. Es sei denn,
man hat sich verimplementiert und läuft irgendwie Schleifen oder so.
Aber das ist tatsächlich auch der Worst Case. Während der Optimal Case,
also der Best Case, kann ja zum Beispiel auch sein,
dass es sehr schnell geht bei manchen Operationen
So, aber wie gesagt,
nächste Woche ganz spannende Datenstrukturen für sehr viel effizientere Suche.
Aber ich habe noch, ich habe ja noch mindestens zwei Minuten Zeit.
Ich habe ja noch einen Storesi. Ich hoffe mal,
dass das funktioniert heute. Ich muss mal ganz kurz gucken,
ob das Netz hier funktioniert. Moment. Naja,
ich bin verbunden. So, jetzt noch mal hoffen,
dass ich hier mit dem Storesi hinbekomme.
Ich habe eine Abstimmung
So, welche der folgenden Aussagen ist korrekt? Eine Linked-List
ist schneller als eine Area-List. Ich starte das schon mal.
Ein LinkedIn ist schneller als eine Area-List beim Einfügen in der Mitte,
beim Einfügen am Anfang,
beim Zugriff von Element in der Mitte einer großen Liste,
beim Suchen eines Elements in einer großen Liste.
Ich hoffe mal, dass ich hier jetzt auch Stimmen kriege,
die über die Klicker hinausgehen.
Das werden wir dann gleich sehen, ob das Netz jetzt funktioniert.
Eben habe ich keine positive Rückmeldung gehabt. Ja,
aber es sind ja auch schon recht viele dabei jetzt
Ja, wir haben 100 Antworten,
ich halte das mal an. Mal schauen. Ja,
naja, das habe ich auch wirklich gut erklärt heute.
Ja, das ist natürlich die richtige Antwort.
Sehr schön. Ich habe hier noch eine andere,
welche der folgenden Aussagen ist richtig
Immer wenn bei einer Arealist die Cardinality gleich der Kapazität ist,
muss die Kapazität vergrößert werden.
Das Einfügen am Ende erfolgt bei einer Areast immer denselben Aufwand.
Wenn bei einer Areas beim Entfernen von Elementen die Kapazität niemals verkleinert wird,
kann bei einer sehr dynamischen Liste viel Speicherplatz unnötig belegt sein.
Im Java Collections Framework implementieren sowohl Linklist als auch Arealist das Interface Set.
Da könnt ihr jetzt auch nochmal eure Meinung abgeben,
welche der Aussagen jetzt richtig ist
Ja, sehr schön. Wir haben jetzt auch schon wieder
über 100 Stimmen. Also, wer sich noch
ganz schnell entscheiden will, kann das jetzt tun?
Ich halte das an
So, mal gucken. Also nicht mehr ganz so eindeutige Verteilung.
Aber immerhin haben sich 50 Prozent für die richtige Antwort entschieden.
Das ist so ein klassischer Ablenker hier. Also wenn ich
das nochmal zeigen darf, immer wenn bei einer Arealist,
die kann natürlich gleich der Kapazität ist, muss die Kapazität,
natürlich muss es erst dann vergrößert werden,
wenn ich ein weiteres Element einfügen will.
Es kann durchaus in Ordnung sein, dass die gleich groß sind.
Erst beim Einfügen wird es wichtig, dass man damit Platz schafft.
Das stimmt auf jeden Fall. Bei einer Arealist ist es
natürlich nicht immer der gleiche Aufwand
Weil es kann ja sein, dass das Ding voll ist.
Da muss ich umkopieren. Und Linklist und Areas
implementieren natürlich jetzt unter Face-List. Und nicht das Interface-Set.
Gibt es noch Fragen? Nein, wunderbar, danke für
heute.