Programmieren in Java Datenstrukturen
Name: Patrick Förster, Michael Hasseler
Programmieren in Java
Was bisher geschah • Bisher wurden zur Gruppierung von Daten immer Arrays genutzt • Bis auf die Index-Funktion gibt es allerdings keinerlei logischer Operationen, mit denen der Array manipuliert werden könnte • Wenn die zu organisierende Menge dynamisch ist, machen Arrays einem das Leben daher schwer: • Arrays sind statisch: Länge einmal festgelegt, nicht mehr änderbar • „Mal eben“ ein Element hinzufügen oder entfernen ist aufwändig • Zumeist muss der Array-Inhalt kopiert werden • Große Arrays großer Kopieraufwand • Datentypen, die eine Menge an Daten gruppieren und auf dieser Gruppierung eine gewisse Logik bereitstellen werden Datenstrukturen genannt • Datenstrukturen & Algorithmen meist eine eigene Vorlesung!
Name: Patrick Förster, Michael Hasseler
2
Programmieren in Java
Lauf- und Zugriffszeit • Wichtige Punkte bei der Auswahl einer Datenstruktur sind • Laufzeit: Die Anzahl der Schritte, um ein bestimmtes Element zu finden • Zugriffszeit: Schritte, um auf ein bestimmtes Element zuzugreifen • Beispiel Array: • Zugriffszeit: • Array liegt „aneinander gereiht“ im Speicher • Zugriff: Speicheradresse des 1. Elementes + Position des gesuchten Zugriff in einem Schritt (Notation: Ɵ(1), „konstante Zugriffszeit“) • Laufzeit: • Im schlechtesten Fall ist das Element nicht vorhanden • Daher muss es mit allen Elementen verglichen werden Ɵ(n), n ist die Anzahl der Elemente („lineare Laufzeit“)
Name: Patrick Förster, Michael Hasseler
3
Programmieren in Java
Collection-API • Java bietet mit der Collection-API einen Basissatz an Datenstrukturen • All diese Strukturen sind dynamisch und damit flexibler als Arrays • Jede der Strukturen basiert auf einem der folgenden Interfaces • Collection: Interface für Listen bzw. Mengen • Map: Interface für eine Gruppe assoziativ angeordneter Elemente • Sowohl Collection als auch Map sind generisch, d.h. der Datentyp der Inhalte kann generisch spezifiziert werden: Collection list = new LinkedList(); dice("Dies ist ein String der durcheinander gewürfelt wird", list); [ein, ist, gewürfelt, String, Dies, wird, der, durcheinander]
• LinkedList ist eine Implementierung von Collection • Angenommen: dice fühlt die List in zufälliger Reihenfolge mit StringFragmenten getrennt nach dem Leerzeichen aus dem übergebenen String
Name: Patrick Förster, Michael Hasseler
4
Programmieren in Java
Das Interface Collection public interface Collection extends Iterable { boolean add(E e); boolean remove(Object o); boolean contains(Object o); int size(); boolean isEmpty(); Iterator iterator(); Object[] toArray(); T[] toArray(T[] a); boolean containsAll(Collection c); boolean addAll(Collection c); boolean retainAll(Collection c); void clear(); }
Name: Patrick Förster, Michael Hasseler
5
Programmieren in Java
Das Interface Collection • Jede Implementierung des Collection-Interfaces unterstützt das • Hinzufügen und Entfernen von Elementen (einzeln oder massenhaft) • Überprüfen ob gewisse Inhalte vorhanden sind • Abfragen der Größe • Umwandeln in einen Array • Desweiteren erweitert Collection das Interface Iterable • Dies schreibt die Methode iterator() vor • Alle Klassen, die dieses Interface implementieren können in der foreach-Schleife genutzt werden: Collection list = new LinkedList(); dice("Dies ist ein String der durcheinander wird", list); for (String element : list) { System.out.println(element); }
Name: Patrick Förster, Michael Hasseler
6
Programmieren in Java
Listen • Listen sind Collections mit einer Ordnungshierarchie • Auf einen bestimmten Inhalte innerhalb der Liste kann über seinen Index (~ Position) zugegriffen werden List list = new LinkedList(); String fragment = list.get(3);
// füllen mit dice
• List ist ein Interface mit verschiedenen bereits vorhandenen Implementierungen: • ArrayList: das dynamische Pendant zum Array • Vector: siehe ArrayList • LinkedList: Die Elemente sind miteinander verkettet (nächste Folie) • Sowohl ArrayList als auch Vector greifen intern auf Arrays zurück • Die Unterschiede liegen in der Thread-Sicherheit (Vector) und der Art und Weise wie der interne Array verwaltet wird
Name: Patrick Förster, Michael Hasseler
7
Programmieren in Java
LinkedList • Jedes Element in der LinkedList wird in einem „Node“ gespeichert • Jeder Node kennt seinen Vorgänger und seinen Nachfolger • Der erste und letzte Node sind in der Liste „verlinkt“
Node first
last
data
Name: Patrick Förster, Michael Hasseler
8
Programmieren in Java
LinkedList (Code) • Node-Klasse LinkedListNode public class LinkedListNode { protected LinkedListNode prev; protected LinkedListNode next; protected DATA data;
protected LinkedListNode(LinkedListNode prev, LinkedListNode next, DATA data) { this.prev = prev; this.next = next; this.data = data; } public String toString() {return data == null ? "" : data.toString();}
}
• Man beachte: • Der Konstruktor ist protected Instanzen nur innerhalb des eigenen Paketes erzeugbar • Keine Getter/Setter, da auch die Attribute protected
Name: Patrick Förster, Michael Hasseler
9
Programmieren in Java
LinkedList (Code II) public class LinkedList /* … */ { private LinkedListNode first = null; private LinkedListNode last = null; public LinkedList() {} protected LinkedListNode getNode(int index) { LinkedListNode node = first; int i = 0; while (i != index && node != null) { node = node.next; i++; } return node; }
public DATA get(int index) { LinkedListNode node = getNode(index); return node == null ? null : node.data; } … }
Name: Patrick Förster, Michael Hasseler
10
Programmieren in Java
Lauf- und Zugriffzeit (ArrayList, LinkedList) • ArrayList/Vector: • Intern werden die Elemente in einem Array gespeichert • Zugriffszeit: Ɵ(1) • Laufzeit: Ɵ(n) • LinkedList • Elemente sind verkettet • Kein direkter Zugriff • Um das n-te Element zu erreichen, muss die Kette vom ersten bis zum n-1 Element durchlaufen werden Zugriffszeit: Ɵ(n) • Im schlechtesten Fall ist ein gesuchtes Element nicht vorhanden • Es muss die gesamte Kette durchlaufen werden, um dies festzustellen Laufzeit: Ɵ(n) Name: Patrick Förster, Michael Hasseler
11
Programmieren in Java
LinkedList • Suche eines Elements in einer LinkedList ist vergleichsweise langsam • Das Löschen und Hinzufügen jedoch ist schneller als bei ArrayList/Vector • Da jeder Node seinen Vorgänger und Nachfolger kennt, lassen sich beide Operationen durch „umbiegen“ der Verlinkung in einem Schritt durchführen • ArrayList/Vector müssen die richtige Stelle zum Löschen finden, den Array unter Umständen kopieren und neufüllen Löschen first
last
Hinzufügen
first
Name: Patrick Förster, Michael Hasseler
last
12
Programmieren in Java
LinkedList (Code III) public boolean add(int index, DATA data) { if (index < 0 || index > size()) throw new IndexOutOfBoundsException(); if (first == null) { first = new LinkedListNode(null, null, data); last = first; return true ; } if (index == size()) { last.next = new LinkedListNode(last, null, data); last = last.next; return true; } LinkedListNode temp = getNode(index); LinkedListNode node = new LinkedListNode(temp.prev, temp, data); if (temp == first) first = node; if (temp.prev != null) temp.prev.next = node; temp.prev = node; return true; }
Name: Patrick Förster, Michael Hasseler
13
Programmieren in Java
LinkedList (Code IV) public boolean remove(int index) { if (index < 0 || index >= size()) throw new IndexOutOfBoundsException(); LinkedListNode node = getNode(index); if (node.prev != null) node.prev.next = node.next; if (node.next != null) node.next.prev = node.prev; if (node == first) first = node.next; if (node == last) last = node.prev; return true; } public int size() { LinkedListNode node = first; int size = 0; while (node != null) { size++; node = node.next; } return size; }
Name: Patrick Förster, Michael Hasseler
14
Programmieren in Java
Stack • Ein Stack ist bereits im Kapitel „Speichermanagement“ aufgetaucht • Der Stack-Speicher baute sich Frame für Frame auf einer Seite des Speichers auf • Nach jedem Methodenende wird der Stack auf genau der Seite wieder abgebaut • Die Stack-Datenstruktur ist analog aufgebaut • Auch LIFO (Last-In-Firs t-Out) genannt peek()
pop()
push(Object)
boolean empty(); E peek (); E pop(); E push(E o); int search(Object o);
• Zugriff: Ɵ(1) (es ist immer nur das letzte Element zugreifbar!) • Laufzeit: Ɵ(n) (es müssen alle Elemente verglichen werden) Name: Patrick Förster, Michael Hasseler
15
Programmieren in Java
Queue • Ähnlich dem Stack • Eine Queue (Warteschlange) wird „hinten“ auf- und „vorne“ abgebaut • Daher: FIFO (First-In-First-Out) E element(); boolean offer(E o); E peek(); E poll(); E remove(); element()/peek()
offer(object)
poll()/remove()
• Zugriff: Ɵ(1), Laufzeit: Ɵ(n) (beides, analog zum Stack) Name: Patrick Förster, Michael Hasseler
16
Programmieren in Java
Set • Spezielle Collection mit der Einschränkung: • Jedes Element in der Menge ist eindeutig, d.h. es gibt keine zwei Element e1 und e2 für die gilt e1.equals(e2) == true
• Sets müssen nicht zwangsläufig geordnet sein • Zugriffs- und Laufzeit variieren wie schon bei der Liste je nach Implementierung
Name: Patrick Förster, Michael Hasseler
17
Programmieren in Java
Map • Der Datentyp Map wird auch als „assoziativer Speicher“ bezeichnet • Jedes Element in der Map ist eindeutig über einen Schlüssel assoziiert • Eine Map enthält demnach Schlüssel-Wert Paare Key Key Key Key … Key
1 2 3 4 n
Value Value Value Value … Value
1 2 3 4 n
Name: Patrick Förster, Michael Hasseler
public interface Map { int size(); boolean isEmpty(); boolean containsKey(Object key); boolean containsValue(Object value); V get(Object key); V put(K key, V value); V remove(Object key); void putAll(Map