Algorithmen und Datenstrukturen
Prüfungsinfo
- Alle Papier-Unterlagen erlaubt
- Bleistift erlaubt (empfohlen für Zeichnungen)
- 90 Min
- Stoff: Vorlesung + Übungen ohne JUnit
- Einzelheiten: Siehe Titelblatt
Vorbereitung
- "Index" erstellen (z.B. im Buch markieren), vorallem für den Übungsstoff
- Arithmetische Folgen (Üb 01, 02)
- Explizite Form Big-Oh (Üb 02, 03)
- Induktion mit zwei Summenformeln (Üb 03)
- O-Notation (Üb 04) - Konstanten heraus streichen
- Binärbäume / Heap (Üb 09, 12)
- Hashing (Linear Probing etc., Üb 13)
- Skip-Lists (Üb 14)
- Binary Search
Vorlesung 1 - Intro / OO-Design
-
Error-Left
Error-Zeitpunkte: Compilation, Linking, Runtime
Ziel: zb. von Runtime zu Compilation Errors, zb. bei Generics: Vorher Runtime-Fehler, mit Generics Compilation-Errors -
Folie 7: Divide-and-conquer: Problem in einzelne Teilprobleme teilen und diese einzeln lösen
Vorlesung 2 - Arrays / LinkedLists / Analyse von Algorithmen
-
Arrays
+ Schneller Zugriff (bei random access)
- Feste Grösse -
Singly-Linked vs Doubly-Linked List
- Im schlimmsten Fall braucht es nur halb so viel Such-Operationen bei der Doubly-linked-List (wenn Element in der Mitte)
Vorlesung 3 - Algorithm Analysis / Rekursion
-
Folie 30:
Big-Omega ist die Umkehrung von O(n): f(n) >= c * g(n)
big-Theta: "Bandbreite", in der Komplexität ist -
Folie 31:
Asympotisch: Für sehr grosse zahlen
Vergleich Wachstum der Funktionen: http://fooplot.com/plot/qit69ncs0u
Rekursion
-
Stack-Size ist in der JVM standardmässig 1MB, kann aber angepasst werden unter Run Configuration - Java-Argument: "-Xss2m" für 2 MB Stack-size
-
Folie 12
Bei n=123 ist Ausgabe "321" -
Design-pattern Adapter:
- Variante: Komposition: Interface implementieren, bestehende Klasse verwenden (Wrapper)
- Variante: Vererbung: Ein Interface implementieren und bestehende Klasse vererben (Klassen-Adapter)
Vorlesung 4 - Rekursion
-
Folie 28:
Vorteil von Klassen-Adapter hier: Weniger "boiler-plate" code, Methoden wie "size()" müssen nicht neu implementiert werden
Grosser Nachteil: Alle Methoden von Vector werden geerbt, also auch solche, die eigentlich nicht zu einem Stack gehören dürfen (zb. set(), removeElementAt(), ...) -
Objekt-Adapter für robuste Software
-
Klassen-Adapter ist schneller (weniger boiler-plate), dafür hat man mehr Kontrolle über die verfügbaren Methoden
Note
Vererbung wird zu Compile-Time festgelegt, bei der Komposition wird erst zur Runtime das objekt erstellt, es ist also flexibler
Vorlesung 5 - Rekursion II
-
Folie 41: Mit diesem Algorithmus gibt es weniger multiplikationen, da nicht von 1 bis n eine Multiplikation gemacht werden muss, sondern n wird jeweils halbiert: Dies ist keine Endrekursion!
-
Folie 50:
Keine Endrekursion, da das Ergebnis erst beim Stack-Abbau berechnet wird -
Folie 52:
Dies ist Endrekursion, da das Ergebnis schon nach dem Aufbau der Rekursion feststeht -
Folie 66:
Dies ist ! Weil die Lösungsmenge nicht aufgeteilt wird, sondern nur "divide and conquer" (zb. bei Parallelisierung sinnvoll)
Keine Endrekursion
rekursive Aufrufe -
Folie 68
Alternative mit Integer:
Vorlesung 6 - Stacks
- Folie 5
- In diesem Beispiel gibt pop() bei leerem Stack null zurück
- Folie 6
- java.util.Stack
.push() gibt das Item gleich wieder zurück - Gibt EmptyStackException, falls Stack leer ist (bei pop() und peek())
- java.util.Stack
- Folie 8
- EmptyStackException ist RuntimeException, also unchecked (Designfrage)
- Folie 11
- PC (Programmzähler) zeigt auf Position nach dem Funktionsaufruf (Rücksprungadresse). Hier zb. zeigt PC = 1 auf die Zeile nach
bar (k);
- PC (Programmzähler) zeigt auf Position nach dem Funktionsaufruf (Rücksprungadresse). Hier zb. zeigt PC = 1 auf die Zeile nach
- Folie 17
- Das gepopte Element wird auf null gesetzt, damit die Referenz entfernt wird und der GC aufräumen kann.
- Folie 18
- list.addFirst() ist , da einfach ein neues Element an den Anfang "gehängt" wird.
- Würde das oberste Element als letztes angehängt, wäre es , da die ganze Liste durchiteriert werden müsste
- Folie 31
- Im Java Doc steht, dass statt java.util.Stack besser Deque benutzt werden soll
- Problem: Der Stack erbt alle Methoden von Vector und kann somit auch zb. "SetElementAt", also mehr als ein Stack eigentlich können sollte
- Merksatz Design: Wenn B von A erbt, fragen: Ist B auch ein A?
- Hier: Ist Stack auch ein Vektor? <- Nein
Vorlesung 7 - Queues
- "DeQueue", "Deque (sprich Decked)": Double ended Queue
- Nicht zu verwechseln mit der Operation "Dequeue"!
- NodeDequeue: Eine leere Queue hat trailer und header (2 Nodes)
- Muss nicht immer auf null testen, sondern nur, ob man am "Rand" ist
Vorlesung 8 - Lists (1)
Unterschied zu Qeues:
- Positionsangabe
-
Implementation kann verschieden sein
-
Folie 3
E set(i, e)gibt das alte Element zurück- Ebenso
E remove(i) - Fehler in Folie: add(i, e) wirf nur eine Exception wenn
-
Folie 5
- Achtung bei
set(2, C), es kann mit set() nichts hinten an der Liste angehängt werden
- Achtung bei
-
Folie 10
- Dies ist nicht java.util.ArrayList!
- Size-Attribut für schnellere Performance
- Folie 20
- Für Array-Kopieren
java.lang.System.arracopyverwenden (Native C-Funktion)
- Für Array-Kopieren
- Folie 23
- k ist die Anzahl Umkopierungen
- (Arithmetische Reihe)
- Folie 25
- auf vereinfachen
- Folie 26
- Folie 30
- Hier wird das Array jeweils um 1.5x vergrössert
- Ammortisierungszeit auch
-
Folie 37
- p, q, r, usw. sind Positions-Objekte, die Zahlen die Inhalte davon (mit
Position.getElement()darauf zugreifen) - Vorteil mit Positionsobjekte: Set(), remove() etc. sind , da nur das Objekt verändert werden muss, nicht durchiteriert bis zur entsprechenden Position in der Liste
- p, q, r, usw. sind Positions-Objekte, die Zahlen die Inhalte davon (mit
-
Folie 46
- trailer und header nodes sind nicht Teil der liste, es werden nur die Elemente dazwischen zurück gegeben
Vorlesung 9 - Lists & Trees
Lists
-
Folie 50
- addBetween() ist eine private Method
- Weil node davor und danach stimmen muss; gefährlich, wenn Klasse public wäre
-
Das Position Interface wird verwendet, damit für den Benutzer nur
getElement()exposed wird, und nicht Node-Operationen wiegetNext()odergetPrevious(). So wird die interne Struktur isoliert -
validate()stellt sicher, dass die Position eine Node ist und noch in der Liste ist (noch einnexthat). Voraussetzung ist, dass die Referenzen bei remove() aus der Liste auf null gesetzt werden.

- Folie 61
- Hier ist die Liste "zirkulär", d.h wenn mit header.getNext() durchiteriert wird, landet man irgendwann wieder auf dem header (darum der for-loop dementsprechend)
-
Folie 63
- Aus Performance-Gründen. Die Methoden werden für die entsprechenden Datenstrukturen optimiert. Funktionieren würde es auch ohne Überschreiben, aber viel langsamer
-
Folie 64
- Korrektur:
addFirst()ist bei ArrayList indexOf(p)ist bei ArrayList nur wenn "p" Positions-Objekte sind
- Korrektur:
Vorlesung 10 - Iteratoren & Trees
Iterators
- Iterator-Varianten
- Snapshot-Iterator
- Vor dem Iterieren eine Kopie erstellen
- Lazy-Iterator
- Direkt auf der Datenstruktur iterieren
- Wenn Datenstruktur verändert wird, kann der Iterator unerwartet reagieren
- Bei java api verwendet
- Java Framework wirft exception bei
next(), wenn Datenstruktur verändert wurde - Java hat Iterator.remove(), um das aktuelle Element zu löschen
- Snapshot-Iterator
- Implementierung
NoSuchElementException- Wenn
next()false ergibt, abernext()aufgerufen wird
- Wenn
UnsupportedOperationException- Iterator hat remove() nicht implementiert, zb. wenn Liste mit
Array.asList()erstellt wird (AbstractList)
- Iterator hat remove() nicht implementiert, zb. wenn Liste mit
IllegalStateException- Wenn zwei mal hintereinander
remove()aufgerufen wird
- Wenn zwei mal hintereinander
Trees
Tress sind abstrakte, hierarchische Strukturen bestehend aus Knoten in Eltern-Kind Relationen
- Eigenschaften
- Internene Knoten: Knoten mit mind. 1 Child
- Externer Knoten: Blattknoten (Leaf-Nodes), Knoten ohne Kinder
- Tiefe: Anzahl Vorgänger
- Höhe eines Knotens: Höhe im Baum von oben. Leaf-Nodes: 0
- Höhe eines Baums: Höhe der Wurzel (Root)
- Sibling: Geschwisterknoten
- Baum-Traversierung (jeden Knoten "besuchen")
- Preorder: Jeder Knoten vor seinen Childs besuchen, wie der Ausdruck eines Dokuments
- Postorder: Jeder Knoten wird nach seinen Children besucht
- Anwendung: Auswertung arithmetischer Ausdrücke: Jeder Aufruf liefert Wert des Unterbaums. Eine solche Traversierung ist das gleiche wie ein Stack-Rechner (Reverse Polnische Notation)
- Inorder: Von einem Knoten wird zuerst der linke Subtree besucht, dann der Knoten, und dann der rechte Subtree
- Anwendung: Graphische Darstellung eines Baumes (x= inorder Rang, y=Tiefe)
- Anwendung: Ausgabe arithmetischer Ausdrücke. Operanden sind in den Blattknoten, Operatoren in den internen Knoten. Über jeden Subtree werden Klammern gesetzt
- Alle drei sind Spezialfälle der Euler Tour Traversierung. Darin wird jeder Knoten drei mal besucht. Von links (preoder), unten (inorder) und rechts (postorder)
- Breadth-First: Zuerst alle Knoten mit Tiefe t besuchen, bevor Knoten der Tiefe t+1 besucht werden
- Anwendung: Entscheidungen für Spielzüge in AI
- Binäre Bäume
- Haben pro Knoten ein geordnetes Paar von children (left, right)
- Jeder Knoten hat (in einem echten Binärbaum) ein Sibling (ausser root)
- Balancierter, "Echter" Binärbaum: Jeder Knoten hat genau zwei Children
- Unbalanciert: Das "Gewicht" liegt auf einer Seite
Vorlesung 11 - Trees & Priority Queues
Trees
- Template Method Pattern Euler Traversierung
- visitExternal(), visitLeft(), visitRight() etc. sind in abstrakter Klasse leer
- Die benötigten Methoden werden in konkreter Klasse überschrieben und "implementiert"
- GoF = Gang of Four
- Autoren des bekannten "Design Patterns" (1995)
Priority Queues
- Jeder Entry der Queue besteht aus einem Key-Value-Pair
removeMin()entfernt jeweils das Element mit dem niedrigsten Key (= höchste Priorität)- Comparator
- allgemein:
compare(a, b) {return a - b;}
- allgemein:
- Folie 8
- Hier wird nur der Key verwendet, Values bleiben immer null.
- Selection Sort
- Implementierung mit unsortierter Liste
insertbenötigt ,removeMinbenötigt- Das Entfernen von n Elementen braucht
- Insertion Sort
- Die Sequence ist immer sortiert
- Beim Einfügen muss sortiert werden, also
- Das Entfernen braucht
- In-Place Insertion Sort
In-Placeheisst, dass die Sortierung direkt in der Sequence, ohne zusätzliche Datenstruktur sortiert wird- Das neue Element vorne anfügen und mit Swaps nach hinten bis an die richtige Position schieben, Laufzeit
Vorlesung 12 - Heaps & Adaptable PQ
Heaps
- Folie 3
- Der Heap wird in einem Binärbaum gespeichert
- Der Key jedes Knoten ausser die Wurzel muss grösser sein als sein parent-Knoten
- -> Das kleinste Element ist der Root-Knoten
- Folie 4
- Alle Knoten bis h-1 sind gefüllt
- Die letzte Ebene (tiefe h) wird von links her aufgefüllt
- -> Der letzte Knoten ist der weiteste Rechts auf Tiefe h
- -> h ist abgerundet
- Einfügen in Heap
- Neues Element als letzter Knoten einfügen
- Mit Upheap jeweils mit dem Parent-Node vergleichen und tauschen, bis der Node < Parentnode ist (= Ordnung wieder hergestellt)
- Zeitaufwand:
-
Entfernen aus Heap
- Root entfernen
- Der letzte Knoten (der grösste) wird root
- DownHeap: Vom Root aus den Knoten mit dem jeweils kleineren Child-Knoten vertauschen, bis keiner der Children grösser ist oder ein Blattknoten erreicht wurde
- Zeitaufwand:
-
Folie 9
- Die Bedingung für den Heap muss nur "upheap" geprüft werden, da Elemente immer von rechts eingefügt werden und der linke "Teilbaum" nie verändert werden muss
- Folie 11
- Es sind explizit Vergleiche, da immer beide Children überprüft werden müssen
- Folie 12
- Die Priority-Queue mit einem Heap hat für insert() und removeMin(), was sie viel schneller macht als Insertion- bzw. Selection-Sort, die für jeweils eine Operation benötigen
- Heap-Sort
- Mit einer Heap-basierten PQ kann man Elemente in sortieren
- Ein Element einfügen braucht , also brauchen Elemente
- Heap mit Array realisieren
- Für Knoten braucht es ein Array mit Grösse , da man bei Index 1 beginnt
- Ein Knoten wird bei Index gespeichert
- Der linke Child-Knoten bei Index
- Der rechte Child-Knoten bei Index
Adaptable PQs
remove(e): Entfernt eine Entry aus und liefert sie zurückreplaceKey(e,k): Schlüssel der Entry e ersetzen und der alte Schlüssel zurück gebenreplaceValue(e,v): Der Wert der Entry e ersetzen und der alte Wert zurück geben- "Location-Aware" Entries haben eine Referenz auf ihre Position in der Datenstruktur gespeichert, damit für den Zugriff nicht die ganze Struktur abgesucht werden muss
Vorlesung 13 - Maps & Hash-Tables
Maps
Eine Map ist eine durchsuchbare Collection von Key-Value Entries. Pro Key wird nur eine Entry erlaubt.
get(k): Value mit dem Keykzurückgeben, wenn nicht vorhandennullput(k, v): Neue Entry(k, v) hinzufügen (Rückgabenull), wenn schon vorhanden, wird Valueversetzt und der alte Value zurück gegebenremove(k): Entry mit Keykentfernen und zurück geben. Wenn nicht vorhandennullvalues(): Collection mit allen Werten (Duplikate möglich, da unterschiedliche Keys gleiche Values haben können)- Implementierung mit Verketteter Liste
-
für
remove()undput()undget() - Mit Sentinel Trick nur die hälfte der Abfrage möglich
- Am Ende der Liste einen Eintrag mit gesuchtem Key eintragen
- Man muss dann nicht jedes Mal fragen, ob man am Ende der Liste ist, sondern nur, ob der Key stimmt
- Immer noch , aber Schritte werden ca. halbiert
-
für
- Sentinel-Trick
- Am Ende der Liste einen neuen Knoten (Sentinel) mit dem gesuchten Wert einfügen und kennzeichnen (z.B. Referenz darauf halten)
- Dann muss beim durchiterien nicht jedes Mal geprüft werden, ob das Ende erreicht wurde, denn der Knoten wird garantiert gefunden
- Am Ende prüfen, ob es der Sentinel-Knoten ist oder der Echte, gesuchte
- Damit wird die Anzahl Tests der Suche halbiert
Hash-Table
- Hash-Funktion bildet Keys auf Integer in bestimmten Intervall ab
- Die Werte sollen möglichst gestreut werden
hashCode()gibt irgendeinen Integer zurück, der dann noch in das Intervall "gemappt" werden mussObjectin Java gibt eine Integer Adresse des Objekts aus dem Heap zurück- Wert wird als Integer angeschaut (z.B. als Bits bei float)
- Sonst mit Komponentensumme: Wert unterteilen in Komponenten fixer Länge und die Komponenten addieren (overflow ignorieren)
- Polynom-Akkumulation: Komponenten als Koeffizienten eines Polynoms betrachten und mit einer konstanten Primzahl ausrechnen. Wird z.B. bei Strings verwendet. Bsp:
- Hashtable Grösse: Primzahl wählen (bessere Streuung)
- offene Adressierung: Man bleibt bei Kollisionen in der Tabelle
- Linear Probing: Wenn Kollision, suche nächster freier Platz
- Löschen:
- Problem, weil get() dann evtl. nicht mehr funktioniert
- Lösung: Datensätze werden nicht gelöscht, sondern nur als gelöscht markiert (DEFUNCT Entry)
- geschlossene Adressierung: Separte Datenstruktur für Kollisionen (z.B. linked list)
- doppeltes Hashing: Zusätzliche Hash-Funktion verwenden
- Performance: Schlimmster Fall O(n) (wenn alle Elemente zu Kollissionen führt), erwarter Fall O(1) wenn Auslastungsfaktor n/N nicht zu hoch
Vorlesung 13 - Skip-Lists & Sets
Skip-List
- Die Liste hat zwei künstliche Endknoten +Infinty und -Infinity
- Key-Value-Elemente aufsteigend sortiert
- Perfekte Skip-List:
- Der erste Knoten ist der Median
- Die zwei nächsten Knoten die Mittelwerte auf beiden Seiten
- usw..
- Beim Suchen wie ein Tree (Binary Search)
- Problem: Bei jedem Einfügen muss die komplette Liste neu organisiert werden
- Randomisierte Skip-List:
- Es wird zufällig versucht, die Werte gleichmässig zu verteilen
- Beim einfügen wird eine zufällige Höhe gewählt und das neue Element in alle Ebenen dieser Höhe eingefügt
- Suchen
- scan forward: Auf selber Höhe bleiben und nach rechts
- Drop down: Eine Ebene nach unten
- Bsp: Wenn wir 12 suchen, wird auf -Infinity bis auf S0 "gedroppt"
- Speicherplatz: O(n), aber im Schnitt 2n
- Höhe ist mit grosser Wahrscheinlichkeit sehr klein
- Suchen mit O(n) (erwartet)
- Ist in Java.util.concurrent, (normalerweise für multi-threading), da der Thread nicht wie beim Baum die ganze Struktur locken muss
Sets, Multisets & Multilaps
- Set: Unsortierte Collection ohne Duplikate
- Multiset: Set mit Duplikaten
- Multimap: Eine Map, wobei ein Key mehrere Values haben kann
retain()ist der DurchschnittaddAll()ist die VereinigungremoveAll()ist die Differenz- Set-Operationen können mit generischem Mischen implementiert werden (mit O(n) Laufzeit)
- Dafür müssen Listen sortiert sein!
- Multimaps
- früher Dictionaries genannt ("Ein Wort, mehrere Übersetzungen")
- Quasi das gleiche wie
Map<K, List<V>> entries()stattentrySet(), da nun Duplikate vorkommen können