Informatik 2 - Datenstrukturen

Werbung
Informatik 2
Datenstrukturen
Prof. Dr.-Ing. Holger Vogelsang
[email protected]
Inhaltsverzeichnis








Abstrakte Datentypen (3)
Datenstrukturen in Java (11)
Elementare Datenstrukturen (14)
Iteratoren (42)
Streams (58)
Hashtabellen (67)
Bäume (113)
Graphen (202)
Holger Vogelsang
Informatik 2 - Datenstrukturen
2
Abstrakte Datentypen
Übersicht
Grafische
Oberflächen
Übersicht
Datenstrukturen
ADTs
Typinfo.,
I/O
Annotationen
JavaScript
Prinzipien
Holger Vogelsang
Layouts
Ereignisse
Datenstrukturen
in Java
Laufzeittypinfo.
Einstieg
Grafikoperationen
Widgets
Elementare
Datenstrukturen
Iteratoren,
Streams
Grafikwidgets
Hashtabellen
Effekte,
Animationen
Bäume
Offene
Punkte
Graphen
Ein-,
Ausgabe
Objekte Vererbung
Module
DOMZugriffe
Informatik 2 - Datenstrukturen
3
Abstrakte Datentypen
Übersicht


Bisher:

Einführung in objektorientierte Programmierung

Einfache Datenstrukturen wie Array, Vektor und Liste
Jetzt:

Festlegung von Schnittstelle und Funktionalität einer Datenstruktur, ohne eine konkrete
Implementierung zu verwenden
Definition: Abstrakter Datentyp
Ein abstrakter Datentyp (ADT) ist ein Datentyp (eine Menge von Werten und eine Sammlung
von Operationen auf diesen Werten), der nur über eine Schnittstelle zugänglich ist. Ein ADT
kann bei identischer Funktionalität unterschiedliche mögliche Implementierungen besitzen.
Holger Vogelsang
Informatik 2 - Datenstrukturen
4
Abstrakte Datentypen
Übersicht


Eine ADT-Spezifikation besteht aus einer Signatur und Axiomen (hier stark vereinfacht
dargestellt, die mathematischen Begriffe wurden teilweise nicht verwendet).

Signatur ∑ = (S, Ω) mit
- S = Die Menge von Datentypen, die der ADT verwendet. Einer der Datentypen wird
in der Regel durch den ADT neu definiert. Die anderen existieren bereits.
- Ω = Die Menge von Methoden und Konstanten des ADT.

Axiome legen die Semantik und damit das Verhalten des ADT unabhängig von einer
konkreten Implementierung fest.
In diesen Unterlagen werden teilweise Beispiele aus dem Buch „Algorithmen und
Datenstrukturen“ von Saake und Sattler übernommen. Die Darstellung der ADTs entspricht
auch der Syntax aus dem Buch.
Holger Vogelsang
Informatik 2 - Datenstrukturen
5
Abstrakte Datentypen
Beispiel: Liste

Unvollständiger ADT für eine Liste List von Elementen des Datentyps T.
type List(T)
import Nat
operators
[]  List
_:_ : T × List  List
addFirst : List × T  List
getFirst : List  T
getTail : List  List
size : List  Nat
axioms ∀l : List, ∀x : T
getTail(addFirst(l, x)) = l
getFirst(addFirst(l, x)) = x
getFirst(x : l) = x
getTail(x : l) = l
size([]) = 0
size(x : l) = succ(size(l))
Holger Vogelsang
Informatik 2 - Datenstrukturen
[] erzeugt eine
neue, leere Liste
_:_ Konstruktionsvorschrift (neuer
Operator)
x : l Element x
und weitere
Listenelemente in l
succ entstammt ADT
für natürliche Zahlen
6
Abstrakte Datentypen
Beispiel: Liste



Mögliche Listen:

[] leere Liste

1 : [] Liste mit dem Element 1

1 : 2 : 3 : 4 : [] Liste mit den Elementen 1 bis 4
Deutlich erkennbar: Das Wissen über die konkrete Implementierung der Liste ist für die
Anwendung nicht erforderlich.

In einer Programmiersprache: Schnittstelle der Klasse (öffentliche Methoden und
Datentypen) sowie die Dokumentation des Verhalten sind erforderlich.

Die Art der Implementierung ist unwichtig (sofern sie das Laufzeitverhalten nicht
beeinflusst).
Wozu dient das Wissen über ADTs?

ADT könnte in Java eine Schnittstelle (ein interface) sein.

Die konkrete Implementierung implementiert die Schnittstelle.

Es lässt sich prima „auf der Schnittstelle“ arbeiten.
Holger Vogelsang
Informatik 2 - Datenstrukturen
7
Abstrakte Datentypen
Anwendung eines ADT
Beispiel ADT List und konkrete Java-Umsetzung der Operatoren
type List(T)
import Nat
operators
[]  List
_:_ : T × List  List
addFirst : List × T  List
getFirst : List  T
getTail : List  List
size : List  Nat
axioms ∀l : List, ∀x : T
getTail(addFirst(l, x)) = l
getFirst(addFirst(l, x)) = x
getFirst(x : l) = x
getTail(x : l) = l
size([]) = 0
size(x : l) = succ(size(l))
<<interface>>
List
+add(int i, e: T): boolean
+get(index: int): T
+remove(index: int): T
+size(): int
T: class
T: class
LinkedList
ArrayList
+add(int i, e: T): boolean
+get(index: int): T
+remove(index: int): T
+size(): int
+add(int i, e: T): boolean
+get(index: int): T
+remove(index: int): T
+size(): int
Verkettete
Liste
Holger Vogelsang
T: class
Informatik 2 - Datenstrukturen
Vektor
8
Abstrakte Datentypen
Anwendung eines ADT
Beispiel ADT List und konkrete Java-Umsetzung der Axiome
type List(T)
import Nat
operators
[]  List
_:_ : T × List  List
addFirst : List × T  List
getFirst : List  T
getTail : List  List
size : List  Nat
axioms ∀l : List, ∀x : T
getTail(addFirst(l, x)) = l
getFirst(addFirst(l, x)) = x
getFirst(x : l) = x
getTail(x : l) = l
size([]) = 0
size(x : l) = succ(size(l))
z.B. als Dokumentation oder
zur Validierung einer Implementierung
Holger Vogelsang
Informatik 2 - Datenstrukturen
9
Abstrakte Datentypen
ADT: Beschreibung der Axiome


Die Axiome können durch nahezu beliebige „Sprachen“ beschrieben werden.
Komplexere Aussagen sind z.B. mit OCaml (funktionale Programmiersprache, siehe
http://caml.inria.fr/) möglich  soll hier nicht näher vertieft werden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
10
Datenstrukturen in Java
Übersicht
Grafische
Oberflächen
Übersicht
Datenstrukturen
ADTs
Typinfo.,
I/O
Annotationen
JavaScript
Prinzipien
Holger Vogelsang
Layouts
Ereignisse
Datenstrukturen
in Java
Laufzeittypinfo.
Einstieg
Grafikoperationen
Widgets
Elementare
Datenstrukturen
Iteratoren,
Streams
Grafikwidgets
Hashtabellen
Effekte,
Animationen
Bäume
Offene
Punkte
Graphen
Ein-,
Ausgabe
Objekte Vererbung
Module
DOMZugriffe
Informatik 2 - Datenstrukturen
11
Datenstrukturen in Java
Übersicht

Vereinfachte Übersicht über die Collections-Klassen in Java
Holger Vogelsang
Informatik 2 - Datenstrukturen
12
Datenstrukturen in Java
Schnittstellen












Iterable<E>: Über die Datenstruktur kann direkt iteriert werden  siehe Iteratoren.
Collection<E>: Gruppe von Elementen, Duplikate können erlaubt sein
List<E>: Collection mit einer Ordnung, Indexzugriff ist erlaubt (möglicherweise
ineffizient)
RandomAccess: Leere Schnittstelle, der Indexzugriff ist mit konstanter Zeit möglich.
Queue<E>: spezielle Queue-Operationen vorhanden
Deque<E>: Queue mit Einfüge- und Löschoperationen an Anfang und Ende
Set<E>: Menge von Elementen ohne Duplikate
SortedSet<E>: Menge mit einer Totalordnung der Elemente (anhand ihrer natürlichen
Ordnung oder anhand eines Vergleichs-Objektes)
NavigableSet<E>: SortedSet mit Methoden, um kleinere oder größere Elemente zu
finden
Map<K,V>: Bildet Schlüssel-Objekte (K) auf Werte (V) ab.
SortedMap<K,V>: Eine Map mit einer Totalordnung der Elemente
NavigableMap<K,V>: SortedMap mit Methoden, um kleinere oder größere Schlüssel zu
finden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
13
Elementare Datenstrukturen
Übersicht
Grafische
Oberflächen
Übersicht
Datenstrukturen
ADTs
Typinfo.,
I/O
Annotationen
JavaScript
Prinzipien
Holger Vogelsang
Layouts
Ereignisse
Datenstrukturen
in Java
Laufzeittypinfo.
Einstieg
Grafikoperationen
Widgets
Elementare
Datenstrukturen
Iteratoren,
Streams
Grafikwidgets
Hashtabellen
Effekte,
Animationen
Bäume
Offene
Punkte
Graphen
Ein-,
Ausgabe
Objekte Vererbung
Module
DOMZugriffe
Informatik 2 - Datenstrukturen
14
Elementare Datenstrukturen
Übersicht in Java

Elementare Datenstrukturen
Holger Vogelsang
Informatik 2 - Datenstrukturen
15
Elementare Datenstrukturen
Vektor – Prinzip







Die Klassen ArrayList<E> und Vector<E> verwalten dynamisch beliebig viele Objekte.
Die Anordnung der Objekte erfolgt sequentiell.
Die interne Implementierung erfolgt durch ein Array.
Der Vektor besitzt eine Referenz auf den Speicherbereich.
Ein Vektor eignet sich besonders für einen schnellen freien Zugriff über Indizes auf die
Elemente.
Der Indexzugriff erfolgt immer geprüft.
Vector ist im Gegensatz zu ArrayList thread-sicher und damit langsamer.
ArrayList
ArrayList
size
Extra Platz
0
Holger Vogelsang
1
2
3
4
5
6
E: class
-values[*]: E
-size: int
7
Informatik 2 - Datenstrukturen
16
Elementare Datenstrukturen
Beispiel zu ArrayList

Beispiel:
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> contents = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
contents.add(i * i);
}
int v = contents.get(12);
}
}
// Geprüfter Zugriff -->
// Abfangen der Fehler
System.out.println(contents.size());
System.out.println(contents.contains(2));
Holger Vogelsang
Informatik 2 - Datenstrukturen
17
Elementare Datenstrukturen
ArrayList und Vektor in Java

Beide implementieren RandomAccess: Indexzugriff in konstanter Zeit
Holger Vogelsang
Informatik 2 - Datenstrukturen
18
Elementare Datenstrukturen
Vektor – Aufwandsabschätzungen
Aufwandsabschätzungen für die Vektor-Klassen
Operation
Holger Vogelsang
Aufwand
Einfügen
O(N)
Löschen
O(N)
Indexzugriff
O(1)
Suche, sortierte Daten
O(ln N)  Binärsuche
Suche, unsortierte Daten
O(N)
 sequentielles Durchlaufen
Informatik 2 - Datenstrukturen
19
Elementare Datenstrukturen
Liste: Prinzip

Prinzip einer einfach verketteten Liste:
LinkedList
last
first



Listelement
value
Listelement
value
Listelement
value
Die Liste besitzt einen Kopf und ein Ende.
Jeder Listeneintrag verweist auf seinen Nachfolger.
Jeder Listeneintrag beinhaltet die Nutzdaten.
LinkedList
E: class
first 0, 1
last
Holger Vogelsang
0, 1
Listelement
-value: E
E: class
0, 1
next
Informatik 2 - Datenstrukturen
20
Elementare Datenstrukturen
Liste: Prinzip

Prinzip einer doppelt verketteten Liste:
LinkedList
last
first



Listelement
value
Listelement
value
Listelement
value
Die Liste besitzt einen Kopf und ein Ende.
Jeder Listeneintrag verweist auf seinen Nachfolger und Vorgänger
Jeder Listeneintrag beinhaltet die Nutzdaten.
LinkedList
E: class
first 0, 1
last
0, 1
Listelement
-value: E
0, 1
Holger Vogelsang
E: class
0, 1
next
prev
Informatik 2 - Datenstrukturen
21
Elementare Datenstrukturen
Liste in Java

LinkedList implementiert nicht RandomAccess: Indexzugriff nicht in konstanter Zeit!
Holger Vogelsang
Informatik 2 - Datenstrukturen
22
Elementare Datenstrukturen
Liste – Aufwandsabschätzung
Operation
Holger Vogelsang
Aufwand
Einfügen (an Index x)
O(N)  sequentielles Durchlaufen
Einfügen (Anfang, Ende)
O(1)
Löschen (an Index x)
O(N)  sequentielles Durchlaufen
Löschen (Anfang, Ende)
O(1)
Indexzugriff
O(N)  sequentielles Durchlaufen
Suche, sortierte Daten
O(N)  sequentielles Durchlaufen
Suche, unsortierte Daten
O(N)  sequentielles Durchlaufen
Informatik 2 - Datenstrukturen
23
Elementare Datenstrukturen
Beste Eigenschaften von Vektor und Liste kombinieren



Lassen sich die guten Eigenschaften von Vektor und Liste kombinieren?

Schnelles Einfügen und Löschen an Anfang und Ende

Indexzugriff

Wiederholtes schnelles Einfügen und Löschen an benachbarten Positionen
(Lokalitätseigenschaft)
Ja: GapList (CircularArrayList, …)
Zur Erinnerung: ArrayList (ist ein Vektor)
ArrayList
ArrayList
size
Extra Platz
0

1
2
3
4
5
6
E: class
-values[*]: E
-size: int
7
Die GapList ist ähnlich aufgebaut, kann aber den „Extra Platz“ an einer beliebigen Position,
also auch zwischen belegten Einträgen, haben.
Holger Vogelsang
Informatik 2 - Datenstrukturen
24
Elementare Datenstrukturen
Beste Eigenschaften von Vektor und Liste kombinieren

Ausgangssituation:
GapList
Extra Platz
E1
0

E2
1
E3
2
3
4
5
6
7
8
9
10
11
9
10
11
Einfügen am Ende (wie bei ArrayList):
GapList
Extra Platz
E1
0
Holger Vogelsang
E2
1
E3 E4
2
3
4
5
6
7
8
Informatik 2 - Datenstrukturen
25
Elementare Datenstrukturen
Beste Eigenschaften von Vektor und Liste kombinieren

Ausgangssituation:
GapList
Extra Platz
E1
0

E2
1
E3
2
3
4
5
6
7
8
9
10
11
Einfügen am Anfang (wie bei einem Ring-Puffer):
GapList
Extra Platz
E1
0
Holger Vogelsang
E2
1
E4
E3
2
3
4
5
6
7
8
9
10
11
Informatik 2 - Datenstrukturen
26
Elementare Datenstrukturen
Beste Eigenschaften von Vektor und Liste kombinieren

Ausgangssituation:
GapList
Extra Platz
E1
0

E2
1
E3
2
3
4
5
6
7
8
9
10
11
Einfügen in der Mitte zwischen 1 und 2 (die Elemente an der Einfügestelle werden so
verschoben, dass dort die Lücke entsteht):
GapList
Extra Platz
E1
0
Holger Vogelsang
E2
E4
1
2
3
4
5
6
7
8
9
10
E3
11
Informatik 2 - Datenstrukturen
27
Elementare Datenstrukturen
Beste Eigenschaften von Vektor und Liste kombinieren






Das Einfügen in eine GapList ist immer dann sehr schnell, wenn an benachbarten Positionen
eingefügt wird (Lokalitätseigenschaft).
Das Löschen funktioniert ähnlich wie das Einfügen, ist ebenfalls sehr schnell.
Ist die GapList voll, dann wird wir bei einer ArrayList ein neues Array angelegt und alle
Elemente kopiert.
Dummerweise gibt es im SDK keine GapList…
Ausweg:

Brownies Collections von http://www.magicwerk.org/page-collections-overview.html

Bieten weitere schnelle und speichersparendere Array-artige Datenstrukturen  sollen
hier nicht betrachtet werden

GapList implementiert List und Collection aus dem SDK.
Auch Nachteil:

Bei sehr vielen Elementen wird der Aufwand trotzdem recht hoch.
Holger Vogelsang
Informatik 2 - Datenstrukturen
28
Elementare Datenstrukturen
Unveränderliche Listen



Bereits bekannt: „Value Objects“ für unveränderliche Objekte
Gibt es dasselbe auch bei Datenstrukturen?

Mit Collections.unmodifiableList(List<? extends T list> lässt sich eine
unveränderliche Sicht auf eine vorhandene List (und damit z.B. eine ArrayList oder
LinkedList) erzeugen. Diese Methode existiert auch für andere Datenstrukturen.

Diese List kann an Methoden übergeben werden, ohne dass der Aufrufer die List
verändern kann.

Der Versuch, die unveränderbare List zu verändern, führt zu einer Exception.
Dummerweise ist das den Methoden nicht anzusehen  gut testen.

Enthält die List lediglich „Value Objects“, dann kann der Aufrufer sicher sein, dass die
aufgerufene Methode keine Änderungen vorgenommen hat.
Unterschied zu den vorgestellten „Value Objects“: Es ist nicht vorgesehen, eine Änderung in
Form einer veränderten Kopie zu erstellen und diese Kopie zurückzugeben.
Holger Vogelsang
Informatik 2 - Datenstrukturen
29
Elementare Datenstrukturen
Unveränderliche Listen

Beispiel:
public void doSomething(List<String> names) {
// Ist names veränderbar oder nicht??
names.add("Vogelsang");
}
public static void main(String[] args) {
Kein
// Veränderbare ArrayList
Fehler
List<String> names = new ArrayList<>();
doSomething(names);
// Unveränderbare Sicht auf die ArrayList
List<String> unmodNames = Collections.unmodifiableList(names);
doDomething(unmodNames);
}


UnsupportedOperationException
Die Lösung ist etwas unbefriedigend, zumal keine veränderte Kopie erzeugt werden kann.
Leider bietet das JDK auch keine bessere Lösung  Ausweg: Fremdbibliotheken.
Holger Vogelsang
Informatik 2 - Datenstrukturen
30
Elementare Datenstrukturen
Unveränderliche Listen



Beispiel: Open-Source-Bibliothek „a-foundation“ (https://github.com/arnohaase/afoundation), angelehnt an Datenstrukturen aus der Sprache Scala.
Anderer Aufbau der Listen:

Eine Liste kennt ihr erstes Element sowie

den Rest der Liste, der wiederum eine Liste ist.

Das letzte Element ist die leere Liste nil.

Idee: siehe ADT-Einführung!
Beispiel:
// Leere Liste erzeugen
AList<String> noNames = Alist.<String>nil();
// Neue Liste erzeugen, dabei jeweils Elemente vorne in "Kopien" von noNames einfügen
// (noNames bleibt unverändert).
AList<String> names = noNames.cons("Vogelsang").cons("Pape");
AList<String> names2 = names.cons("Körner");
AList<String> names3 = names.cons("Hoffmann");
System.out.println(noNames); // Leere Liste []
System.out.println(names);
// Liste [Pape, Vogelsang]
System.out.println(names2); // Liste [Körner, Pape, Vogelsang]
System.out.println(names3); // Liste [Hoffmann, Pape, Vogelsang]
Holger Vogelsang
Informatik 2 - Datenstrukturen
31
Elementare Datenstrukturen
Unveränderliche Listen
Interne Repräsentation:
names2
"Körner"
names3
names
"Hoffmann"
"Pape"
"Vogelsang"
Holger Vogelsang
Informatik 2 - Datenstrukturen
nil
32
Elementare Datenstrukturen
Unveränderliche Listen



Da sich die Listen nie ändern, können neue Listen auf die Daten aller Listen verweisen.
Das klappt nur, wenn die Listen lediglich „Value Objects“ enthalten.
Fazit:

Unveränderliche Objekte und Datenstrukturen vereinfachen manchmal die
Programmierung und erlauben besser lesbaren Code.

Sie können, müssen aber nicht, effizienter als veränderbare Objekte und
Datenstrukturen sein.

Im Zusammenhang mit Multithreading (siehe drittes Semester) sind solche
unveränderlichen Daten manchmal ganz hilfreich.
Holger Vogelsang
Informatik 2 - Datenstrukturen
33
Elementare Datenstrukturen
Queue – Prinzip

Prinzip einer Queue (Warteschlange):
Queue
0



1
2
3
Daten werden in einer Queue am Ende mit offer eingetragen und am Anfang der Queue
mit poll entfernt  Warteschlange (häufig auch push/pop).
Es gibt einige Container-Klassen, die Queue-Funktionalität einsetzen. Beispiel: LinkedList.
Einsatzgebiete

einfaches Nachrichtensystem (Nachricht ablegen = offer, Nachricht abholen = poll).

allgemeine asynchrone Kommunikation zwischen Auftragnehmer und Auftraggeber
Holger Vogelsang
Informatik 2 - Datenstrukturen
34
Elementare Datenstrukturen
Queue in Java

LinkedList implementiert Queue
Holger Vogelsang
Informatik 2 - Datenstrukturen
35
Elementare Datenstrukturen
Queue in Java

Die Methoden gibt es doppelt (aus Queue und Deque):
Funktion
Methoden mit Ausnahmen
Methoden ohne Ausnahmen
(Fehlercode)
Einfügen (push)
add(E), push(E)
offer(E), addLast(E)
Entfernen (pop)
remove(), removeFirst()
poll(), pollFirst()
Auslesen (top)
element(), getFirst()
peek(), peekFirst()
Holger Vogelsang
Informatik 2 - Datenstrukturen
36
Elementare Datenstrukturen
Queue – Prinzip




Arbeitsweise einer Queue an einem Beispiel.
Initialzustand:
Queue
offer(E4):
poll():
E2
E3
0
1
2
Queue
offer
E1
E2
E3
E4
0
1
2
3
E2
E3
E4
0
1
2
Queue
poll
Holger Vogelsang
E1
Informatik 2 - Datenstrukturen
37
Elementare Datenstrukturen
Stack – Prinzip

Prinzip eines Stacks (Stapel):
Stack
0


1
2
3
Daten werden in einem Stack am Ende mit offer eingetragen und ebenfalls am Ende mit
poll entfernt  Stapel (häufig auch push/pop).
Einsatzgebiete u.a.:

Zwischenspeicherung von Objekten, wenn eine Rekursion zu einer Iteration aufgelöst
wird.

Text-Parser.
Holger Vogelsang
Informatik 2 - Datenstrukturen
38
Elementare Datenstrukturen
Stack in Java

Es gibt eine Stack-Klasse, die aber nicht optimal ist. Besser: Klasse, die Deque implementiert.
Holger Vogelsang
Informatik 2 - Datenstrukturen
39
Elementare Datenstrukturen
Stack in Java

Die Methoden gibt es doppelt (aus Queue und Deque):
Funktion
Methoden mit Ausnahmen
Methoden ohne Ausnahmen
(Fehlercode)
Einfügen (push)
add(E), push(E)
offer(E), addLast(E)
Entfernen (pop)
removeLast()
pollLast()
Auslesen (top)
getLast()
peekLast()
Holger Vogelsang
Informatik 2 - Datenstrukturen
40
Elementare Datenstrukturen
Stack – Prinzip




Arbeitsweise eines Stacks an einem Beispiel.
Initialzustand:
Stack
offer(E4):
pollLast():
Holger Vogelsang
E1
E2
E3
0
1
2
Stack
offer
E1
E2
E3
E4
0
1
2
3
Stack
pollLast
E1
E2
E3
0
1
2
Informatik 2 - Datenstrukturen
41
Iteratoren
Übersicht
Grafische
Oberflächen
Übersicht
Datenstrukturen
ADTs
Typinfo.,
I/O
Annotationen
JavaScript
Prinzipien
Holger Vogelsang
Layouts
Ereignisse
Datenstrukturen
in Java
Laufzeittypinfo.
Einstieg
Grafikoperationen
Widgets
Elementare
Datenstrukturen
Iteratoren,
Streams
Grafikwidgets
Hashtabellen
Effekte,
Animationen
Bäume
Offene
Punkte
Graphen
Ein-,
Ausgabe
Objekte Vererbung
Module
DOMZugriffe
Informatik 2 - Datenstrukturen
42
Iteratoren
Motivation


Wie lässt sich ein Algorithmus schreiben, der unabhängig von einer konkreten
Datenstruktur arbeiten kann?
Wie können beliebig viele Algorithmen quasi parallel eine unbekannte Datenstruktur
bearbeiten?
Holger Vogelsang
Informatik 2 - Datenstrukturen
43
Iteratoren
Konzept






Problem: Bisher werden Listen oder Vektoren durch eine Schleife, die direkt auf die Elemente
des Containers zugreift, durchlaufen. Wie kann aber ein Algorithmus unabhängig von der
Klasse (dem Container) geschrieben werden?
Ziel: Konstruktion eines Algorithmus unabhängig von der konkreten Klasse (dem Container).
Lösung: Iteratoren dienen als Bindeglied zwischen den Containern und Algorithmen auf
Containern  Prinzip „Umkehr der Abhängigkeiten“!
Iteratoren sind abstrakte Datentypen, mit deren Hilfe auf eine Folge von Objekten
zugegriffen werden kann.
Ein Iterator verweist immer auf ein Objekt innerhalb einer Folge von Objekten.
Die Vorlesung behandelt Iteratoren nicht vollständig.
Holger Vogelsang
Informatik 2 - Datenstrukturen
44
Iteratoren
Konzept

Mögliche Vektor- und Listenimplementierungen mit Sortierung der Elemente.
ArrayList
Extra Platz
sequentielles Durchlaufen
LinkedList
last
first
sequentielles Durchlaufen

Mit einem Iterator können alle Elemente in ihrer Sortierreihenfolge besucht werden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
45
Iteratoren
Konzept

Iterator als Bindeglied zwischen Algorithmus und Datenstruktur:
ArrayList
Zugriff
erzeugt
Zugriff auf die Datenstruktur
über eine einheitliche
Schnittstelle
erzeugt
Algorithmus
Zugriff
first
LinkedList
Holger Vogelsang
last
Informatik 2 - Datenstrukturen
46
Iteratoren
Funktionsweise

Jede Datenstruktur bietet eigene Iteratoren, die eine der beiden folgenden Schnittstellen
implementieren:
normaler Iterator, um eine Collection
vom Anfang bis zum Ende zu
durchlaufen
Iterator, um eine List vorwärts und rückwärts zu
durchlaufen
• erlaubt das Ersetzen von Elementen
• ermöglicht das Auslesen des aktuellen Indexes
Holger Vogelsang
Informatik 2 - Datenstrukturen
47
Iteratoren
Funktionsweise

Jede Datenstruktur hat Methoden, die die Iteratorobjekte erzeugen:
Datenstruktor
Erzeugung des Iterators
Collection<E>
Iterator<E> iterator()
List<E>
ListIterator<E> listIterator()
und
Iterator<E> iterator()
(weil List auch eine Collection ist)
Holger Vogelsang
Informatik 2 - Datenstrukturen
48
Iteratoren
Beispiel
public static void main(
String[] args) {
ArrayList<String> source =
new ArrayList<>();
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
public class IteratorTest {
source.add("Hallo
source.add("Hallo
source.add("Hallo
source.add("Hallo
public static void print(
Iterator<?> iter) {
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
public static <E> void modify(
ListIterator<? super E> iter,
E value) {
while (iter.hasNext()) {
iter.next();
iter.set(value);
}
}
Holger Vogelsang
1");
2");
3");
4");
print(source.iterator());
}
}
modify(source.listIterator(),
"----");
Informatik 2 - Datenstrukturen
49
Iteratoren
Weiteres Beispiel
public static void main(
String[] args) {
ArrayList<String> source =
new ArrayList<>();
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
public class IteratorTest {
public static <E> void copy(
ListIterator<? super E> dest,
ListIterator<? extends E> source) {
while (source.hasNext()) {
// Im Ziel überschreiben?
if (dest.hasNext()) {
dest.next();
dest.set(source.next());
}
// Anhängen, da Ziel kürzer
else {
dest.add(source.next());
}
}
}
Holger Vogelsang
source.add("Hallo
source.add("Hallo
source.add("Hallo
source.add("Hallo
1");
2");
3");
4");
ArrayList<String> dest =
new ArrayList<>();
}
}
copy(dest.listIterator(),
source.listIterator());
kopiert von ArrayList in ArrayList
Informatik 2 - Datenstrukturen
50
Iteratoren
Weiteres Beispiel – Variante 2

copy arbeitet auch mit LinkedList,
Vector, und anderen Datentypen.

copy kann auch zwischen
unterschiedlichen Containern kopieren.
public static void main(
String[] args) {
LinkedList<String> source =
new LinkedList<>();
source.add("Hallo
source.add("Hallo
source.add("Hallo
source.add("Hallo
}
}
public static void main(
String[] args) {
ArrayList<String> source =
new ArrayList<>();
1");
2");
3");
4");
source.add("Hallo
source.add("Hallo
source.add("Hallo
source.add("Hallo
1");
2");
3");
4");
LinkedList<String> dest =
new LinkedList<>();
LinkedList<String> dest =
new LinkedList<>();
copy(dest.listIterator(),
source.listIterator());
copy(dest.listIterator(),
source.listIterator());
}
}
Im SDK wird aber eher mit Collection und List statt mit Iteratoren gearbeitet.
Holger Vogelsang
Informatik 2 - Datenstrukturen
51
Iteratoren
Bedeutung von Iteratoren: Beispiel

Die folgenden Klassen verwalten ein einfaches Dateisystem direkt im Hauptspeicher des
Computers.
File
Directory
-name: String
-access: int
-contents: byte[*]
0..*
-name: String
-access: int
files
subdirectories
0..*
parent 0, 1
// Eine Datei im Dateisystem
public class File {
private String name;
// Name
private int
access;
// Rechte
private byte[] contents; // Inhalt
}
public File(String name,
int access, byte[] contents){}
public boolean equals(
Object other){}
//
Holger Vogelsang
// Verzeichnis im Dateisystem
public class Directory {
private String
name;
private Directory parent; // Vater
private LinkedList<Directory>
subdirectories;
private LinkedList<File> files;
private int
access;
}
//
Informatik 2 - Datenstrukturen
52
Iteratoren
Bedeutung von Iteratoren: Beispiel


Es ergibt sich ein Baum aus Verzeichnissen, in denen jeweils eine Anzahl von Dateien liegen
kann.
Ziel: Schreiben einiger Methoden der Directory-Klasse, die über Iteratoren auf den Baum
zugreifen.
Zählen aller Dateien

Methode der Klasse Directory, die die Anzahl der Dateien in dem Verzeichnis sowie seinen
Unterverzeichnissen ermittelt.
public int countFiles() {
int size = files.size();
for (Iterator<Directory> iter = subdirectories.iterator(); iter.hasNext();) {
Directory currentDirectory = iter.next();
size += currentDirectory.countFiles();
}
return size;
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
53
Iteratoren
Bedeutung von Iteratoren: Beispiel
Suchen einer Datei

Methode der Klasse Directory, die nur in diesem Verzeichnis (nicht in seinen
Unterverzeichnissen) eine Datei sucht. Doppelte Dateinamen kommen nicht vor.
public boolean containsFile(File file) {
for (Iterator<File> iter = files.iterator(); iter.hasNext(); ) {
if (iter.next().equals(file)) {
return true;
}
}
return false;
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
54
Iteratoren
Bedeutung von Iteratoren

Abstrakte Zugriffe auf Datenstrukturen (nicht auf konkrete Collection-Klassen):

Iteratoren

Methoden der Schnittstellen List und Collection
- Viele Algorithmen sind auch als statische Methoden in der Klasse Collections
vorhanden (sortieren usw.).
- Diese arbeiten fast immer auf den Schnittstellen List und Collection.
Holger Vogelsang
Informatik 2 - Datenstrukturen
55
Iteratoren
Eine Auswahl an Algorithmen der Klasse Collections
Algorithmus (Funktion)
static <T> void copy(
List<? super T> dest,
List<? extends T> src)
Bedeutung
Kopiert Elemente eines Containers in einen
anderen. Dabei werden die vorhandenen Elemente
des Ziels überschrieben. Das Ziel muss genügend
Platz für alle Elemente haben. Beispiel:
LinkedList<String> src =
new LinkedList<>();
LinkedList<String> dest =
new LinkedList<>();
src.add("Test");
Collections.copy(dest, src);
static <T> void fill(
List<? super T> list, T obj)
Überschreibt alle Elemente eines Ziel-Containers mit
einem festen Wert. Beispiel:
ArrayList<String> a = new ArrayList<>();
a.add("42");
a.add("66");
Collections.fill(a, "9");
Holger Vogelsang
Informatik 2 - Datenstrukturen
56
Iteratoren
Eine Auswahl an Algorithmen der Klasse Collections
Algorithmus (Funktion)
Bedeutung
static
<T extends Comparable<? super T>>
void sort(List<T> list)
Mit sort kann der Inhalt eines Containers sortiert
werden. Diese Methode verwendet den
Standardtest equals, um zwei Objekte zu
vergleichen. Beispiel:
Vector<String> v = new Vector<>();
v.add("66");
v.add("42");
Collections.sort(v);
static void sort(List<T> list,
Comparator<? super T> c)
Diese Sortier-Methode verwendet den
übergebenen Vergleicher, um zwei Objekte zu
vergleichen.
static void reverse(List<?> list)
Dreht die Reihenfolge der Elemente um. Beispiel:
Vector<String> v = new Vector<>();
v.add("66");
v.add("42");
Collections.reverse(v);
Holger Vogelsang
Informatik 2 - Datenstrukturen
57
Streams
Motivation



Mit Iteratoren lassen sich Datenstrukturen durchlaufen. Geht das auch eleganter?
Wie kann das Durchlaufen von den Aktionen auf den einzelnen Elementen
entkoppelt werden?
Wie lassen sich Datenstrukturen parallel effizienter bearbeiten  nicht Bestandteil
dieser Vorlesung.
Holger Vogelsang
Informatik 2 - Datenstrukturen
58
Streams
Motivation

Zur Erinnerung: Durchlaufen einer Datenstruktur mit Hilfe von Iteratoren, Beispiel:
for (Iterator<String> iter = names.iterator(); iter.hasNext(); ) {
System.out.println(iter.next());
}

Beispiel zur Summation aller positiven Werte:
int sum = 0;
for (Iterator<Integer> iter = values.iterator(); iter.hasNext(); ) {
int value = iter.next();
if (value > 0) {
sum += value;
}
}



Es zeigt sich ein immer wiederkehrendes Muster:

äußere Schleife zum Durchlaufen

innerhalb der Schleife mit dem eigentlichen Code
Problem: Große Datenstrukturen werden immer sequentiell abgearbeitet, obwohl im
Computer vielleicht einige Prozessorkerne nichts zu tun haben.
Die Stream-API übernimmt das Durchlaufen der Collection-Klassen und Anwenden der
Funktionen auf die einzelnen Elemente.
Holger Vogelsang
Informatik 2 - Datenstrukturen
59
Streams
Einführung



Was bietet die Stream-API?

Filtern von Werten (z.B. nur Strings einer bestimmten Länge auswählen)

Abbilden von Daten eines Typs auf andere Typen (z.B. automatisch die Kundennummer
aus einem Kundenobjekt auslesen)

Begrenzung der Anzahl der Elemente (z.B. nur die ersten 100 Elemente berücksichtigen)

Sortierung der Elemente beim Auslesen

Reduktions-Operationen wie z.B. Summenbildung, erstes Element mit einer bestimmten
Bedingung suchen, …

Und vieles mehr, was aus Zeitgründen hier entfällt.
Die übergebenen Funktionen sind Lambda-Ausdrücke.
Ablauf:
Datenstruktur


Funktion1
Funktion2
Funktion3
Ergebnis
Die Collections-Klassen besitzen eine Methode stream, um einen Stream zu erzeugen.
Die Funktionen dürfen sehr häufig keine Nebeneffekte haben (siehe API-Dokumentation).
Holger Vogelsang
Informatik 2 - Datenstrukturen
60
Streams
Einführung

Beispiel zur Summation aller positiven Werte:
int sum = values.stream().filter(v -> v > 0).mapToInt(x -> x).sum();

Was passiert hier?

stream() erzeugt den „Strom von Daten“ aus der Datenquelle values.

Filter erwartet als Parameter ein Objekt der Schnittstelle Predicate<T> mit diesem
vereinfachten Aufbau:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}


Die Methode test wertet den Übergabeparameter aus und entscheidet, ob er die
Bedingung erfüllt oder nicht.
Alle Werte, die der Filter durchlässt, werden aufsummiert:
values
filter()
Alle Werte
aus values
Holger Vogelsang
v>0
mapToInt()
Alle Werte
aus values,
die größer
als 0 sind.
sum()
Werte
als intZahlen
Informatik 2 - Datenstrukturen
sum
Summe
der
Werte
61
Streams
Erzeugung

Weiteres Beispiel, Entfernung von Duplikaten, Sortierung der Zahlen in aufsteigender
Reihenfolge und Beschränkung auf die ersten 10 Werte und Ausgabe:
values.stream().distinct().sorted().limit(10).forEach(System.out::println);

Ablauf:
values
distinct()
Alle Werte
aus values

sorted()
Alle Werte
ohne Duplicate
forEach
limit()
Alle Werte ohne
Duplikate, sortiert
nur die ersten
10 Werte
Ausgabe
auf der
Konsole
Weitere Möglichkeiten, Streams zu erzeugen:

von primitiven Datentypen und Strings:
- Stream<String> str = Stream.of("Douglas", "Adams");
- IntStream str = IntStream.of(1, 2, 3, 4, 5);
- IntStream str = IntStream.range(0, 100); // Werte von 0 - 99
Holger Vogelsang
Informatik 2 - Datenstrukturen
62
Streams
Erzeugung


Erzeugung von Daten auf einem Stream
- Erzeugt einen Stream mit einer (potentiell) endlosen Folge von Strings:
Stream<String> str = Stream.generate(()->"Adams");
- Erzeugt einen Stream mit einer (potentiell) endlosen Folge von Zufallszahlen:
Stream<Double> str = Stream.generate(() -> Math.random());
und noch viele weitere Möglichkeiten!
Holger Vogelsang
Informatik 2 - Datenstrukturen
63
Streams
Filterung und Reduktion


Filtern von Daten aus einem Stream:

filter: Die Funktion ermittelt, ob der Wert behalten werden soll.

map: Die Funktion bildet einen Wert auf einen anderen ab. Beispiel zur Umwandlung
eines Namens auf seinen Anfangsbuchstaben vor der Ausgabe:
names.stream().map(n -> n.charAt(0)).forEach(System.out::println);
Reduktion des Streams auf ein Ergebnis:

max: liefert den größten Wert eines Streams. Beispiel:
names.stream().max(String::compareTo);

sum: liefert die Summe aller Werte.

findFirst: liefert den ersten Wert des Streams.

usw.
Holger Vogelsang
Informatik 2 - Datenstrukturen
64
Streams
Optionale Werte


Was passiert eigentlich beim Aufruf von findFirst oder max, wenn es keinen Wert im
Stream gibt?
Diese Reduktion liefern ein Optional-Objekt zurück:

Optional<String> first = names.stream().findFirst();

Die Methode isPresent dieses Objektes liefert true zurück, wenn es wirklich ein
Ergebnis gab, ansonsten false.

Die Methode get() liefert das Ergebnisobjekt zurück.

Verwendung:
if (first.isPresent()) {
System.out.println(first.get());
}

Warum so kompliziert? Optional kann auch innerhalb eines Stream verwendet werden,
um Werte zu erzeugen, wenn es keinen Wert gespeichert hat  nicht Bestandteil der
Vorlesung.
Holger Vogelsang
Informatik 2 - Datenstrukturen
65
Streams
Sammlung der Ergebnisse, Streams mit primitiv


Sammlung der Ergebnisse von Stream-Operationen in einer Datenstruktur:

List<String> result = stream.collect(Collectors.toList());
Es gibt viel mehr Möglichkeiten, Ergebnisse zu sammeln oder in Gruppen einzuteilen 
lassen wir lieber…

Gute Erklärungen auch unter java.util.stream in der API-Dokumentation.

Streams werden später in der Vorlesung beim Zugriff auf das Dateisystem wiederkommen!
Holger Vogelsang
Informatik 2 - Datenstrukturen
66
Hashtabellen
Übersicht
Grafische
Oberflächen
Übersicht
Datenstrukturen
ADTs
Typinfo.,
I/O
Annotationen
JavaScript
Prinzipien
Holger Vogelsang
Layouts
Ereignisse
Datenstrukturen
in Java
Laufzeittypinfo.
Einstieg
Grafikoperationen
Widgets
Elementare
Datenstrukturen
Iteratoren,
Streams
Grafikwidgets
Hashtabellen
Effekte,
Animationen
Bäume
Offene
Punkte
Graphen
Ein-,
Ausgabe
Objekte Vererbung
Module
DOMZugriffe
Informatik 2 - Datenstrukturen
67
Hashtabellen
Motivation




Wie können Daten laufzeiteffizient verwaltet werden?
Welche Bedingungen gelten dabei?
Wie sieht der Speicherbedarf dazu aus?
Idee: Daten werden in einem Array abgelegt und der Index im Array aus den Daten
berechnet.
Holger Vogelsang
Informatik 2 - Datenstrukturen
68
Hashtabellen
Idee





Problem: Wie kann eine Menge von Werten M, die durch eine Schlüsselmenge K
repräsentiert werden kann, effizient verwaltet werden?

schnelles Einfügen

schnelles Suchen

schnelles Löschen
Gesucht ist eine Abbildung H von der Menge der Schlüssel K in den zur Verfügung stehenden
Adressraum.
Bisherige Lösung: Listen-, Vektordarstellungen, Arrays,...
Dabei wurde beim Suchen jeweils die Speicheradresse ermittelt.
Jetzt: Neue Lösung, bei der die Werte in einem Vektor oder Array liegen und die Werte
mittels einer Schlüsseltransformation auf den Adressraum (den Vektor) abgebildet werden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
69
Hashtabellen
Problem der Schlüsselabbildung



Problem: Menge der möglichen Schlüsselwerte ist viel größer als die Menge der freien
Speicheradressen.

Beispiel: Die Elemente einer Menge werden mit Schlüsseln der Länge 10 Zeichen
beschrieben. Es sollen maximal 1000 Elemente und damit 1000 Adressen verwendet
werden. Wie sollen 2610 mögliche Schlüssel auf 1000 Adressen verteilt werden?

Schlussfolgerung: Die Abbildungsfunktion H kann nicht eindeutig sein.
Die Funktion H wird Hashfunktion genannt.
Das Array, das die Werte aufnimmt, wird Hashtabelle genannt.
Holger Vogelsang
Informatik 2 - Datenstrukturen
70
Hashtabellen
Perfekte Hashfunktion


Eine (perfekte) Hashfunktion kann über einen Schlüsselwert direkt die Position des Objekts
im Feld berechnen.
Beispiel: Es existieren 26 Objekte, die Personen beschreiben:
public class Person {

.... }
Diese Personen werden durch Ihren Nachnamen als Schlüssel identifiziert. Zufällig sind
die Namen über das Alphabet verteilt. Es gibt also zu jedem Großbuchstaben genau eine
Person, deren Namen mit diesem Schlüssel anfängt. Damit ergibt sich eine perfekte
Hashfunktion:
Person[] hashtable = new Person[26];
int hash(String key){
return key.charAt(0) – 'A';
}

Dann kann die Person zu einem Schlüssel einfach so gefunden werden:
Person dozent = hashtable[ hash("Vogelsang") ];
Holger Vogelsang
Informatik 2 - Datenstrukturen
71
Hashtabellen
Perfekte Hashfunktion




Dieser Fall ist ziemlich unwahrscheinlich.
Zumeist werden mehrere Personen den gleichen Anfangsbuchstaben haben und sich dann
die Positionen im Feld teilen müssen.
Man spricht dann auch von Kollision.
Bis zu einem gewissen Grad kann man Kollisionen dadurch begegnen, dass man die
Hashtabelle größer macht und die Hashfunktion so erweitert, dass zum Beispiel der zweite
Buchstabe berücksichtigt wird. Damit das Ergebnis der Berechnung wieder in die Tabelle
passt, wird es später modulo der Tabellengröße gerechnet.
final int SHIFT = 257;
int hash(String key) {
return key.charAt(0) + key.charAt(1) * SHIFT;
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
72
Hashtabellen
Strategien zur Kollisionserkennung


Ab jetzt soll der Fall der nicht-perfekten Hashfunktion betrachtet werden.
Frage: Wie soll verfahren werden, wenn beim Einfügen die Hashfunktion einen Index in der
Tabelle ermittelt, der bereits durch einen anderen Wert belegt ist?
Holger Vogelsang
Informatik 2 - Datenstrukturen
73
Hashtabellen
Hashtabellen mit Verkettung

Idee: Alle Objekte, die auf den selben Index abgebildet werden, werden am selben Index der
Hashtabelle am Ende einer linearen Liste angehängt.
0
HashMap
1
K, V: class
*
lists
Holger Vogelsang
2
3
list
4
5
K, V: class
6
7
first 0, 1
last 0, 1
Informatik 2 - Datenstrukturen
listelement
K, V: class
-data: pair<K, V>
0, 1
next
74
Hashtabellen
Hashtabellen mit Verkettung




Vorteil:

Die Hashtabelle kann nicht überlaufen.

Solange überhaupt noch Speicher vorhanden ist, lassen sich auch Elemente in der
Tabelle eintragen.
Bezeichnung:

mit Verkettung: Beim Suchen müssen nur Objekte mit gleichem Schlüsselwert in einer
verketteten Liste verglichen werden.
Problem:

Der Zugriff wird mit zunehmender Anzahl der Kollisionen langsamer.

Die Suche kann sogar zur linearen Suche entarten. Ist die zu erwartende Anzahl von
Objekten bekannt, kann auch die Lösung mit einer Liste gut sein.
Eine mögliche Lösung:

Einsatz von Bäumen statt Listen.
Holger Vogelsang
Informatik 2 - Datenstrukturen
75
Hashtabellen
Hashtabellen mit Verkettung

Nachteile von Hashtabellen mit Verkettung:

Es muss dynamisch Speicher belegt werden, was das Eintragen in die Hashtabelle zu
einer relativ aufwändigen Operation macht.

Dynamische Container brauchen mehr Platz als ein statisches Feld mit gleich vielen
Elementen.

Bei zunehmender Anzahl der Kollisionen ist Listensuche erforderlich.
Holger Vogelsang
Informatik 2 - Datenstrukturen
76
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung

Implementierung einer sehr einfachen und nicht optimalen Hashtabelle für Schlüssel und
Werte eines beliebigen Typs mit Kollisionslisten (Projekt HashMap, Pair überschreibt
equals für die Duplikatprüfung beim Einfügen):
public class SimpleHashMap<K,V> implements Iterable<SimpleHashMap<K,V>.Pair> {
// Paar für Schlüssel (K) und Wert (V), normalerweise impl. Interface
class Pair {
private K key;
// + Getter
private V value; // + Getter und Setter
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object otherPair) {
if (otherPair != null && otherPair.getClass() == getClass()) {
return ((Pair) otherPair).key.equals(key);
}
return false;
}
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
77
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
// Vektor mit den Listen, die ihrerseits die Paare aufnehmen
private ArrayList<LinkedList<Pair>> entries;
// Größe des Vektors übergeben
public SimpleHashMap(int size) {
entries = new ArrayList<>();
// Im Vektor alle Listen anlegen
for (int i = 0; i < size; ++i) {
entries.add(new LinkedList<Pair>());
}
}
// Schlüssel "key" und Wert "value" in der Hash-Tabelle ablegen
public void put(K key, V value) {
int index = indexFor(key.hashCode());
Pair pair = new Pair(key, value);
// Eventuell vorhandenes Paar mit id. Schlüssel löschen
LinkedList<Pair> list = entries.get(index);
list.remove(pair);
list.add(pair);
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
78
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
// Wert zu einem Schlüssel auslesen
public V get(K key) {
int index = indexFor(key.hashCode());
// Die Listen sequentiell durchsuchen
for (Pair pair: entries.get(index)) {
if (pair.key.equals(key)) {
return pair.value;
}
}
return null;
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
79
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
/**
* Berechnet den Index aus einem Hashwert. Die Modulo-Berechnung
* <code>abs(hashCode % laenge)</code> ist nicht ausreichend, da
* der <code>hashCode</code> den Wert <code>Integer.MIN_VALUE</code>
* besitzen kann. Der Absolutwert von <code>Integer.MIN_VALUE</code>
* ist wiederum <code>Integer.MIN_VALUE</code>, also negativ!
* @param hashCode Berechneter Hashwert.
* @return Index innerhalb der Tabelle.
*/
private int indexFor(int hashCode) {
int absHashCode = abs(hashCode);
if (absHashCode < 0) {
absHashCode = 0;
}
return absHashCode % entries.size();
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
80
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
// Die Hash-Tabelle benötigt einen Iterator. Der wird
// hier etwas anders als in der HashMap des JDK implementiert.
// Innere Klasse von SimpleHashMap
class HashMapIterator implements Iterator<Pair> {
private int index;
private Iterator<Pair> listIterator;
public HashMapIterator() {
// Erste nicht-leere Liste finden
for (LinkedList<Pair> list: entries) {
if (list.size() > 0) {
listIterator = list.iterator();
break;
}
++index;
}
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
81
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
@Override
public boolean hasNext() {
// Gibt es überhaupt ein Element?
if (index >= 0 && index < entries.size() && listIterator != null) {
// Hat die aktuelle Liste ein weiteres Element?
if (listIterator.hasNext()) {
return true;
}
// Nächste Liste mit Eintrag suchen
int nextIndex = index;
while (++nextIndex < entries.size()) {
// Hat sie einen Eintrag?
if (entries.get(nextIndex).size() > 0) {
return true;
}
}
}
return false;
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
82
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
@Override
public Pair next() {
// Gibt es überhaupt ein Element?
if (index >= 0 && index < entries.size() && listIterator != null) {
// Hat die aktuelle Liste ein weiteres Element?
if (listIterator.hasNext()) {
return listIterator.next();
}
// Nächste Liste mit Eintrag suchen
while (++index < entries.size()) {
// Hat sie einen Eintrag?
if (entries.get(index).size() > 0) {
listIterator = entries.get(index).iterator();
return listIterator.next();
}
}
}
throw new NoSuchElementException();
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
83
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
}
}
// Ein Löschen des aktuellen Elementes ist hier nicht implementiert
// --> ist etwas länglich.
@Override
public void remove() {
throw new UnsupportedOperationException();
}
// Methode, um den Iterator der Hash-Tabelle auszulesen. Auch dieses
// ist hier anders als in der HashMap des JDK umgesetzt.
@Override
public Iterator<Pair> iterator() {
return new HashMapIterator();
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
84
Hashtabellen
Hashtabellen mit Verkettung: Eine einfache Implementierung
public class SimpleHashMapTest {
public static void main(String[] args) {
SimpleHashMap<String, Integer> simpleHashMap = new SimpleHashMap<>(31);
simpleHashMap.put("Answer", 42);
simpleHashMap.put("What?", 66);
for (Iterator<SimpleHashMap<String, Integer>.Pair> iter =
simpleHashMap.iterator(); iter.hasNext();) {
SimpleHashMap<String, Integer>.Pair pair = iter.next();
System.out.println(pair.getKey() + ": " + pair.getValue());
}
}
}
for (SimpleHashMap<String, Integer>.Pair pair: simpleHashMap) {
System.out.println(pair.getKey() + ": " + pair.getValue());
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
85
Hashtabellen
Hashtabellen mit Verkettung: Aufwandsabschätzung

Annahmen:

Die Auslastung der Tabelle ist α = N / M. N = Anzahl der Schlüssel, M = Anzahl der
Speicherplätze in der Tabelle.

Der Schlüsselbereich S ist uniform, d.h., die Schlüssel werden gleichmäßig auf die Tabelle
verteilt.

Die Berechnung der Hashfunktion hat Aufwand O(1).
Operation
Holger Vogelsang
Aufwand
Einfügen (inkl. Duplikatsuche)
O(N), im Mittel θ(α+1)
Löschen
O(N), im Mittel θ(α+1)
Suche, erfolgreich
O(N), im Mittel θ(α+1)
Suche, erfolglos
O(N), im Mittel θ(α+1)
Informatik 2 - Datenstrukturen
86
Hashtabellen
Offene Hashtabellen/Geschlossene Hashtabellen





Ein anderer Ansatz zur Kollisionsbehandlung sind offene Hashtabellen (manchmal auch
geschlossene Hashtabellen):

offen: offene Adressierung im Array

geschlossen: Begrenzung der maximalen Schlüssel im Array
Ansatz: Verzicht auf dynamische Verknüpfungen wie Listen etc.
Statt dessen: Ist beim Einfügen der gesuchte Index belegt, so wird ein freier Nachbarindex
gesucht und der Wert dort eingetragen.
Zu Bestimmung eines Nachbarindexes existieren verschiedene Ansätze.
Die Suche erfolgt analog:

Zunächst wird durch den Schlüssel ein Index ermittelt.

Steht dort nicht der gesuchte Wert, so werden die Nachbarindizes durchsucht.
0
Holger Vogelsang
1
2
3
4
5
6
7
Informatik 2 - Datenstrukturen
87
Hashtabellen
Offene Hashtabellen: Einfügen
// Fiktive Beispielimplementierung einer Hash-Map ohne Duplikatsprüfung
public class SimpleHashMap<K,V> {
// Schlüssel- und Wertepaar
class Pair {
public K key;
public V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object otherPair) {
if (otherPair != null && otherPair.getClass() == getClass()) {
return ((Pair) otherPair).key.equals(key);
}
return false;
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
88
Hashtabellen
Offene Hashtabellen: Einfügen
// ArrayList mit den Paaren
private ArrayList<Pair> entries;
// Hash-Map in einer vorgegebenen Größe erzeugen
public SimpleHashMap(int size) {
entries = new ArrayList<Pair>(size);
for (int i = 0; i < size; ++i) {
entries.add(null); // jeder Eintrag ist leer
}
}
// Schon bekannt aus dem vorherigen Beispiel
private int indexFor(int hashCode) {
int absHashCode = abs(hashCode);
if (absHashCode < 0) {
absHashCode = 0;
}
return absHashCode % entries.size();
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
89
Hashtabellen
Offene Hashtabellen: Einfügen
// Einfügen ohne Prüfung auf Duplikate (nicht sehr praxisnah).
public void put(K key, V value) {
int startIndex
= indexFor(key.hashCode());
int count
= 0;
int currentIndex = startIndex;
boolean finished = false;
}
do {
if (entries.get(currentIndex) == null) {
// freien Platz gefunden, Paar erzeugen und eintragen
entries.set(currentIndex, new Pair(key, value));
finished = true;
}
else {
// berechne nächsten Index
count++;
currentIndex = (startIndex + nextStep(count)) % entries.size();
}
} while (!finished && (count < entries.size()));
Holger Vogelsang
Informatik 2 - Datenstrukturen
90
Hashtabellen
Offene Hashtabellen: Einfügen
// Suche nach einem
public V get(K key)
int startIndex
int count
int currentIndex
}
Wert anhand seines Schlüssels
{
= indexFor(key.hashCode());
= 0;
= startIndex;
// Solange suchen, bis ein leerer Eintrag gefunden wurde
do {
if (entries.get(currentIndex) == null) {
return null;
}
else { // gefunden!
if (entries.get(currentIndex).key.equals(key)) {
return entries.get(currentIndex).value;
}
// Berechne nächsten Index
count++;
currentIndex = (startIndex + nextStep(count)) % entries.size();
}
} while (count < entries.size());
return null;
Holger Vogelsang
Informatik 2 - Datenstrukturen
91
Hashtabellen
Offene Hashtabellen: Lineares Sondieren


Die Methode int nextStep(int attempt) ermittelt den nächsten zu testenden Eintrag.
Im einfachsten Fall ist nextStep:
private int nextStep(int attempt) {
return attempt;
}



In diesem Fall nennt man die Vorgehensweise auch lineares Sondieren, und für die
Testindizes gilt:
h0 = hash(key);
hi = (h0 + i);
Nachteil: Die Schlüssel ballen sich um primäre Schlüssel (Schlüssel, die beim Einfügen nicht
kollidieren).
Ziel: nextStep sollte so gewählt werden, dass die Schlüssel wiederum gleichmäßig auf die
freien Plätze verteilt werden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
92
Hashtabellen
Offene Hashtabellen: Quadratisches Sondieren






Die Methode nextStep ermittelt den nächsten Eintrag durch eine quadratische Funktion.
h0 = hash(key);
hi = (h0 + i2);
Vorteil: Die Verteilung ist einfach zu berechnen und verhindert im Wesentlichen primäre
Ballungen.
Nachteil: Es werden nicht alle Indizes der Hashtabelle berücksichtigt, damit wird u.U. ein
freier Eintrag nicht gefunden.
Es wird aber mindestens die halbe Tabelle durchsucht, wenn deren Größe eine Primzahl ist.
Der Nachteil trägt nur dann, wenn die Tabelle relativ voll ist.
In der Praxis sollte man eine Auslastung von max. 50% vorsehen.
Holger Vogelsang
Informatik 2 - Datenstrukturen
93
Hashtabellen
Offene Hashtabellen: Doppelte Hashfunktion



Die Methode nextStep ermittelt den nächsten Eintrag durch einen Aufruf einer anderen
Hashfunktion g.
h0 = hash(key);
hi = (h0 + i * g(key));
Doppelte Hashfunktionen zeigen das beste Verhalten, wenn die beiden Hashfunktionen
hinreichend unabhängig voneinander sind.
Die Wahrscheinlichkeit, dass beide Hashfunktionen den gleichen Wert liefern, sollte also
gering sein.
Holger Vogelsang
Informatik 2 - Datenstrukturen
94
Hashtabellen
Offene Hashtabellen: Pseudozufallszahlen


Nach der Initialisierung durch den Konstruktor liefern sukzessive Aufrufe von nextStep der
Reihe nach die Pseudozufallszahlen.
Die Überlaufstrategie mit einem Pseudozufallszahlengenerator leidet auch unter sekundärer
Clusterbildung, es sei denn, der Startwert wird vom Schlüssel abhängig gemacht.
Holger Vogelsang
Informatik 2 - Datenstrukturen
95
Hashtabellen
Dynamische Hashtabellen


Dynamische Hashtabellen sind in der Lage, sich bei Bedarf automatisch zu vergrößern oder
zu verkleinern, ohne dass ein komplettes Neuberechnen der Hashwerte erforderlich ist.
 soll hier nicht betrachtet werden (zu kompliziert…)
Holger Vogelsang
Informatik 2 - Datenstrukturen
96
Hashtabellen
Vergleich der Verfahren – einige Zahlen



Vorteile der Hashtabelle mit Verkettung:

Sie erlaubt Auslastungen α > 100%.

Sie unterstützt sehr einfach das Löschen.

Ein gegebener Schlüssel wird immer abgespeichert.
Vorteile der offenen Hashtabellen:

Die Zugriffsoperationen sind wesentlich effizienter.

Die Algorithmen sind einfacher zu implementieren.
Konkrete Werte der durchschnittlichen Anzahl von Einfüge-Versuchen für Füllgrade zwischen
60% und 95% (ohne Test auf Duplikate):
Name
Anzahl Versuche bei Füllgrad α
60%
70%
80%
90%
95%
Kollisionsliste
1,00
1,00
1,00
1,00
1,00
Lineares Sondieren
1,75
2,17
3,00
5,50
10,50
Pseudozufallszahlen
2,29
4,01
8,05
23,02
59,91
Holger Vogelsang
Informatik 2 - Datenstrukturen
97
Hashtabellen
Vergleich der Verfahren – einige Zahlen

Verlauf beim Einfügen
70
60
50
mit Verkettung
offen, lineares Sondieren
40
offen, Pseudozufallszahlen
30
20
10
0
Holger Vogelsang
60%
70%
80%
90%
95%
Informatik 2 - Datenstrukturen
98
Hashtabellen
Implementierungen in Java

Es gibt noch weitere Klassen (siehe Folgeseiten)
Holger Vogelsang
Informatik 2 - Datenstrukturen
99
Hashtabellen
Implementierungen in Java

Hashtabellenimplementierungen in Java:

HashMap<K,V>:
- Ablage von Schlüssel-/Wertepaaren
- Schlüsselduplikate sind nicht erlaubt.
- Mehrere Iterierungsmöglichkeiten  kommen gleich

Hashtable<K,V>:
- Ablage von Schlüssel- Wertepaaren
- Schlüsselduplikate sind nicht erlaubt.
- im Gegensatz zu HashMap thread-sicher
- Iterieren wie bei HashMap

HashSet<E>:
- Ablage von Schlüsseln
- Duplikate sind nicht erlaubt.
- Iteratorzugriff mit der Methode public Iterator<E> iterator()
Holger Vogelsang
Informatik 2 - Datenstrukturen
100
Hashtabellen
Implementierungen in Java


LinkedHashMap<K,V>:
- Ablage von Schlüssel- Wertepaaren
- Schlüsselduplikate sind nicht erlaubt.
- Alle Paare sind untereinander durch eine doppelt-verkettete Liste verbunden.
Entweder:
• in Einfügereihenfolge, um diese zu erhalten
• oder in jeweils aktualisierter Zugriffsreihenfolge beim Lesen, damit die
Elemente mit den häufigsten Zugriffen vorne in der Liste stehen  Cache!
- Iterieren wie bei HashMap
LinkedHashSet<E>:
- siehe LinkedHashMap und HashSet (nur Schlüssel, verbunden über eine Liste)
Holger Vogelsang
Informatik 2 - Datenstrukturen
101
Hashtabellen
Implementierungen in Java



Beispiel zum Einsatz einer Hash-Tabelle in einer Server-Anwendung:

Wenn der Browser mit GZIP komprimierte Dateien verarbeiten kann und

wenn der Dateityp nicht ohnehin schon komprimierte Daten enthält

dann komprimiere die Daten vor dem Versand mit GZIP.
Die Hash-Tabelle enthält die Dateiendungen der nicht zu komprimierenden Dateien.
Ausschnitt:
private HashSet<String> compressedFileTypes = new HashSet<String>();
//
public void doFilter(...) {
if (!isGzipSupportedByBrowser(request) ||
compressedFileTypes.contains(fileType)) {
chain.doFilter(request, response);
return;
}
// vor dem Versand komprimieren
GzipResponse gzipRespone = new GzipResponse(response);
chain.doFilter(request, gzipRespone);
gzipResponse.close();
Holger Vogelsang
Informatik 2 - Datenstrukturen
102
Hashtabellen
Implementierungen in Java

Maps und Zugriff auf Iteratoren
Holger Vogelsang
Informatik 2 - Datenstrukturen
103
Hashtabellen
Implementierungen in Java


Es existieren drei Varianten zum Iterieren:

Set<K> keySet(): Liefert die Menge aller Schlüssel, über die iteriert werden kann. Die
Werte sind nicht direkt zugreifbar.

Collection<V> values(): Liefert alle Schlüssel, über die iteriert werden kann. Deren
Zuordnung zu den Schlüsseln ist nicht mehr erkennbar.

Set<Map.Entry<K,V>> entrySet(): Liefert die Menge aller Schlüssel-/Werte-Paare,
über die iteriert werden kann.
Beispiel mit direkter Iterator-Verwendung:
HashMap<String, Integer> map = new HashMap<>();
// füllen
for (Iterator<Map.Entry<String, Integer>> entryIter = map.entrySet().iterator();
entryIter.hasNext(); ) {
Map.Entry<String, Integer> entry = entryIter.next();
String key = entry.getKey();
Integer value = entry.getValue();
// …
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
104
Hashtabellen
Implementierungen in Java


Es existieren dementsprechend auch drei Varianten, um einen Stream zu erhalten:

Stream<K> keySet().stream(): Liefert einen Stream, der alle Schlüssel beinhaltet.

Stream<V> values().stream(): Liefert einen Stream, der alle Werte beinhaltet.

Stream<Map.Entry<K,V>> entrySet().stream(): Liefert Stream, der die Menge
aller Schlüssel-/Werte-Paare beinhaltet.
Beispiel:
HashMap<String, Integer> map = new HashMap<>();
// füllen
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> keyValueStream = map.entrySet().stream();
Holger Vogelsang
Informatik 2 - Datenstrukturen
105
Hashtabellen
Weiterer Einsatz von Hashfunktionen

Wozu kann eine Hashfunktion noch dienen?

Berechnung von Prüfsummen, um zu testen, ob eine Nachricht oder Datei verfälscht
wurde.

Beispiel: siehe http://tomcat.apache.org/download-80.cgi
Datei
Holger Vogelsang
Hashwert
Informatik 2 - Datenstrukturen
106
Hashtabellen
Weiterer Einsatz von Hashfunktionen

SHA1 ist eine 64-Bit Prüfsumme (hier über den Inhalt der Datei):

Algorithmus: siehe https://de.wikipedia.org/wiki/Secure_Hash_Algorithm
Holger Vogelsang
Informatik 2 - Datenstrukturen
107
Hashtabellen
Wahl einer Hashfunktion


Ziel: Gleichmäßige Verteilung der Werte in der Tabelle.
Beispiel für eine schlechte Hashfunktion:

Studenten-Objekte sollen in einer Hashtabelle gespeichert werden.

Jeder Student hat eine 6-stellige Matrikelnummer, die fortlaufend vergeben wird.

Größe der Hashtabelle: 10001.

Hashfunktion 1:
- Die ersten vier Stellen der Matrikelnummer bilden den Hash-Wert.
- Problem: Alle Datensätze eines Jahrganges werden auf sehr wenige Positionen
abgebildet.

Hashfunktion 2:
- Die letzten vier Stellen der Matrikelnummer bilden den Hash-Wert.
- Besser: Die Datensätze verteilen sich gleichmäßiger auf die Tabelle.
Holger Vogelsang
Informatik 2 - Datenstrukturen
108
Hashtabellen
Wahl einer Hashfunktion

Einige Hinweise zur Wahl der Hashfunktion:

Integer-Zahlen i: die Zahl i selbst oder i mod 2n (n ist eine große Primzahl)

Fließkommazahlen: Addition oder andere Verknüpfung von Mantisse und Exponent

Strings: Addition der ASCII/Unicode-Werte einiger/aller Zeichen, eventuell mit einem
Faktor gewichtet

Komplexere Objekte: Reduktion auf primitive Datentypen, die Attribute des Objektes
sind. Beispiel:
public class Rectangle {
private int x;
private int y;
private int w;
private int h;
}
Holger Vogelsang
Hashwert aus Verknüpfung der
Koordinaten + Größe
Informatik 2 - Datenstrukturen
109
Hashtabellen
Wahl einer Hashfunktion: Beispiele von Java


Die Ermittlung einer guten Hashfunktion wird hier nicht besprochen.
Hashfunktionen in Java (Ergebnis ist immer int):

für einen String s der Länge n:
- s[0]*31(n-1) + s[1]*31(n-2) + ... + s[n-1]
- 0 für leere Strings

für Byte-, Short- und Integer-Zahlen:
- Die Zahl ist der Hashwert.

für Long-Zahlen:
- Exklusiv-Oder-Verknüpfung der unteren und oberen 32 Bit
- Rückgabe der unteren 32 Bit

für Double-Zahlen:
- Umwandlung der Bit-Repräsentation der Zahl in long
- Exklusiv-Oder-Verknüpfung der unteren und oberen 32 Bit (um Mantisse und
Exponent zu berücksichtigen)
- Rückgabe der unteren 32 Bit
Holger Vogelsang
Informatik 2 - Datenstrukturen
110
Hashtabellen
Wahl einer Hashfunktion: Beispiele von Java
für Boole‘sche Werte:
- true: 1231
- false: 1237

für einen Vector bzw. eine List der Länge n:
- v[0].hashCode()*31(n-1) + v[1].hashCode()*31(n-2) + ... +
v[n-1] .hashCode()
- 0 für leere Vektoren
Wichtig in Java:

Objekte liefern durch Überschreiben der Methode int hashCode() ihren eigenen
Hashwert zurück.

Der Wert darf sich bei mehreren Aufrufen der Methode nicht ändern, solange sich das
Objekt nicht ändert.

Wenn zwei Objekte beim Vergleich mit der equals-Methode gleich sind, so müssen
auch ihre Hashwerte identisch sein.


Holger Vogelsang
Informatik 2 - Datenstrukturen
111
Hashtabellen
Wahl einer Hashfunktion: Beispiele von Java

Beispiel für Klasse java.awt.geom.Rectangle2D (stark vereinfacht!):
public abstract class Rectangle2D {
private double x;
private double y;
private double w;
private double h;
@Override
public int hashCode() {
long bits = Double.doubleToLongBits(x);
bits += Double.doubleToLongBits(y) * 37;
bits += Double.doubleToLongBits(w) * 43;
bits += Double.doubleToLongBits(h) * 47;
return (((int) bits) ^ ((int) (bits >> 32)));
}
}
// Die equals-Methode liefert dann true, wenn
// alle x, y, w, h bei beiden Objekten gleich sind.
Holger Vogelsang
Informatik 2 - Datenstrukturen
112
Bäume
Übersicht
Grafische
Oberflächen
Übersicht
Datenstrukturen
ADTs
Typinfo.,
I/O
Annotationen
JavaScript
Prinzipien
Holger Vogelsang
Layouts
Ereignisse
Datenstrukturen
in Java
Laufzeittypinfo.
Einstieg
Grafikoperationen
Widgets
Elementare
Datenstrukturen
Iteratoren,
Streams
Grafikwidgets
Hashtabellen
Effekte,
Animationen
Bäume
Offene
Punkte
Graphen
Ein-,
Ausgabe
Objekte Vererbung
Module
DOMZugriffe
Informatik 2 - Datenstrukturen
113
Bäume
Motivation





Was sind Bäume?
Wozu dienen Bäume?
Wie sind die Daten in einem Baum sortiert?
Wie kann ein Baum effizient implementiert werden?
Verschiedene Baumarten für unterschiedliche Einsatzgebiete
Holger Vogelsang
Informatik 2 - Datenstrukturen
114
Bäume
Übersicht




Beispiel: Teilebaum eines „Autos“ (der Fahrer ist eher kein Teil des Autos…)
Es ergibt sich ein Aufbau der Teile wie bei einem Stammbaum.
Jeder Strich von oben nach unten bedeutet dabei, dass sich das Ausgangsobjekt aus den
tiefer liegenden Objekten zusammensetzt.
Zusammengesetzte Objekte können durch einen solchen Baum eindeutig beschrieben
werden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
115
Bäume
Übersicht

Stammbaum wichtiger Programmiersprachen (bis 2003)
[P. Henning, H. Vogelsang (Hrsg.), „Handbuch Programmiersprachen“]
Holger Vogelsang
Informatik 2 - Datenstrukturen
116
Bäume
Übersicht

Darstellung von (X)HTML-Seiten im Browser
wird im Browser intern durch
einen Baum (DOM) repräsentiert
Holger Vogelsang
Informatik 2 - Datenstrukturen
117
Bäume
Übersicht

Quelltext-Verwaltung in Eclipse
Quelltext wird
intern als Baum
dargestellt
Holger Vogelsang
Informatik 2 - Datenstrukturen
118
Bäume
Übersicht

Dokumentenstruktur
Holger Vogelsang
Informatik 2 - Datenstrukturen
119
Bäume
Übersicht

Scene Graph von JavaFX:
Scene
FlowPane
Button
Button
Button
Ellipse
Holger Vogelsang
Informatik 2 - Datenstrukturen
Group
Rectangle
120
Bäume
Übersicht

Entscheidungsbäume:
Person
Temperatur
Regenwahrscheinlichkeit
Norddeutscher?
< 20
< 30%
ja
>= 20
>= 30%
nein
ja
< 50%
nein
>= 50%
ja
nein
im Meer
baden?
Holger Vogelsang
Informatik 2 - Datenstrukturen
121
Bäume
Begriffe

Nicht nur für zusammengesetzte Objekte können Bäume verwendet werden. In der
Informatik werden Bäume häufig verwendet, um effizient Objekte einzufügen, zu suchen und
zu löschen. Begriffsübersicht
Kante
linker Sohn
(der Wurzel)
Wurzel
42
27
68
6
39
12
34
linker
Teilbaum
Holger Vogelsang
rechter Sohn
(der Wurzel)
51
41
75
64
72
Knoten
Blätter (keine
Nachfolger)
rechter
Teilbaum
Informatik 2 - Datenstrukturen
122
Bäume
Binärer Suchbaum

In dieser Vorlesung werden Bäume mit den folgenden Eigenschaften behandelt:

Die Knoten enthalten Werte, die sich vergleichen lassen. In der Praxis wird man für die
Schlüssel die equals-Methode überschreiben und Comparable implementieren bzw.
einen Comparator übergeben.

Für jeden Knoten gilt, dass er einen eindeutigen rechten Sohn und einen eindeutigen
linken Sohn hat (sofern es diesen jeweils gibt).

Der linke Sohn (sofern es ihn gibt) hat immer einen niedrigeren Wert als der rechte Sohn
(sofern es ihn gibt).

Der linke Sohn (sofern es ihn gibt) eines Knotens hat immer einen kleineren Wert als der
Knoten.

Der rechte Sohn (sofern es ihn gibt) eines Knotens hat immer einen größeren Wert als
der Knoten.
Holger Vogelsang
Informatik 2 - Datenstrukturen
123
Bäume
Binärer Suchbaum – eine einfache Implementierung

Beispielklasse für einen Baum mit Schlüsseln und Werten (siehe Projekt TreeMap):
public class SimpleTreeMap<K extends Comparable<K>,V> {
// Gleiche Paar-Klasse wie in der SimpleHashMap
class Pair {
public K key;
public V value;
public Pair(K key, V value) { /* ... */ }
}
// Knoten des Binärbaumes
class Node {
private Pair data;
private Node left;
private Node right;
public Node(Pair data, Node left, Node right) { /* ... */ }
// Getter und Setter
}
// Wurzel des Baums
private Node root;
Holger Vogelsang
Informatik 2 - Datenstrukturen
124
Bäume
Binärer Suchbaum – Komplettes Durchlaufen

Es gibt mehrere Arten, einen Baum zu durchlaufen:

Preorder: Zuerst wird der Knoten selbst ausgegeben, dann seine Söhne.

Inorder: Zuerst werden der linke Sohn, dann der Knoten selbst, dann der rechte Sohn
ausgegeben.

Postorder: Zuerst werden die Söhne des Knotens ausgegeben, dann der Knoten selbst.
Holger Vogelsang
Informatik 2 - Datenstrukturen
125
Bäume
Binärer Suchbaum – Traversierung

Preorder:
public void dump(Node node) {
if (node != null) {
System.out.println(node.data.key);
dump(node.left);
dump(node.right);
}
}

Inorder:
public void dump(Node node) {
if (node != null) {
dump(node.left);
System.out.println(node.data.key);
dump(node.right);
}
}

Postorder: Analog
Holger Vogelsang
Informatik 2 - Datenstrukturen
126
Bäume
Binärer Suchbaum – Traversierung

Preorder, nicht-rekursiv:
void dump(Node node) {
stack.offer(node);
while (stack.size() > 0) {
node = stack.pollLast();
System.out.println(node.data.key);
if (node.right != null) {
stack.offer(node.right);
}
if (node.left != null) {
stack.offer(node.left);
}
}
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
127
Bäume
Binärer Suchbaum – Traversierung und Ausgabe

Levelorder (Queue anstatt Stack):
void dump(Node node) {
queue.offer(node);
while (queue.size() > 0) {
node = queue.poll();
System.out.println(node.data.key);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
128
Bäume
Binärer Suchbaum – Suche anhand eines Beispiels

Suche nach dem Schlüssel 34:

Enthält der aktuelle Knoten den gesuchten Schlüssel: fertig.

Ist der gesuchte Schlüssel kleiner als der Schlüssel im Knoten: linker Sohn

Ist der gesuchte Schlüssel größer als der Schlüssel im Knoten: rechter Sohn
34
42
27
68
6
39
12
Holger Vogelsang
34
51
41
75
64
72
Informatik 2 - Datenstrukturen
129
Bäume
Binärer Suchbaum – Implementierung der Suche

Suchmethode:
public V get(K key) {
// Start am Wurzelknoten
Node searchNode = root;
}
// Solange es noch Knoten gibt und der aktuelle Knoten nicht
// den gesuchten Schlüssel enthält, suche weiter.
while ((searchNode != null) &&
(!searchNode.data.key.equals(key))) {
// Wenn der gesuchte Schlüssel größer als der Schlüssel des
// Knotens ist, nimm den rechten Zweig, ansonsten den linken.
searchNode = (key.compareTo(searchNode.data.key) > 0) ?
searchNode.right:
searchNode.left;
}
return searchNode != null ? searchNode.data.value : null;
Holger Vogelsang
Informatik 2 - Datenstrukturen
130
Bäume
Binärer Suchbaum – Algorithmus zum Einfügen

Vorgehensweise beim Einfügen:

Zunächst wird die Stelle gesucht, an der sich der Schlüssel im Baum befinden sollte.

Ist der Schlüssel schon vorhanden, so wird einfach die Adresse dieses Schlüssels
zurückgegeben.

Ist er nicht vorhanden, so wird ein neuer Knoten erzeugt und dort eingehängt.
Holger Vogelsang
Informatik 2 - Datenstrukturen
131
Bäume
Binärer Suchbaum – Einfügen anhand eines Beispiels

Beispiel: Einfügen des Schlüssels 36
36
42
27
68
6
39
12
34
51
41
75
64
72
36
Holger Vogelsang
Informatik 2 - Datenstrukturen
132
Bäume
Binärer Suchbaum – Algorithmus zum Löschen

Die komplizierteste Funktion ist das Löschen aus einem binären Teilbaum. Dazu müssen drei
Fälle unterschieden werden:
1. Der zu löschende Knoten hat gar keinen Sohn. Damit kann er direkt gelöscht werden.
2. Der zu löschende Knoten hat genau einen Sohn. Dann wird der Sohn in den aktuellen
Knoten „kopiert“ und der Sohn gelöscht.
3. Der zu löschende Knoten hat zwei Söhne. Dann wird im linken Teilbaum der Sohn mit
dem größten Schlüssel gesucht und als neuer zentraler Knoten eingefügt (oder im
rechten Teilbaum der Knoten mit dem kleinsten Schlüssel).
Holger Vogelsang
Informatik 2 - Datenstrukturen
133
Bäume
Binärer Suchbaum – Löschen anhand eines Beispiels

Beispiel: Löschen des Schlüssels 36 (Fall 1, der Knoten hat keinen Nachfolger)
42
27
68
6
39
12
34
51
41
75
64
72
36
kann direkt gelöscht werden
Holger Vogelsang
Informatik 2 - Datenstrukturen
134
Bäume
Binärer Suchbaum – Löschen anhand eines Beispiels

Beispiel: Löschen des Schlüssels 34 (Fall 2, der Knoten hat einen direkten Nachfolger)
42
27
68
6
39
12
34
51
41
75
64
72
36
36
36 ersetzt 34, die alte 36 wird gelöscht
Holger Vogelsang
Informatik 2 - Datenstrukturen
135
Bäume
Binärer Suchbaum – Löschen anhand eines Beispiels

Beispiel: Löschen des Schlüssels 42 (Fall 3, der Knoten wird durch den Knoten mit dem
größten Schlüssel des linken oder dem kleinste des rechten Teilbaums ersetzt  der hat
immer nur einen direkten Nachfolger)
42
27
68
41
6
39
12
34
51
41
75
64
72
36
41 ersetzt 42, die alte 41 wird gelöscht
Holger Vogelsang
Informatik 2 - Datenstrukturen
136
Bäume
Binärer Suchbaum – Beispielanwendung
public class SimpleTreeMapTest {
public static void main(String[] args) {
SimpleTreeMap<String, Integer> simpleTreeMap = new SimpleTreeMap<>();
simpleTreeMap.add("Question", 66);
simpleTreeMap.add("Answer", 42);
System.out.println(simpleTreeMap.get("Question"));
System.out.println(simpleTreeMap.get("Answer??"));
}
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
137
Bäume
Binärer Suchbaum – Aufwandsabschätzung
Annahme: Der Baum ist nicht balanciert.
Operation
Holger Vogelsang
Aufwand
Einfügen, sortierte Reihenfolge
O(N)
Einfügen, zufällige Reihenfolge
O(N), im Mittel θ(ln N)
Löschen
O(N), falls degeneriert
Indexzugriff
O(N)
Suche, degeneriert
O(N)
Suche, optimal eingefügt
O(ln N)
Informatik 2 - Datenstrukturen
138
Bäume
Beispiel zum binären Suchbaum


Darstellung eines arithmetischen Ausdrucks als Baum:
f = (a - b) * c - (d / b + sin(e))
Tipp: Klammerungen werden durch die Höhen der Operatorknoten untereinander
wiedergegeben.
Holger Vogelsang
Informatik 2 - Datenstrukturen
139
Bäume
Balancierter Baum (AVL) – Idee





Ein Baum heißt vollständig, wenn jeder Knoten entweder zwei Söhne hat oder gar keine.
Bei einem solchen Baum wächst die Höhe des Baumes, also der längste Weg beim Suchen,
logarithmisch mit der Anzahl der Objekte.
Die Komplexität des Suchens wächst logarithmisch mit der Anzahl der Elemente.
Gegenteil: Auch die lineare, geordnete Liste ist ein Suchbaum, wenn auch ein vollständig
entarteter. Die Komplexität des Suchens in einem derart degenerierten Baum wächst linear
mit der Anzahl der Schlüssel.
Zu den gleichen Schlüssel kann man unterschiedliche binäre Suchbäume konstruieren.
Holger Vogelsang
Informatik 2 - Datenstrukturen
140
Bäume
Balancierter Baum (AVL) – Idee

Beispiel, in dem der Baumaufbau von der Reihenfolge des Einfügens der Schlüssel abhängt.
45
68
41
34
68
42
39
64
51
51
45
64
42
41
39
34
Holger Vogelsang
Informatik 2 - Datenstrukturen
141
Bäume
Balancierter Baum (AVL) – Idee






Ein Baum kann also zur Liste degenerieren.
Problem: In der Praxis sind Suchbäume praktisch nie vollständig ausgeglichen, denn das
würde einen sehr großen Aufwand bedeuten.
Lösung: Näherungsweises Ausgleichen eines Baums (Begriff: ausgeglichener Baum).
Die erste Klasse von ausgeglichenen Bäumen waren die AVL-Bäume (nach den Erfindern G.
M. Adelson-Velski/E. M. Landis).
Ziel: Je zwei Teilbäume an einem Knoten dürfen sich in der Höhe um nicht mehr als 1
unterscheiden. Dieses gilt für alle Teilbäume an allen Knoten.
Ein Baum ist genau dann ausgeglichen, wenn dieses Ziel erreicht ist.
Holger Vogelsang
Informatik 2 - Datenstrukturen
142
Bäume
Balancierter Baum (AVL) – Ausgleichen



Nach jeder Einfüge- oder Löschoperation muss überprüft werden, ob ein Ausgleichen des
Baums erforderlich ist
Das Ausgleichen erfolgt durch „Rotation“ der Knoten.
Beispiel: Linksrotation um die Knoten B/D.
B
D
Rotationspunkt
B
D
e
a
c

e
a
c
Eine Linksrotation reduziert die Höhe des rechten Teilbaums um 1 und erhöht die Höhe des
linken um 1.
Holger Vogelsang
Informatik 2 - Datenstrukturen
143
Bäume
Balancierter Baum (AVL) – Ausgleichen durch Doppelrotation


Um die Höhe eines inneren Baums zu verändern, muss eine Doppelrotation LR oder RL
angewendet werden.
Beispiel: Doppelrotation
B
D
F
a
D
g
c
Holger Vogelsang
F
B
a
c
e
g
e
Informatik 2 - Datenstrukturen
144
Bäume
Balancierter Baum (AVL) – Algorithmus zum Ausgleichen



Gegeben sei ein Baum mit einer Wurzel W sowie deren linken Teilbaum L und rechten
Teilbaum R.
Der neue Knoten soll in L eingefügt werden (wie bisher auch).

Höhe(L) = Höhe(R): Nach dem Einfügen unterscheiden sich die Höhen um 1 ->
Ausgeglichenheit nicht verletzt.

Höhe(L) < Höhe(R): Die Höhen werden gleich. Die Ausgeglichenheit ist nicht verletzt.

Höhe(L) > Höhe(R): Die Ausgeglichenheit wird zerstört. Der Baum muss restrukturiert
werden.
Lösung: Jeder Knoten enthält zusätzlich Balance-Informationen.

Höhe(L) = Höhe(R): balance = 0

Höhe(L) < Höhe(R): balance = 1;

Höhe(L) > Höhe(R): balance = -1;
Holger Vogelsang
Informatik 2 - Datenstrukturen
145
Bäume
Balancierter Baum (AVL) – Beispiel

Aufwandsabschätzung
Operation
Holger Vogelsang
Aufwand
Einfügen
O(ln N)
Löschen
O(ln N)
Indexzugriff
O(N)
Suche
O(ln N)
Informatik 2 - Datenstrukturen
146
Bäume
Balancierter Baum (Top-Down 2-3-4) – Idee



AVL-Bäume sind kompliziert auszugleichen.
Normale binäre Bäume können im schlimmsten Fall zu einer linearen Liste entarten.
Idee: Bäume können an einem Knoten mehr als einen Schlüssel haben.

2-Knoten (1 Schlüssel)
3-Knoten (2 Schlüssel)
n
<n

n0
>n
< n0
4-Knoten (3 Schlüssel)
n0
< n0
Holger Vogelsang
> n0
< n1
n1
n1
> n0
< n1
> n1
n2
> n1
< n2
> n2
Informatik 2 - Datenstrukturen
147
Bäume
Balancierter Baum (Top-Down 2-3-4) – Suchoperation anhand eines Beispiels

Suche nach dem Schlüssel 15:

Enthält der aktuelle Knoten den gesuchten Schlüssel: fertig.

Wähle das Intervall, in dem der Schlüssel liegen müsste und folge der Kante zum
nächsten Knoten.
15
7
Holger Vogelsang
8
10
20
13
14
15
22
24
Informatik 2 - Datenstrukturen
148
Bäume
Balancierter Baum (Top-Down 2-3-4) – Einfügeoperation (naiv)
Grundidee (ineffizient)




Suche nach dem Blatt-Knoten, in dem der Schlüssel liegen müsste.
Der Knoten ist ein 2-Knoten: Schlüssel einfügen, es entsteht ein 3-Knoten.
Der Knoten ist ein 3-Knoten: Schlüssel einfügen, es entsteht ein 4-Knoten.
Der Knoten ist ein 4-Knoten:

Möglichkeit 1: Den Schlüssel als neues Blatt an den 4-Knoten anhängen  Problem: Wie
soll ausbalanciert werden?

Möglichkeit 2: Durchführen der folgenden Schritte:
1. Den mittleren Schlüssel des 4-Knotens entnehmen.
2. Den 4-Knoten in zwei 2-Knoten aufspalten.
3. Den neuen Schlüssel in einen der 2-Knoten einfügen.
4. Den mittleren Knoten des ehemaligen 4-Knotens in den Vaterknoten einfügen.
5. Wenn der Vater vorher ein 4-Knoten war  auch aufspalten.
6. Im schlimmsten Fall bis zur Wurzel aufspalten.
Holger Vogelsang
Informatik 2 - Datenstrukturen
149
Bäume
Balancierter Baum (Top-Down 2-3-4) – Einfügeoperation (effizient)
Grundidee (effizient)


Suche nach dem Knoten/Blatt, in dem der Schlüssel liegen müsste.
Teile jeden auf dem Pfad liegenden 4-Knoten in 2 2-Knoten auf (der 2. Durchlauf entfällt
dadurch).
Holger Vogelsang
Informatik 2 - Datenstrukturen
150
Bäume
Balancierter Baum (Top-Down 2-3-4) – Einfügeoperation (effizient)




Die Wurzel des Baums wird grundsätzlich in 3 2-Knoten aufgeteilt, wenn sie ein 4-Knoten
war.
Da die 4-Knoten auf dem Weg von der Wurzel zu den Blättern gespalten werden, spricht man
von einem Top-Down-Baum.
Der Baum wächst immer in Richtung Wurzel, daher ist er stets ausbalanciert. Alle Äste
wachsen gleichmäßig.
In der Praxis sind relativ wenige Aufspaltungen eines 4-Knotens erforderlich.
Holger Vogelsang
Informatik 2 - Datenstrukturen
151
Bäume
Balancierter Baum (Top-Down 2-3-4) – Einfügeoperation anhand eines Beispiels

Einfügen des Schlüssels 16:
7
8
10
20
13
14
15
22
24
Ausgangssituation
10
7
8
13
14
20
15
16
22
24
Einfügen von 16
(vor Teilen der Wurzel)
Holger Vogelsang
Informatik 2 - Datenstrukturen
152
Bäume
Balancierter Baum (Top-Down 2-3-4) – Einfügeoperation anhand eines Beispiels
14
10
7
8
13
20
15
16
22
24
Einfügen von 16
(nach Teilen der Wurzel)
Holger Vogelsang
Informatik 2 - Datenstrukturen
153
Bäume
Balancierter Baum (Top-Down 2-3-4) – Aufwandsabschätzung
Der Baum ist immer balanciert.
Operation
Holger Vogelsang
Aufwand
Einfügen, sortierte Reihenfolge
O(ln N)
Einfügen, zufällige Reihenfolge
O(ln N), im Mittel θ(ln N)
Löschen
O(ln N)
Indexzugriff
O(N)
Suche
O(ln N)
Informatik 2 - Datenstrukturen
154
Bäume
Balancierter Baum (Red-Black) – Idee



Die Implementierung des Einfügens in einen 2-3-4-Baum ist leicht ineffizient, da in jedem
Schritt geprüft werden muss, ob eine Aufspaltung notwendig ist.
Neue Idee

3-Knoten und 4-Knoten werden als spezielle kleine binäre Bäume dargestellt, die durch
„rote“ Verbindungen verkettet sind. Die „schwarzen“ Verkettungen halten den
kompletten Baum selbst zusammen.

Ein zusätzliches Bit im Knoten zeigt an, ob er über eine rote oder eine schwarze
Verbindung mit seinen Vater verkettet ist.
Ein Rot-Schwarz-Baum kann als eine spezielle Implementierung des 2-3-4-Baums gesehen
werden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
155
Bäume
Balancierter Baum (Red-Black) – Idee

Umwandlung eines 4-Knotens in einen kleinen Binärbaum:

Umwandlung eines 3-Knotens in einen kleinen Binärbaum:
Holger Vogelsang
Informatik 2 - Datenstrukturen
156
Bäume
Balancierter Baum (Red-Black) – Idee


Bedingungen für die Farben:

Jeder Knoten im Baum ist entweder rot oder schwarz eingefärbt.

Die Wurzel des Baums ist immer schwarz.

Alle Blätter sind schwarz.

Ist ein Vaterknoten rot, so sind beide Nachfolger schwarz.

Jeder Pfad von einem beliebigen Knoten zu seinen Blättern enthält die gleiche Anzahl
schwarzer Knoten.
Konsequenz: Die Pfadlängen von der Wurzel zu den Blättern kann sich maximal um den
Faktor 2 unterscheiden. Warum?

Im kürzesten Pfad sind alle Knoten schwarz.

Im längsten Pfad wechseln sich rote und schwarze Knoten ab.
Holger Vogelsang
Informatik 2 - Datenstrukturen
157
Bäume
Balancierter Baum (Red-Black) – Aufwandsabschätzung
Der Red-Black-Tree soll hier nicht näher betrachtet werden.
Der Baum ist immer balanciert.
Operation
Holger Vogelsang
Aufwand
Einfügen, sortierte Reihenfolge
O(ln N)
Einfügen, zufällige Reihenfolge
O(ln N), im Mittel θ(ln N)
Löschen
O(ln N)
Indexzugriff
O(N)
Suche
O(ln N)
Informatik 2 - Datenstrukturen
158
Bäume
Implementierungen in Java

Baum-Klassen
Holger Vogelsang
Informatik 2 - Datenstrukturen
159
Bäume
Implementierungen in Java

Baumimplementierungen in Java (Rot-Schwarz-Baum):

TreeMap<K,V>:
- Ablage von Schlüssel-/Wertepaaren
- Schlüsselduplikate sind nicht erlaubt.
- Mehrere Iterierungsmöglichkeiten (wie bei HashMap):
• public Set<Map.Entry<K,V>> entrySet() liefert die Menge aller
Schlüssel/Werte-Paare, über die iteriert werden kann
• public Set<K> keySet() liefert die Menge aller Schlüssel, über die iteriert
werden kann
• public Collection<V> values() ermittelt alle Werte, über die iteriert
werden kann

TreeSet<E>:
- Ablage von Schlüsseln
- Schlüsselduplikate sind nicht erlaubt.
- Iteratorzugriff mit der Methode public Iterator<E> iterator()
Holger Vogelsang
Informatik 2 - Datenstrukturen
160
Bäume
Die Klasse TreeMap: Beispiel Wörterzählen
public class TreeMapWordCountStreamTest {
// Wörter einlesen. Trennzeichen sind Leerzeichen,
// Tabulatoren und Zeilenumbrüche usw.
public static TreeMap<String,Integer> getWords(String text) {
}
// TreeMap zum sammeln aller Wörter als Schlüssel
// sowie deren Anzahl als Wert.
TreeMap<String, Integer> words = new TreeMap<>();
// Der StringTokenizer zerlegt einen String in einzelne Tokens (Wörter).
// Die Worttrennzeichen sind die einzelnen Zeichen im 2. Parameter.
StringTokenizer tokenizer = new StringTokenizer(text, " \t\r\n.,;-");
while (tokenizer.hasMoreTokens()) {
String input = tokenizer.nextToken();
Integer count = words.get(input);
words.put(input, count == null ? 1 : count.intValue() + 1);
}
return words;
Holger Vogelsang
Informatik 2 - Datenstrukturen
161
Bäume
Die Klasse TreeMap: Beispiel Wörterzählen
public static void main(String[] args) {
String text = "C++ ist meine absolute Lieblingssprache und"
+ " ich freue mich auf die Klausur. Eigentlich"
+ " ist Java meine Lieblingssprache.";
}
}
TreeMap<String, Integer> words = getWords(text);
words.entrySet().stream().forEach(System.out::println);
Holger Vogelsang
Informatik 2 - Datenstrukturen
162
Bäume
Die Klasse TreeMap: Beispiel Wörterzählen

Eingabe
C++ ist meine absolute Lieblingssprache und
ich freue mich auf die Klausur. Eigentlich
ist Java meine Lieblingssprache.

Alphabetische Ausgabe der Wörter
C++=1
Eigentlich=1
Java=1
Klausur=1
Lieblingssprache=2
absolute=1
auf=1
die=1
freue=1
ich=1
ist=2
meine=2
mich=1
und=1
Holger Vogelsang
Informatik 2 - Datenstrukturen
163
Bäume
Die Klasse TreeMap: Beispiel Wörterzählen über Streams

Er geht auch kürzer über Streams:
Pattern pattern = Pattern.compile("[ \t\r\n.,;-]+");
pattern.splitAsStream(text) // erzeugt Stream von Wörtern
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
// Map: Key=Wort, Wert=Häufigkeit
.entrySet() // Menge aller Schlüssel-/Werte-Paare
.stream() // erzeugt Stream der Schlüssel-/Werte-Paare
.sorted(Comparator.comparing(e -> e.getKey()))
// sortiert Daten im Stream anhand der Wörter
.forEach(System.out::println);




Benötigt mehr als die doppelte Zeit!
Problem: pattern.splitAsStream (also das Zerlegen des Textes in Wörter) ist sehr
langsam  spezielle Eigenimplementierung könnte stark beschleunigen
Die Stream-Variante selbst ist ungefähr genauso schnell wie die manuelle Verwendung des
Baums.
Prinzipiell ist hier aber die Baum-Variante sinnvoller.
Holger Vogelsang
Informatik 2 - Datenstrukturen
164
Bäume
Balancierter Baum (B) – Idee eines Mehrwegbaums




B-Bäume sind eine Verallgemeinerung balancierter 2-3-4-Bäume.
B-Bäume sind Mehrwegbäume.
Die Ordnung des Baums ist o, o >= 2.

Jeder Knoten enthält maximal 2 * o Schlüssel.

Jeder Knoten hat minimal o Schlüssel.  Speicherausnutzung beträgt min. 50%
(Ausnahme: Wurzel, die zu weniger als 50% gefüllt sein darf).

Die Schlüssel innerhalb eines Knotens sind aufsteigend sortiert.

Wenn m die Anzahl der Schlüssel in einem Knoten ist, so hat der Knoten genau m + 1
Nachfolger, wenn er kein Blatt ist.

Die Schlüssel des linken Teilbaums sind kleiner als der Schlüssel der Wurzel dieses
Teilbaums.

Die Schlüssel des rechten Teilbaums sind größer als der Schlüssel der Wurzel dieses
Teilbaums.

Alle Blattseiten liegen auf einer Ebene.
Optional: Neben einem Schlüssel können auch Werte abgelegt sein.
Holger Vogelsang
Informatik 2 - Datenstrukturen
165
Bäume
Balancierter Baum (B) – Idee eines Mehrwegbaums

Ein B-Baum der Ordnung 2:
25
10 20
2

5
7
8
13 14 15 18
22 24
26 27 28
32 35 38
41 42 45 46
Aufbau eines Knotens ohne Werte (pi = Verweis auf Nachfolger, ki = Schlüssel):
p0

30 40
k1
p1
k2
…
km-1 pm-1 km
pm
Aufbau eines Knotens mit Werten (pi = Verweis auf Nachfolger, ki = Schlüssel, vi ist Datenwert
von Schlüssel ki):
p0
Holger Vogelsang
k1
v1
p1
k2
v2
…
km-1 vm-1 pm-1 km
vm
Informatik 2 - Datenstrukturen
pm
166
Bäume
Balancierter Baum (B) – Einsatzgebiet

B-Bäume werden häufig zur Verwaltung von Daten auf externen Massenspeichern
eingesetzt:

Der Baum enthält Schlüssel und Indizes für die eigentlichen Nutzdaten.

Die Nutzdaten liegen sequentiell in einer eigenständigen Datei vor.

Vorteile dieser Organisation:
- Wenn auf die Daten nicht sequentiell zugegriffen werden muss, muss nur der Baum
abgesucht werden. Dieser enthält dann die Position der Nutzdaten in der zweiten
Datei.
- Zum Einfügen und Löschen muss die Reihenfolge der Nutzdaten nicht verändert
werden.
- Es sind nur sehr wenige Zugriffe notwendig  Zugriffe auf den Massenspeicher sind
sehr langsam.
- Nur der Wurzelknoten des Baums wird im Speicher gehalten.

Ein ähnlicher Aufbau wird für Datenbanken gewählt.
Holger Vogelsang
Informatik 2 - Datenstrukturen
167
Bäume
Balancierter Baum (B) – Aufbau bei externem Massenspeicher

Zusammenhang zwischen Index (Baum) und Nutzdatendatei:
…
Satz 2
10
Satz 7
Satz 27
2
5
7
8
Satz 1
13 14 15 18
…
Satz 42
usw.
Satz 3
…

Anmerkungen:

Im Beispiel: Schlüssel = Position in der Datei

In der Realität: zusätzlich Nutzdaten mit der Position als Wert
Holger Vogelsang
Informatik 2 - Datenstrukturen
168
Bäume
Balancierter Baum (B) – Suchoperation

Funktionsweise der Suchoperation:
1. Startknoten: Wurzel des Baums
2. Suche mittels Binärsuche nach dem Schlüssel.
3. Ist der Schlüssel vorhanden, so ist die Suche beendet.
4. Ermittlung des Nachfolgeknotens: Auswahl des Verweises zwischen den zwei Werten,
zwischen denen der Suchschlüssel liegen muss.
5. Wenn ein Nachfolgeknoten existiert (Knoten ist kein Blatt), dann lade den Knoten vom
Massenspeicher  weiter an Punkt 2.
6. Wenn kein Nachfolgeknoten existiert, so ist der Schlüssel nicht im Baum vorhanden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
169
Bäume
Balancierter Baum (B) – Suchoperation am Beispiel

Suche nach dem Schlüssel 35:
25
10 20
2
5
Holger Vogelsang
7
8
13 14 15 18
30 40
22 24
26 27 28
Informatik 2 - Datenstrukturen
32 35 38
41 42 45 46
170
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel



Es soll ein Baum durch das Einfügen der folgenden Schlüssel entstehen: 20, 40, 10, 30, 15, 35,
7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45, 25.
Der Baum hat die Ordnung 2.
Einfügen:

Analog zum Top-Down 2-3-4 Baum

Das mittlere Element wird nach dem (gedachten) Einfügen ermittelt.
20
20 40
Eingefügt: 20
Eingefügt: 40
10 20 40
Eingefügt: 10
20
10 15
Eingefügt: 30
20
30 40
10 15
Eingefügt: 15
Holger Vogelsang
10 20 30 40
30 35 40
Eingefügt: 35
Informatik 2 - Datenstrukturen
171
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel
20
20
7
10 15
30 35 40
7
10 15
Eingefügt: 26
Eingefügt: 7
20
7
10 15 18
20 30
26 30 35 40
Eingefügt: 18
Holger Vogelsang
26 30 35 40
7
10 15 18
22 26
35 40
Eingefügt: 22
Informatik 2 - Datenstrukturen
172
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel
10 20 30
5
7
15 18
22 26
35 40
Eingefügt: 5
10 20 30
5
7
15 18
22 26
35 40 42
Eingefügt: 42
Holger Vogelsang
Informatik 2 - Datenstrukturen
173
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel
10 20 30
5
7
13 15 18
22 26
35 40 42
Eingefügt: 13
10 20 30
5
7
13 15 18
22 26
35 40 42 46
Eingefügt: 46
Holger Vogelsang
Informatik 2 - Datenstrukturen
174
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel
10 20 30
5
13 15 18
7
22 26 27
35 40 42 46
Eingefügt: 27
10 20 30
5
7
8
13 15 18
22 26 27
35 40 42 46
Eingefügt: 8
Holger Vogelsang
Informatik 2 - Datenstrukturen
175
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel
10 20 30 40
5
7
8
13 15 18
22 26 27
32 35
42 46
32 35 38
42 46
Eingefügt: 32
10 20 30 40
5
7
8
13 15 18
22 26 27
Eingefügt: 38
Holger Vogelsang
Informatik 2 - Datenstrukturen
176
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel
10 20 30 40
5
7
8
13 15 18
22 24 26 27
32 35 38
42 46
32 35 38
42 45 46
Eingefügt: 24
10 20 30 40
5
7
8
13 15 18
22 24 26 27
Eingefügt: 45
Holger Vogelsang
Informatik 2 - Datenstrukturen
177
Bäume
Balancierter Baum (B) – Einfügeoperation am Beispiel
25
10 20
5
7
8
13 15 18
30 40
22 24
26 27
32 35 38
42 45 46
Eingefügt: 25
Holger Vogelsang
Informatik 2 - Datenstrukturen
178
Bäume
Balancierter Baum (B) – Löschoperation

Löschoperation in einem B-Baum:
1. Unterscheidung zweier Fälle:
- Das zu löschende Objekt liegt in einem Blatt. Es wird gelöscht  Schritt 2.
- Das zu löschende Objekt liegt nicht in einem Blatt: Aus dessen linkem Teilbaum
wird der Knoten mit dem größten Schlüssel geholt (oder aus dem rechten Teilbaum
der mit dem kleinsten). Dieses ersetzt das zu löschende Objekt  Schritt 2.
2. Ausgleichen: Durch das Löschen der Schlüssel kann ein Unterlauf auftreten (der Knoten
ist nicht mehr zu min. 50% gefüllt). Es werden eine benachbarte Seite geladen und die
Schlüssel auf beide Seiten gleichmäßig verteilt.
3. Tritt dabei ein Unterlauf auf, werden beide Seiten zusammengelegt und der mittlere
Schlüssel des Vaterknoten in die gemeinsame Seite eingefügt.
4. Jetzt kann im Vaterknoten ein Unterlauf auftreten  Schritt 2.
Holger Vogelsang
Informatik 2 - Datenstrukturen
179
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel


Es sollen aus dem Baum die folgenden Schlüssel entfernt werden: 25, 45, 24, 38, 32, 8, 27,
46, 13, 42, 5, 22, 18, 26, 7, 35, 15.
Der Baum hat die Ordnung 2.
25
10 20
5
7
8
Holger Vogelsang
13 15 18
30 40
22 24
26 27
Informatik 2 - Datenstrukturen
32 35 38
42 45 46
180
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel
24
10 18
5
7
8
13 15
30 40
20 22
26 27
32 35 38
42 45 46
Gelöscht: 25
24
10 18
5
7
8
13 15
30 40
20 22
26 27
32 35 38
42 46
Gelöscht: 45
Holger Vogelsang
Informatik 2 - Datenstrukturen
181
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel
10 22 30 40
5
7
8
13 15 18 20
26 27
32 35 38
42 46
32 35
42 46
Gelöscht: 24
10 22 30 40
5
7
8
13 15 18 20
26 27
Gelöscht: 38
Holger Vogelsang
Informatik 2 - Datenstrukturen
182
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel
10 22 30
5
7
8
13 15 18 20
26 27
35 40 42 46
Gelöscht: 32
10 22 30
5
7
13 15 18 20
26 27
35 40 42 46
Gelöscht: 8
Holger Vogelsang
Informatik 2 - Datenstrukturen
183
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel
10 22 35
5
7
13 15 18 20
26 30
40 42 46
Gelöscht: 27
10 22 35
5
7
13 15 18 20
26 30
40 42
Gelöscht: 46
Holger Vogelsang
Informatik 2 - Datenstrukturen
184
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel
10 22 35
5
15 18 20
7
26 30
40 42
Gelöscht: 13
10 22
5
7
15 18 20
26 30 35 40
Gelöscht: 42
Holger Vogelsang
Informatik 2 - Datenstrukturen
185
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel
15 22
7
10
18 20
26 30 35 40
Gelöscht: 5
15 26
7
10
18 20
30 35 40
Gelöscht: 22
Holger Vogelsang
Informatik 2 - Datenstrukturen
186
Bäume
Balancierter Baum (B) – Löschoperation am Beispiel
15
15 30
7
10
20 26
7
35 40
10
Gelöscht: 26
Gelöscht: 18
20
10 15
30 35 40
Gelöscht: 7
Holger Vogelsang
20 30 35 40
20
10 15
30 40
Gelöscht: 35
Informatik 2 - Datenstrukturen
10 20 30 40
Gelöscht: 15
187
Bäume
Balancierter Baum (B) – Sequentieller Datenzugriff


Inorder-Durchlauf aller Knoten:
Nachteil: Auf Knoten muss mehrfach zugegriffen werden (Laden vom Massenspeicher!).
Holger Vogelsang
Informatik 2 - Datenstrukturen
188
Bäume
Balancierter Baum (B) – Aufwandsabschätzungen




Bedeutung der Ordnung o des Baums:

Je größer o ist, desto flacher wird der Baum

Je kleiner o ist, desto geringer ist der Aufwand zum Suchen innerhalb des Knotens.
Seien

n = Anzahl Knoten im Baum mit n >= 2

o = Ordnung des Baums mit o >= 1

Dann gilt für die Höhe h des Baum: h <= log2*o ((n+1) / 2)
Damit gilt für die Suche eines Schlüssels

Ermittlung und Laden der Seiten entlang des Pfads: O(log2*o( (n+1)/2 ))

Suche innerhalb einer Seite mittels Binärsuche: O(ld( 2*o ))
Damit gilt für die komplette Suche: O(log2*o((n+1)/2 ))* O(ld( 2*o )), wobei der Aufwand für
das Laden einer Seite wegen der Plattenzugriffe deutlich höher als die Suche innerhalb der
Seite ist.
Holger Vogelsang
Informatik 2 - Datenstrukturen
189
Bäume
Balancierter Baum (B) – Aufwandsabschätzungen



Erste Idee: Ein Knoten soll möglichst viele Schlüssel enthalten.
Konsequenzen:

Beim rekursiven Abstieg zum Einfügen werden alle gefundenen Knoten im Speicher
gehalten, um die Anzahl der Plattenzugriffe klein zu halten. Dadurch wächst bei sehr
vielen Knoten der Speicherbedarf an.

Werden die Knoten nicht mehr im Speicher gehalten, so wächst die Zeit für die
Plattenzugriffe (Verdopplung).
Bessere Idee: Häufig wird die Knotengröße so gewählt, dass ein Knoten sehr gut
beispielsweise in einen Sektor auf dem externen Speicher passt und so effizient gelesen
werden kann.
Holger Vogelsang
Informatik 2 - Datenstrukturen
190
Bäume
Balancierter Baum (B+/B*)

Der B+-Baum (je nach Literatur auch B*-Baum) ist eine Abwandlung des B-Baums mit Daten:

Innere Knoten enthalten nur Schlüssel (so genannte Separatorschlüssel) und Nachfolger
als Paare (ki, pi):
p0

p1
k2
…
km-1 pm-1 km
pm
- p0 verweist auf einen Knoten mit Schlüsseln kleiner oder gleich k1.
- pi (1 ≤ i < m) verweist auf einen Knoten mit Schlüsseln größer als ki und kleiner oder
gleich ki+1.
- pm verweist auf einen Knoten mit Schlüsseln größer als km.
Die Werte befinden sich zusammen mit den Schlüsseln nur in den Blättern als Paare (ki,
vi):
p

k1
k0
v0
k1
v1
…
km-1 vm-1 km
vm
n
Die Daten werden in der Sortierreihenfolge der Schlüssel abgelegt.
Alle Blätter werden zu einer doppelt verketteten Liste verbunden (Verweis p, n oben)
sehr schnelles sequentielles Durchlaufen aller Daten.
Holger Vogelsang
Informatik 2 - Datenstrukturen
191
Bäume
Balancierter Baum (B+/B*)
Jeder innere Knoten hat min. k und max. 2k Einträge (=Ordnung beim B-Baum).

Jeder Blattknoten hat min. k* und max. 2k* Einträge (außer Wurzel).
Wozu?

In den inneren Knoten ist „mehr Platz“ für Schlüssel und Verweise auf Nachfolger 
Baumhöhe sinkt bei identischer Knotengröße.

Einfaches Sequentielles Durchlaufen der Datenelemente.

B* ist die wichtigste Variante des B-Baums.


Holger Vogelsang
Informatik 2 - Datenstrukturen
192
Bäume
Balancierter Baum (B+/B*) – Einfügeoperation

Einfügen:

Ähnlich wie beim B-Baum.
1. Suche den Schlüssel des neuen Datensatzes im Baum  führt immer zu einem
Blatt, da Daten nur in Blättern gespeichert werden.
2. Füge den neuen Datensatz im Blatt ein.
3. Falls der Knoten überläuft, wird er gespalten („in der Mitte“).
4. Beim Spalten wird ein „mittlerer“ Schlüssel (Separatorschlüssel) erzeugt und in den
Vaterknoten eingefügt.
5. Der Separatorschlüssel kann im Blatt vorkommen, muss es aber nicht.
6. Beim Überlauf des Vaterknotens: weiter in Schritt 3
Holger Vogelsang
Informatik 2 - Datenstrukturen
193
Bäume
Balancierter Baum (B+/B*) – Einfügeoperation am Beispiel


Beispiel: k = 4, k* = 2
Startsituation (Knoten bestehen aus Schlüssel und zugehörigem Wert):
10

Vogelsang
15
Pape
20
Gmeiner
25
Nestler
25
Nestler
Schrittweises Einfügen von [30, „Hoffmann“]:
10
Vogelsang
15
Pape
20
Gmeiner
30
Hoffmann
Überlauf
Aufspalten des Knotens und Erzeugung eines Separatorschlüssels (23) in einem neuen
gemeinsamen Vaterknoten.
Neue Knoten als Liste verketten.
23
10
Vogelsang
15
Pape
20
Gmeiner
25
Nestler
30
Hoffmann
Überlauf im Vaterknoten: Rekursiv zur Wurzel hin fortsetzen.
Holger Vogelsang
Informatik 2 - Datenstrukturen
194
Bäume
Balancierter Baum (B+/B*) – Löschoperation, sequentieller Datenzugriff
Löschen


Daten werden immer nur in den Blättern gelöscht.
Unterlauf ähnlich wie beim B-Baum  soll hier nicht näher betrachtet werden.
Sequentieller Datenzugriff


Verzeigerung in den Blattseiten folgen
Vorteil: Auf jeden Blattknoten muss nur einmal zugegriffen werden.
Die Nutzdaten befinden sich nur in den Blättern!
Holger Vogelsang
Informatik 2 - Datenstrukturen
195
Bäume
Vergleich von B- und B+/B*-Baum

B-Baum:
Index
(mit Schlüsseln
und Werten)

B+/B*-Baum:
Index
(mit Separatorschlüsseln)
Schlüssel mit Werten
Holger Vogelsang
Informatik 2 - Datenstrukturen
196
Bäume
Tries (digitale Bäume)



Trie (gesprochen „try“): Baum zur Speicherung von Zeichenketten eines Dokumentes, um
später leicht das Vorhandensein der Texte im Dokument feststellen zu können (retrieval).
Aufbau:

Knoten ist ein Array mit der Größe = Kardinalität des Alphabetes.

Jeder Eintrag enthält einen Verweis auf einen anderen Knoten.

Die Buchstaben werden nicht um Baum gespeichert (Index ergibt Buchstaben).
Aufbau: Alphabet mit 26 Zeichen als Großbuchstaben.
A
A
B
A
C
B
Holger Vogelsang
D
C
…
D
B
X
…
C
Y
X
D
Z
Y
…
X
Y
Z
A
B
C
D
…
X
Y
Z
Z
Informatik 2 - Datenstrukturen
197
Bäume
Tries (digitale Bäume)
Beispiel (unvollständig):
A
…
E
…
…
J
Benjamin

…
…
N
N
R
…
B
C
…
F
…
…
T
…
Bettina
Z
Y
…
…
Bennet
D
E
…
…
E
…
I
…
…
G
…
T
…
Brigitte
Britta
R
…
…
E
Fred
…
I
…
Frieda
Probleme:

Ungleichmäßige Verteilung der Daten (viele leere Verweise z.B. für Kombinationen wie
XX, XY, YY, YYYZ, …).

Entartung zur Liste möglich.
Holger Vogelsang
Informatik 2 - Datenstrukturen
198
Bäume
Binäre Tries


Ausweg aus dem Problem der ungleichmäßigen Auslastung:

Repräsentation der Zeichenketten als Binärfolge.

Knoten enthält nur noch Nachfolger für 0 und 1.
Weiterhin problematisch:

Entartung zu Listen.
Holger Vogelsang
Informatik 2 - Datenstrukturen
199
Bäume
Patricia Bäume




Ziel: Vermeidung des Entartens zu einer Liste.
Lösung: Practical Algorithm to Retrieve Information Coded in Alphanumeric (Patricia).
Idee:

Irrelevante Teile der Zeichenkette werden übersprungen.

Jeder Knoten enthält die Anzahl zu überspringender Zeichen (Trie) oder Bits (bin. Trie).
Beispiel (kompakte Darstellung eines Trie):
e
Oberkante
2
j
i
Objektiv
n
Objektmenge
Holger Vogelsang
4
m
2
t
Objektmethode
Informatik 2 - Datenstrukturen
200
Bäume
Patricia Bäume


Suchen:

Die im Knoten angegebenen Stellen überspringen.

Zum richtigen Nachfolger laufen.
Vorteil:

Kompakte Struktur, schnelleres Durchlaufen (besonders bei langen Wörtern).
Holger Vogelsang
Informatik 2 - Datenstrukturen
201
Graphen
Übersicht
Grafische
Oberflächen
Übersicht
Datenstrukturen
ADTs
Typinfo.,
I/O
Annotationen
JavaScript
Prinzipien
Holger Vogelsang
Layouts
Ereignisse
Datenstrukturen
in Java
Laufzeittypinfo.
Einstieg
Grafikoperationen
Widgets
Elementare
Datenstrukturen
Iteratoren,
Streams
Grafikwidgets
Hashtabellen
Effekte,
Animationen
Bäume
Offene
Punkte
Graphen
Ein-,
Ausgabe
Objekte Vererbung
Module
DOMZugriffe
Informatik 2 - Datenstrukturen
202
Graphen
Motivation




Wozu dienen Graphen?
Welche Arten von Informationen können damit modelliert werden?
Wie können Graphen im Speicher abgebildet werden?
Einige wichtige Algorithmen für Graphen.
Holger Vogelsang
Informatik 2 - Datenstrukturen
203
Graphen
Idee

Viele Probleme lassen sich allgemein unter Verwendung von Objekten und Verbindungen
formulieren:

Darstellung eines Straßennetzes:
[https://maps.google.com/]
Holger Vogelsang
Informatik 2 - Datenstrukturen
204
Graphen
Idee

Schienennetzplan (KVV):
[http://www.kvv.de/fileadmin/user_upload/kvv/dokumente/netz/liniennetz/2013/L0SCHI_DEZ12_Betreiber.pdf]
Holger Vogelsang
Informatik 2 - Datenstrukturen
205
Graphen
Idee

Oder etwas berühmter (London Tube):
[http://www.tfl.gov.uk/assets/downloads/standard-tube-map.gif]
Holger Vogelsang
Informatik 2 - Datenstrukturen
206
Graphen
Idee

Beziehungen in einem sozialen Netzwerk:
[http://blog.iconsultants.eu/2012/10/was-ist-der-facebook-open-graph-und-warum-gibt-es-ihn/]



Netz aller Flugverbindungen
Elektronische Schaltungen aus Komponenten und Verbindungen
Scheduling: Welche Aufgaben hängen von anderen Aufgaben ab?
Holger Vogelsang
Informatik 2 - Datenstrukturen
207
Graphen
Begriffe
Begriffe



Ein Graph ist eine Menge von Knoten und Kanten.
Knoten sind einfache Objekte, die Namen und andere Eigenschaften haben können.
Kanten sind Verbindungen zwischen Knoten.
42
Kante
27
41
6
39
68
16
Knoten
Holger Vogelsang
51
Informatik 2 - Datenstrukturen
208
Graphen
Begriffe

Ein Graph ist unabhängig von seiner Darstellung:
42





27
41
6
39
68
42
16
51
27
6
41
39
68
16
51
Ein Pfad von einem Knoten x zu einem Knoten y ist eine Liste von aufeinander folgenden
Knoten, die durch Kanten verbunden sind.
Ein Graph ist zusammenhängend, wenn von jedem Knoten zu jedem anderen Knoten ein
Pfad existiert.
Ein Zyklus ist ein Pfad, in dem Anfangs- und Endknoten identisch sind.
Ein Baum ist ein Graph ohne Zyklen.
Ein Gruppe nicht zusammenhängender Bäume wird Wald genannt.
Holger Vogelsang
Informatik 2 - Datenstrukturen
209
Graphen
Begriffe

Ein Spannbaum ist ein Teilgraph, der alle Knoten enthält sowie die Kanten, die notwendig
sind, um einen Baum zu bilden.
42
27
41
6
39
42
68
16
51
27
41
6
39



16
51
Spannbaum
Graph

68
Graphen mit wenigen Kanten (E < V log V) werden licht genannt.
Graphen, in denen nur wenige Kanten fehlen, werden dicht genannt.
Gewichtete Graphen: Die Kanten haben Gewichte (Kosten, Entfernungen, ...).
Gerichtete Graphen: Die Kanten können nur in einer vorgegebenen Richtung durchlaufen
werden („Einbahnstraßen“).
Holger Vogelsang
Informatik 2 - Datenstrukturen
210
Graphen
Darstellung im Programm


Knoten werden auf ganze Zahlen (Indizes) abgebildet, um sehr effizient darauf zugreifen zu
können:

Knoten werden nummeriert.

Der Hashwert des Namens eines Knotens wird als Index verwendet  perfekte
Hashfunktion.
Einfachste Darstellung eines Graphen: Adjazenzmatrix („Nachbarschaftsmatrix“):

Annahme: Der Graph hat V Knoten.

Es wird ein Feld (zweidimensionales Array) graph der Größe V*V mit Boole‘schen
Werten gefüllt:
- Der Wert graph[ x ][ y ] = true, wenn eine Kante von Knoten x zu Knoten y
führt.
- Der Wert graph[ x ][ y ] = false, wenn es diese Kante nicht gibt.
- Die Matrix ist symmetrisch für ungerichtete Graphen:
graph[ x ][ y ] = graph[ y ][ x ]  Speicherplatzverschwendung, aber
einfachere Algorithmen
Holger Vogelsang
Informatik 2 - Datenstrukturen
211
Graphen
Darstellung im Programm

- In der Regel ist es praktisch zu definieren, dass ein Knoten immer zu sich selbst
führt:
graph[ x ][ x ] = true  abhängig vom Einsatz des Graphen
Beispiel:
1
6
0

2
4
5
7
3
0
1
2
3
4
5
6
7
0
t
f
f
f
f
t
t
f
1
f
t
t
f
f
f
t
f
2
f
t
t
f
t
f
f
f
3
f
f
f
t
t
t
f
t
4
f
f
t
t
t
t
t
f
5
t
f
f
t
t
t
f
f
6
t
t
f
f
t
f
t
f
7
f
f
f
t
f
f
f
t
Löschen eines Knotens x: An den Positionen [ 0...V-1 ][ x ] und
[ x ][ 0...V-1 ] muss false eingetragen werden (Löschen der Kanten).
Holger Vogelsang
Informatik 2 - Datenstrukturen
212
Graphen
Darstellung im Programm

Beispielgraph:
1
6
0
Holger Vogelsang
2
4
5
3
7
Informatik 2 - Datenstrukturen
213
Graphen
Darstellung im Programm




Aufwand Adjazenzmatrix: O(V2) Speicherplätze sowie O(V2) Schritte zur Initialisierung.
Nachteil der Adjazenzmatrix: Bei lichten Graphen ist die Speicherplatzverschwendung sehr
hoch.
Für lichte Graphen existiert daher eine Adjazenzliste („Nachbarschaftsliste“, auch
Adjazentstruktur):

Für jeden Knoten werden alle mit ihm verbundenen Knoten in einer Liste gehalten.

Die Listen liegen in einem eindimensionalen Array.
Beispiel:
1
6
0
Holger Vogelsang
2
4
5
7
0
1
2
3
4
5
6
7
5
2
1
4
2
0
0
3
6
6
4
5
3
3
1
7
5
4
4
3
6
Informatik 2 - Datenstrukturen
214
Graphen
Darstellung im Programm




Ein Kante, die Knoten x mit Knoten y verbindet, wird in der Liste von Knoten x und in der
Liste von Knoten y aufgeführt  effiziente Suche: Mit welchen Knoten ist Knoten x
verbunden?
Vorteil Speicherbedarf: O(V+E), Initialisierung: O(V)
Nachteile:

Einige Algorithmen sind aufwändiger und ineffizienter zu implementieren.

Das Löschen eines Knotens x ist aufwändig: In allen Listeneinträgen von x den Knoten x
löschen, dann alle Listeneinträge von x löschen.
Konsequenz: Keine „direkte“ Darstellung eines Graphen im Speicher, da der Aufwand für
Algorithmen sonst sehr hoch wird.
Holger Vogelsang
Informatik 2 - Datenstrukturen
215
Graphen
Darstellung gerichteter und gewichteter Graphen im Programm
Gerichtete Graphen


Jede Kante wird nur einmal dargestellt.
Darstellung einer Kante von Knoten x zu Knoten y:

Adjazenzmatrix: graph[ x ][ y ] = true.

Adjazenzliste/Adjazenzstruktur: y erscheint in der Liste von x.
Gewichtete Graphen

Die Darstellung erfolgt wie bei ungerichteten Graphen mit den folgenden Erweiterungen:

Adjazenzmatrix: Anstelle von true steht in graph[ x ][ y ] der numerische Wert
(die Gewichtung) der Kante. Anstelle von false wird eine nicht benutzte Gewichtung
eingetragen (z.B. -1).

Adjazenzliste/Adjazenzstruktur: Die Liste enthält für jeden Eintrag ein weiteres Feld mit
der Gewichtung.
Holger Vogelsang
Informatik 2 - Datenstrukturen
216
Graphen
Tiefensuche



Tiefensuche: systematisches Besuchen aller Knoten und Kanten im Graphen
Der Algorithmus ist Basis vieler anderer Lösungen im Zusammenhang mit Graphen.
Ablauf:

Ein Feld visitedNodes nimmt für alle Knoten den Index in der Besuchsreihenfolge auf.

Wurde der Knoten noch nicht besucht, so enthält es die Konstante UNSEEN (z.B. -1).

Solange es noch unbesuchte Knoten ki gibt, wird der nächste unbesuchte aus
visitedNodes genommen:
- In visitedNodes erhält der Knoten ki den nächsten Index.
- Es werden alle von ki aus erreichbaren Knoten besucht, die bisher noch nicht
besucht wurden.
Holger Vogelsang
Informatik 2 - Datenstrukturen
217
Graphen
Tiefensuche
V ist die Anzahl der Knoten
private int[] visitedNodes = new int[ V ];
private int visitId;
private static final int UNSEEN = -1;
public void visitNodes() {
visitId = 0;
// Reihenfolgearray löschen
for (int i = 0; i < V; ++i) {
visitedNodes[ i ] = UNSEEN;
}
}
// Knoten besuchen
for (int i = 0; i < V; ++i) {
if (visitedNodes[ i ] == UNSEEN) {
visitNode(i);
}
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
218
Graphen
Tiefensuche
Tiefensuche bei Darstellung mit Adjazenzmatrix


Der Graph liegt im zweidimensionalen Array graph.
Rekursives Besuchen aller Knoten, die mit dem übergebenen Knoten verbunden sind:
public void visitNode(int nodeIndex) {
visitedNodes[ nodeIndex ] = ++visitId;
}

// Alle Zellen der Zeile des Knotens absuchen
for (int i = 0; i < V; ++i) {
if (graph[ nodeIndex ][ i ] && (visitedNodes[ i ] == UNSEEN)) {
visitNode(i);
}
}
Zeitaufwand für die Tiefensuche: O(V2), da jedes Bit in der Matrix geprüft wird.
Holger Vogelsang
Informatik 2 - Datenstrukturen
219
Graphen
Tiefensuche

Beispiel (siehe Tafelanschrieb):
1
Holger Vogelsang
6
4
0
5
2
7
3
Informatik 2 - Datenstrukturen
220
Graphen
Tiefen- und Breitensuche
Hinweise



Die Rekursion kann genau wie bei Bäumen durch eine Iteration unter Verwendung einer
Stack-Klasse implementiert werden.
Wird der Stack durch eine Schlange (Queue) ersetzt, so ergibt sich automatisch Breitensuche.
Unterschiede zwischen Breiten- und Tiefensuche:

Tiefensuche: Es werden erst die Pfade zu den am weitesten entfernt liegenden Knoten
gesucht. Erst im Fall einer Sackgasse werden näher liegende Knoten besucht.

Breitensuche: Erst werden alle Knoten in der Nähe und danach immer weiter weg
liegende Knoten betrachtet.
Holger Vogelsang
Informatik 2 - Datenstrukturen
221
Graphen
Breitensuche iterativ

Der folgende Ausschnitt zeigt eine iterative Lösung zur Breitensuche im Graphen.
private int[] visitedNodes = new int[ V ];
private static final int UNSEEN = -1;
public void breadthFirstSearch(int startNode) {
int visitId = 0;
// Breitensuche mit Queue
// Tiefensuche mit Deque (als Stack)
Queue<Integer> queue = new LinkedList<>();
// Reihenfolgearray löschen
for (int i = 0; i < V; ++i) {
visitedNodes[ i ] = UNSEEN;
}
queue.offer(startNode);
while (!queue.isEmpty()) {
// Vordersten Knoten der Queue besuchen
int node = queue.poll(); // Tiefensuche: queue.pollLast();
Holger Vogelsang
Informatik 2 - Datenstrukturen
222
Graphen
Breitensuche iterativ
if (visitedNodes[ node ] == UNSEEN) {
visitedNodes[ node ] = ++visitId;
}
}
}
// Alle Zellen der Zeile des Knotens absuchen
for (int i = V - 1; i >= 0; --i) {
if (graph[ node ][ i ] && (visitedNodes[ i ] == UNSEEN)) {
queue.offer(i);
}
}
Holger Vogelsang
Informatik 2 - Datenstrukturen
223
Graphen
Suche des kürzesten Pfades iterativ


Der Breitensuchalgorithmus kann sehr leicht zur Suche des kürzesten Pfades zwischen zwei
Knoten verwendet werden:

Statt der Reihenfolge der Besuche wird in predNodes der Vorgänger jedes Knotens
abgelegt.

Der Algorithmus bricht ab, wenn das Ziel erreicht ist.
Algorithmus:
private boolean[] visitedNodes = new boolean[ V ];
private int[] predNodes = new int[ V ];
private final static int UNSEEN = -1;
public boolean pathfinder(int startNode, int endNode) {
LinkedList<Integer> queue = new LinkedList<>();
// Reihenfolgearray löschen
for (int i = 0; i < V; i++) {
visitedNodes[ i ] = false;
predNodes[ i ] = UNSEEN;
}
queue.offer(startNode);
Holger Vogelsang
Informatik 2 - Datenstrukturen
224
Graphen
Suche des kürzesten Pfades iterativ
while (!queue.isEmpty()) {
// Vordersten Knoten der Queue besuchen
int node = queue.pollFirst();
if (!visitedNodes[ node ]) {
visitedNodes[ node ] = true;
}
}
// Fertig ?
if (node == endNode) {
return true;
}
// Alle Zellen der Zeile des Knotens absuchen
for (int i = V - 1; i >= 0; i--) {
if (graph[ node ][ i ] && !visitedNodes[ i ]
&& predNodes[ i ] == UNSEEN) {
predNodes[ i ] = node; // jetzigen als Vorgänger eintragen
queue.offer(i);
}
}
}
return false;
Holger Vogelsang
Informatik 2 - Datenstrukturen
225
Graphen
Suche des kürzesten Pfades in einem gewichteten Graphen

Suche des kürzesten Pfades in einem gerichteten (nicht so wichtig) und gewichteten Graphen
mit nicht-negativen Gewichten mittels Dijkstra-Algorithmus (gehört zu den GreedyAlghorithmen  wählen schrittweise in jedem Zustand den aussichtsreichsten Folgezustand
aus)
1
1
1
1
1
6
3
0

2
1
1
4
3
10
2
8
5
1
7
2
1
3
2
2
3
Zeitaufwand (abhängig von der Darstellung des Graphen im Speicher): min. O(V2 + E)
Holger Vogelsang
Informatik 2 - Datenstrukturen
226
Graphen
Suche des kürzesten Pfades in einem gewichteten Graphen


Ablauf:
1. Alle Knoten erhalten die Attribute „aktuelle Distanz zum Startknoten“ (Distanz) und
„Vorgängerknoten im Pfad“ (Vorgänger).
2. Die Distanzen werden mit ∞ (unendlich) initialisiert. Nur der Startknoten erhält zu sich
selbst die Distanz 0.
3. Solange es noch unbesuchte Knoten ki gibt, wird derjenige mit dem geringsten Abstand
zum Startknoten gewählt (verspricht am ehesten Erfolg):
1. Markiere diesen Knoten ki als schon besucht
2. Berechne für alle Knoten kx, die von ki aus erreichbar sind, die Abstände zum
Startknoten als: Abstand kx = Abstand ki + Gewichtung von ki zu kx
3. Ist der berechnete Abstand kx kleiner als der bisherige Abstand zu kx, dann wurde
ein kürzerer Pfad zu kx gefunden:
a. Trage den neuen Abstand kx am Knoten kx ein und merke als Vorgänger von kx
den Knoten ki.
b. Trage ki als Vorgänger von kx ein.
Java-Code: siehe Eclipse-Projekt „GraphAlgos“.
Holger Vogelsang
Informatik 2 - Datenstrukturen
227
Graphen
Weitere Algorithmen


Es existieren viele weitere Algorithmen für Graphen, die in der Praxis sehr wichtig sind:

Kürzeste Pfade zwischen allen Knoten, kürzester Pfad von einem Knoten zu allen
anderen, …

Kürzeste Menge der Linien, die alle Punkte innerhalb einer Ebene verbinden.

Topologisches Sortieren eines azyklischen, gerichteten Graphen: Erst werden die Knoten
ermittelt, auf die kein anderer Knoten verweist. Dann die Knoten, auf die nur bereits im
ersten Schritt ermittelte Knoten verweisen...  Abhängigkeitsgraph.
Diese Algorithmen sollen nicht mehr Bestandteil der Vorlesung sein.
Holger Vogelsang
Informatik 2 - Datenstrukturen
228
Graphen
Bibliotheken


Für die Verwendung von Graphen in Java existieren eine ganze Anzahl unterschiedlicher
Bibliotheken. Interessant sind:

JGraphT (http://www.jgrapht.org/): Frei verfügbare Bibliothek mit Graphenalgorithmen

JGraph (http://www.jgraph.com/jgraph.html): Frei verfügbare Bibliothek zur Darstellung
von Graphen (z.B. denen von JGraphT)
Beispiel zur Suche des kürzesten Pfades in einem Graphen (Projekt GraphDemo):
// Gerichteter Graph
ListenableGraph g = new ListenableDirectedGraph(DefaultEdge.class);
// Knoten ergänzen
g.addVertex("v1");
g.addVertex("v2");
g.addVertex("v3");
g.addVertex("v4");
// Knotenverbinden
g.addEdge("v1", "v2");
g.addEdge("v2", "v3");
g.addEdge("v3", "v1");
g.addEdge("v4", "v3");
List<DefaultEdge> result = DijkstraShortestPath.findPathBetween(g, "v1", "v4");
Holger Vogelsang
Informatik 2 - Datenstrukturen
229
Herunterladen