Algorithmen und Datenstrukturen CS1017 Th. Letschert TH Mittelhessen Gießen University of Applied Sciences Datenstrukturen I: Lineare Sequenzen / Listen Datenstrukturen für Sequenzen Sequenzen in der Java-API: Listen und Ströme Iteratoren Algorithmen auf Sequenzen
Lineare Sequenzen / Listen Funktionaler Datentyp Liste Eine Liste ist eine endliche Kollektion von Werten, bei der jedem Wert eine eindeutige Position aus dem Bereich der natürlichen Zahlen zugeordnet ist. (a, b, c) ~ 0 a, 1 b, 2 c Listen-Werte: Alle Sequenzen (endliche total geordnete Folgen) von Element-Werten Operationen: Den Wert an (Index-) Position i feststellen Feststellen der Länge (Anzahl der Elemente) Seite 2
Lineare Sequenzen / Listen Imperativer Datentyp Liste Eine imperative (veränderliche) Liste ist eine endliche Kollektion von Variablen, bei der jeder Variablen eine eindeutige Position aus dem Bereich der natürlichen Zahlen zugeordnet ist. 0 a, ( a, b, c ) ~ 1 b, 2 c Listen-Variable: Alle veränderlichen Sequenzen (endliche total geordnete Folgen) von Variablen Operationen: Den Wert an (Index-) Position i feststellen Feststellen der Länge (Anzahl der Elemente) Ein Element an einer bestimmten Position einfügen Eventuell weitere abgeleitete Operationen (Vorgänger / Nachfolger, etc.) Eventuell Beschränkungen bei den Einfüge- und Zugriffs-Operationen (Nur am Anfang / Ende der Sequenz)... Seite 3
Lineare Sequenzen / Listen Im Zusammenhang mit Listen werden viele Begriffe gebraucht die eine völlig unterschiedliche, ähnliche oder gleiche Bedeutung haben. Es ist wichtig zu wissen, was genau gemeint ist, bzw. gemeint sein könnte und selbst eine klare Ausdrucksweise zu benutzen. Liste als (Daten-) Typ Alles mit einer von aussen beobachtbaren linearen Ordnung* Folge, Sequenz, lineare Sequenz, Vektor, Strom, sind weitere Bezeichnungen Unterscheidung: funktional (= unveränderlich) ist unveränderlich ~ ist ein Wert imperativ (= veränderlich) Die Liste und eventuell aber nicht unbedingt auch ihre Elemente können verändert werden ist veränderlich ~ ist eine Variable *siehe Veranstaltung Diskrete Mathematik oder https://de.wikipedia.org/wiki/ordnungsrelation Seite 4
Lineare Sequenzen / Listen Liste als (Daten-) Struktur Alles was tatsächlich in einer linearen Ordnung angeordnet ist Weitere Bezeichnungen: Folge, Sequenz, lineare Sequenz, Vektor, Strom, Auch hier kann zwischen veränderlich und unveränderlich unterschieden werden. Datenstrukturen werden aber in der Regel als veränderliche Beziehungen realisiert, auch wenn sie von aussen unveränderlich zu sein scheinen Seite 5
Imperative Listen (lineare Sequenzen) in der Java-API Imperative Listen / Sequenzen in der Java-API Imperative (Veränderliche) Listen werden in unterschiedlichen Varianten von der Java-API zur Verfügung gestellt Interfaces (Varianten in den möglichen Operationen) java.util Interface List<E> Einfügen und Entnehmen an beliebigen Positionen möglich java.util Interface Queue<E> Einfügen nur an einem Ende, Entnehmen nur am anderen Ende möglich java.util Interface Deque<E> Einfügen und Entnehmen nur an den beiden Enden möglich Klassen (ADTs, Varianten in der Implementierung der Interfaces) java.util Class ArrayList<E> Array-basierte Listenimplementierung java.util Class Vector<E> Array-basierte Listenimplementierung mit synchronisierten Operationen java.util Class ArrayDeque<E> Array-basierte Listenimplementierung mit Einfügen und Entnehmen nur an den beiden Enden möglich Seite 6
Funktionale Listen (lineare Sequenzen) in der Java-API Funktionale Listen / Sequenzen in der Java-API: Streams Die Java-API unterstützt ab Java-8 auch die funktionale Programmierung Dazu gehören funktionale / unveränderliche Listen mit speziellen Operationen Streams java.util.stream.stream ist ein Interface es bietet eine funktionale Sicht auf nicht-funktionale Listen / Sequenzen Beispiel: import java.util.arrays; import java.util.list; import java.util.stream.stream; public class Streams_App { public static void main(string[] args) { List<Integer> lst = Arrays.asList(new Integer[]{1,2,3,4,5,6,7,8,9,10); int sum1 = 0; for (int i: lst) { sum1 = sum1+i; System.out.println(sum1); Klassisch imperativ auf imperativer Liste: Summe mit Iterator Imperative Liste bekommt funktionales Gesicht und wird funktional summiert Stream<Integer> strm = lst.stream(); int sum2 = strm.reduce(0, (a, x) -> a+x ); System.out.println(sum2); Seite 7
Datenstrukturen für Listen Listen auf Basis von Arrays Listen können mit Hilfe von Arrays implementiert werden Datentyp Liste Vorteil Datenstruktur Array Effiziente Zugriffsoperationen Nachteil Aufwendiges Einfügen und Entfernen im Inneren Aufwendiges Vergrößern Eventuell Verschwendung von Speicherplatz Array-basierte Liste Implementierungen in der Java-API ArrayList<E> Vector<E> veraltet Stack<E> veraltet Verwenden Sie die Klasse ArrayList, es sei denn Sie wissen, dass und warum diese nicht geeignet ist. Verwenden Sie dann eine andere Klasse aus der API. Gibt es nichts Geeignetes dann implementieren Sie eine Klasse auf Basis von java.util.abstractlist. Seite 8
Datenstrukturen für Listen Eigene Listenimplementierung auf Basis von Arrays Listen auf Basis von Arrays sind leicht zu implementieren public class MyArrayList<E> { private int topindex = private int arraysize = Aufgabe: 0; // index of first empty position 10; // actual array size Ist dies eine Implementierung von java.util.list<e>? @SuppressWarnings("unchecked") private E[] array = (E[]) new Object[arraySize]; @SuppressWarnings("unchecked") public boolean add(e element) { if (topindex == arraysize) { E[] arraytemp = (E[]) new Object[arraySize+10]; System.arraycopy(array, 0, arraytemp, 0, arraysize); array = arraytemp; arraysize = arraysize+10; array[topindex++] = element; public static void main(string[] args) { return true; MyArrayList<String> l = new MyArrayList(); for (int i=0; i<15; i++) { l.add("blub "+i); public E get(int index) { return array[index]; System.out.println(l.size()); public int size() { return topindex; for (int i=0; i<l.size(); i++) { System.out.println(l.get(i)); Seite 9
Datenstrukturen für Listen Eigene Listenimplementierung auf Basis von Arrays Listen auf Basis von Arrays sind leicht zu implementieren Allerdings erfüllt eine einfache Implementierung nicht das Interface java.util.list<e> public class MyArrayList<E> implements List<E> { Es fehlen sehr (!) viele Methoden die im Interface deklariert sind! Seite 10
Datenstrukturen für Listen Eigene Listenimplementierung auf Basis von Arrays Die Java-API bietet eine Basisklasse zur Erleichterung der Implementierung eigener Array-basierter Listen: java.util Class AbstractList<E> Implementierungsskelett für eigene Listenimplementierungen durch Ableitung import java.util.abstractlist; import java.util.list; public class MyArrayList<E> extends AbstractList<E> implements List<E> { private int topindex = private int arraysize = 0; // index of first empty position 10; // actual array size @SuppressWarnings("unchecked") private E[] array = (E[]) new Object[arraySize]; @SuppressWarnings("unchecked") public boolean add(e element) { if (topindex == arraysize) { E[] arraytemp = (E[]) new Object[arraySize+10]; System.arraycopy(array, 0, arraytemp, 0, arraysize); array = arraytemp; arraysize = arraysize+10; array[topindex++] = element; return true; public E get(int index) { return array[index]; public int size() { return topindex; Seite 11 Die fehlenden Methoden werden von AbstractList geliefert!
Datenstrukturen für Listen Verwendungs-Beispiel der selbst definierten Liste: public static void main(string[] args) { Fragen: List<String> l = new MyArrayList<>(); 1. Warum gibt add true zurück? for (int i=0; i<15; i++) { l.add("blub "+i); 2. Funktionieren beide (überladenen) Varianten der Methode add? 3. Funktionieren die Schleifen? l.add(5, "Hi"); 4. Wie nennt man die Form der dritten Schleife? System.out.println(l.size()); 5. Implementiert die Klasse das Interface Iterable<E>? for (int i=0; i<l.size(); i++) { System.out.println(l.get(i)); for (String s : l) { System.out.println(s); Seite 12
Datenstrukturen für Listen Verwendungs-Beispiel der selbst definierten Liste: public static void main(string[] args) { Fragen: List<String> l = new MyArrayList<>(); 1. Warum gibt add true zurück? Das Interface List verlangt es so for (int i=0; i<15; i++) { l.add("blub "+i); 2. Funktionieren beide (überladenen) Varianten der Methode add? Nein, nur die erste, siehe API zu AbstractList l.add(5, "Hi"); 3. Funktioniert die Schleifen? Ja! System.out.println(l.size()); 4. Funktioniert die zweite Schleife? Ja! for (int i=0; i<l.size(); i++) { System.out.println(l.get(i)); 5. Wie nennt man die Form der dritten Schleife? for each Schleife for (String s : l) { System.out.println(s); 6. Implementiert die Klasse das Interface Iterable<E>? Ja: MyArrayList ~extends~> AbstractList ~implements~> List ~extends~> Iterable Seite 13
Iteratoren for-each-schleife ~> Iterator Elegantes Durchlaufen einer iterierbaren Kollektion Basiert auf einem Iterator. Iterator: Mechanismus zum Durchlaufen einer Kollektion ohne deren innere (Daten-) Strukturen kennen zu müssen. Iterator<Integer> iter = c.iterator(); Eine ordentliche Kollektion ist iterierbar!! Sie implementiert das Interface Iterable Sie hat eine Methode Iterator diese Methode liefert einen Iterator. while ( iter.hasnext() ) { System.out.println( iter.next() ); automatische Umwandlung durch den Compiler for (int i : c ) System.out.println(i); Die Maus (der Iterator) agiert im Hintergrund Seite 14
Iteratoren Kollektion Iterable und Iterator Iterable Die Kollektion Iterator Der Mechanismus um die Werte der Kollektion zu durchlaufen Ein Iterator kann das jeweils nächste Element zur Verfügung stellen und testen, ob alle Elemente durchlaufen wurden. Iterator Sinn Die Kollektion kann durchlaufen werden ohne den Aufbau und die Organisation der Kollektion kennen zu müssen Geheimnisprinzip: Ich will nicht jeden in meinen Keller lassen der bei mir eine Flasche nach der anderen trinkt! java.lang Iterable<T> java.lang Iterable<T> Iterator<T> iterator(); Iterator<T> iterator(); iterator java.util List<T> java.util List<T> java.lang Iterator<T> java.lang Iterator<T> next():t next():t hasnext():boolean hasnext():boolean java.util AbstractList<T> java.util AbstractList<T> MyArrayList<T> MyArrayList<T> Seite 15 iterator
Iteratoren Iterator Mechanismus zum Durchlaufen einer Kollektion deren inneren Aufbau man nicht kennt und der nicht offen gelegt werden soll. Die Kollektion stellt ihren Nutzern einen Iterator zur Verfügung Sie ist damit iterierbar (erfüllt java.lang Interface Iterable<T>) Ein Iterator kann das jeweils nächste Element zur Verfügung stellen und testen, ob alle Elemente durchlaufen wurden. Jede Kollektion, die Sie schreiben, muss iterierbar sein es sei denn Sie haben wirklich gute Gründe, die dagegen sprechen. Seite 16
Iteratoren Beispiel: Array-basierte Liste mit eigenem Iterator import java.util.iterator; public class SimpleIterableList<E> implements Iterable<E> { private int topindex = private int arraysize; private E[] element; 0; // index of first empty position // maximal size public SimpleIterableList(int maxsize) { this.arraysize = maxsize; element = (E[]) new Object[this.arraySize]; public void add(e e) { if (topindex == arraysize) throw new IllegalStateException(); element[topindex++] = e; public Iterator<E> iterator() { public Iterator<E> iterator() { return new Iterator<E>() { return new Iterator<E>() { private int pos = 0; private int pos = 0; public boolean hasnext() { public boolean { return pos hasnext() < topindex; return pos < topindex; public E get(int index) { if (index < 0 index >= topindex) throw new IllegalArgumentException(); return element[index]; public E next() { public E next() { return element[pos++]; return element[pos++]; public void remove() { public void remove() { throw throw new UnsupportedOperationException(); new UnsupportedOperationException(); ; ; Seite 17
Datenstrukturen für Listen Listen auf Basis von verketteten Zellen (verkettete Listen) Listen können mit Hilfe von verketteten Zellen implementiert werden Vorteil Datentyp Liste Schnelles Einfügen / Entfernen Datenstruktur Nachteil Langsamer Indexzugriff Implementierungen in der Java-API Liste mit Verkettung LinkedList<E> Verwenden Sie die Klasse LinkedList, wenn Einfügeund Entnahme-Operationen häufig vorkommen. Seite 18
Datenstrukturen für Listen Listen auf Basis von verketteten Zellen (verkettete Listen) Die Java-API bietet eine Basisklasse zur Erleichterung der Implementierung eigener verketteter Listen java.util Class AbstractSequentialList<E> Implementierungsskelett für eigene Listenimplementierungen durch Ableitung Komplexe Aufgabe da ein ListIterator implementiert werden muss. Seite 19
Datenstrukturen für Listen Beispiel verkettete Liste: Klassisch mit null-zeiger Zur Demonstration des Umgangs mit Verkettungen hier ein einfaches Beispiel einer verketteten Liste. public class MyLinkedList<E> { private static class Node<E>{ E data; Node<E> next; Node(E data) { this.data = data;... private int nodecount = 0; private Node<E> first; first public int size() { return nodecount; Knoten speichern die Informationen und einen Verwies auf den nächsten Knoten Seite 20
Datenstrukturen für Listen Beispiel verkettete Liste: Klassisch mit null-zeiger Anhängen, Abfragen und Löschen eines Elements public void add(e element) { if (first == null) { first = new Node<>(element); else { Node<E> l = first; while (l.next!= null) { l = l.next; l.next = new Node<>(element); nodecount++; public E get(int i) throws NoSuchElementException { Node<E> l = first; int j = 0; while (i!= j) { if (l == null) { throw new NoSuchElementException(); j++; l = l.next; if (l == null) { throw new NoSuchElementException(); return l.data; public void delete (int i) throws NoSuchElementException { if (first == null) { throw new NoSuchElementException(); int j = 0; Node<E> act = first; // points to the j-th element Node<E> prev = null; while (j < i && act!= null) { prev = act; act = prev.next; j++; if (act == null) { throw new NoSuchElementException(); else { nodecount--; if (prev!= null) { prev.next = act.next; else { first = act.next; Seite 21
Datenstrukturen für Listen Beispiel verkettete Liste: Klassisch mit null-zeiger Die Liste soll iterierbar sein. Dazu muss sie das Interface java.util.iterable implementieren import java.util.iterator; import java.util.nosuchelementexception; public class MyLinkedList<E> implements Iterable<E> { private static class Node<E>{ E data; Node<E> next; Node(E data) { this.data = data; private int nodecount = 0; private Node<E> first;... public Iterator<E> iterator() { // TODO Auto-generated method stub return null; Was muss diese Methode liefern? Seite 22
Datenstrukturen für Listen Beispiel verkettete Liste: Klassisch mit null-zeiger Die Liste soll iterierbar sein. Dazu muss sie das Interface java.util.iterable implementieren public Iterator<E> iterator() { return new Iterator<E>() { public boolean hasnext() { // TODO Auto-generated method stub return false; public E next() { // TODO Auto-generated method stub return null; public void remove() { // TODO Auto-generated method stub ; Seite 23
Datenstrukturen für Listen Beispiel verkettete Liste: Klassisch mit null-zeiger Die Liste soll iterierbar sein. Dazu muss sie das Interface java.util.iterable implementieren public Iterator<E> iterator() { return new Iterator<E>() { Node<E> actnode = first; public boolean hasnext() { return actnode!= null; public E next() { if (actnode == null) { throw new NoSuchElementException(); E value = actnode.data; actnode = actnode.next; return value; public void remove() { throw new UnsupportedOperationException(); ; Seite 24 Vergleichen Sie die Funktionalität dieser Methoden mit der der entsprechenden Methoden von java.util.iterator<e>
Algorithmen, Datentypen und Datenstrukturen Algorithmen auf Listen Algorithmen können auf der Schnittstelle oder im Inneren einer Liste agieren. Algorithmen auf dem Datentyp Liste Beispiel Suchen: Die Suche verwendet die öffentlichen Methoden der Liste Algorithmen auf sequentiellen Datenstrukturen (Array / Verkettete-Liste) Beispiel Suchen: Die Suche operiert auf den Datenstrukturen der Listen-Implementierung public <T> boolean seach(list<t> l, T element) { for (int i= 0; i<l.size(); i++) { if (l.get(i).equals(element)) { return true; Suche in einer Liste: Laufzeit: O(n) da schlimmstenfalls auf alle n Elemente zugegriffen werden muss. return false; Angenommen die Liste ist als verkette Liste implementiert so wie MyLinkedList oben welche Komplexität hat der Algorithmus in dieser Implementierung dann? Seite 25
Algorithmen, Datentypen und Datenstrukturen Algorithmen auf Listen Algorithmen können auf der Schnittstelle oder im Inneren einer Liste agieren. public <T> boolean seach(list<t> l, T element) { for (int i= 0; i<l.size(); i++) { if (l.get(i).equals(element)) { return true; return false; Suche in einer Liste: Laufzeit dieses Algorithmus': O(n): Für den Zugriff l.get(i) wird konstante Laufzeit angenommen. Wenn die Liste als verkette Liste implementiert wurde, dann ist die Annahme O(1) für l.get(i) falsch und damit die Annahme O(n) für die Suche ebenfalls. Die sequentielle Suche als solche hat aber trotzdem die Komplexität O(n)! Sie ist definiert unter der Voraussetzung, dass der Zugriff auf ein Element der Liste O(1) ist. Eine Annahmen, die sinnvollerweise implizit bei allen Komplexitäts-Betrachtungen von Algorithmen auf Listen vorausgesetzt wird. Seite 26
Algorithmen, Datentypen und Datenstrukturen Algorithmen auf Listen Algorithmen operieren auf Datentypen, hinter denen dann die Datenstrukturen der Implementierung dieser Datentypen liegen Beispiel Suchen: public <T> boolean seach(mylinkedlist<t> l, T element) { for (int i= 0; i<l.size(); i++) { if (l.get(i).equals(element)) { return true; O(n) Operationen auf der Liste (get) return false; public class MyLinkedList<E> {... public E get(int i) throws NoSuchElementException { Node<E> l = first; int j = 0; while (i!= j) { if (l == null) { throw new NoSuchElementException(); j++; l = l.next; if (l == null) { throw new NoSuchElementException(); return l.data;... Seite 27 Jede get-operation ist O(n) Diese Suche ist O(n2). Das liegt aber nicht an am schlechten Suchalgorithmus, sondern an einer für dieses Einsatz ungeeigneten, schlechten Datenstruktur.
Algorithmen, Datentypen und Datenstrukturen Algorithmen und Datentypen / Datenstrukturen Für Algorithmen sind Datenstrukturen interessant. Der Aufruf einer Methode eines Datentyps ist nichts weiter als ein Methodenaufruf. (Die Veranstaltung heißt darum auch Datenstrukturen und Algorithmen ) Trotzdem spielen Datentypen eine wichtige Rolle : Datentypen erfordern die Implementierung einer bestimmten Funktionalität. Diese wird durch Algorithmen realisiert, Deren Effizienz massiv davon beeinflusst wird, mit welchen Datenstrukturen der Datentyp implementiert wurde. Sequenz / Sequentielle Datenstruktur Datentypen und Datenstrukturen werden oft verwechselt oder identifiziert Das ist teilweise berechtigt, verwirrt aber gelegentlich. Liste ist für uns ein Datentyp, bei dem unterschiedliche Datenstrukturen zur Implementierung verwendet werden können Sequenz oder Sequenzielle Datenstruktur Ein Datentyp mit der Eigenschaft dass Alle Elemente können über einen Index zugegriffen werden können der Zugriff auf jedes beliebige Element ist O(1) Seite 28
Algorithmen auf Sequenzen Sequenz / Sequenzielle Datenstruktur Datenstruktur (elementarer Datentyp) mit Listen-Operationen und mit Zugriffsoperationen der Komplexität O(1) Algorithmen auf Sequenzen Suchen finde ein Element (mit einer bestimmten Eigenschaft) in einer Sequenz Sortieren erzeuge eine Permutation der Elemente, derart, dass ein Ordnungskriterium erfüllt ist. Muster erkennen z.b. finde die längste gemeinsame Teilsequenz von zwei Sequenzen... Seite 29